Skip to content

Latest commit

 

History

History
58 lines (38 loc) · 5.2 KB

File metadata and controls

58 lines (38 loc) · 5.2 KB

八、附加信息:使用字符驱动

与字符驱动交换数据

与外围设备交换数据意味着向其发送数据或从其接收数据,为此,我们已经看到,我们必须使用write()read()系统调用,其原型在内核中定义,如下所示:

ssize_t write(struct file *filp,
              const char __user *buf, size_t count,
              loff_t *ppos);
ssize_t read(struct file *filp,
              char __user *buf, size_t count,
              loff_t *ppos);

另一方面,它们在用户空间中的对应部分如下所示:

ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);

前面的原型(在内核或用户空间中)看起来很相似,但是,当然,它们有不同的含义,作为驱动开发人员,我们必须完全知道这些含义是什么,才能准确地完成我们的工作。

先说write();当我们从用户空间程序调用write()系统调用时,我们必须提供一个文件描述符,fd;一个缓冲区,buf,填充要写入的数据;和缓冲区大小,count。然后,系统调用返回一个值,该值可以是负值(如果有错误)、正值(指实际写入了多少字节)或零(表示没有写入任何内容)。

Note that count does not represent how many bytes we wish to write, but just the buffer size! In fact, write() can return a positive value that is smaller than count. That's why I enclosed the write() system call of the chrdev_test.c program inside a for() loop! In fact, if I have to write a buffer that is 10 bytes long and write() returns, for instance, 4 bytes, I have to recall it until all the remaining 6 bytes have been written.

从内核空间的角度来看,我们将文件描述符fd看作struct file *filp(存储我们的文件描述符的内核信息的地方),而数据缓冲区由buf指针和count变量指定(目前不考虑ppos指针;这将很快解释)。

write()内核原型中我们可以看到,buf参数标有__user属性,指出这个缓冲区来自用户空间,所以我们不能直接从中读取。事实上,这个内存区域是虚拟的,因此,当我们的驱动的write()方法被执行时,它实际上不能被映射到真实的物理内存中!为了解决这种情况,内核提供了copy_from_user()功能,如下所示:

unsigned long copy_from_user(void *to,
                   const void __user *from, unsigned long n);

我们可以看到,这个函数从用户空间缓冲区from中取数据,然后在验证from指向的内存区域可以读取后,复制到to指向的缓冲区。一旦数据被传输到内核空间(在to指向的缓冲区内),我们的驱动就可以自由访问它。

read()系统调用执行相同的步骤(即使方向相反)。我们还有一个文件描述符,fd;一个缓冲区,buf,读取数据必须放入其中,以及它的count大小。然后,系统调用返回一个值,该值可以是负值(如果有错误)、正值(这意味着实际读取了多少字节)或零(这意味着我们处于文件末尾)。

Again, we should notice that count is not how many bytes we wish to read but just the buffer size. In fact, read() can return a positive value smaller than count, which is why I put it inside a for() loop in the chrdev_test.c program. More significantly than in the preceding write() case, the read() system call can also return 0, which means end-of-file; that is, no more data is available from this file descriptor and we should stop reading.

对于前面的write()情况,我们还有buf指向的缓冲区关联的__user属性,这意味着要从中读取数据,必须使用copy_to_user()函数,定义如下:

unsigned long copy_to_user(void __user *to,
                   const void *from, unsigned long n);

Both copy_from_user() and copy_to_user() are defined in the linux/include/linux/uaccess.h file.

现在,在本节结束之前,我们必须花一些时间在ppos指针上,这两个内核原型中都有。

当我们希望读取存储在文件中的一些数据时,我们必须多次使用read()系统调用(尤其是在文件相当大并且我们的内存缓冲区很小的情况下)。为此,我们希望简单地多次调用read(),而不必费心记录我们在之前的每次迭代中到达的位置;例如,如果我们有一个大小为 16 KB 的文件,并且我们希望通过使用 4 KB 的内存缓冲区来读取它,我们简单地调用read()系统调用四次,但是每个调用应该如何知道前一个调用在哪里完成了它的工作?这个任务被分配给ppos指针:当文件被打开时,它首先指向文件的第一个字节(在索引 0 处),然后,每次调用read()时,系统调用本身将它移动到下一个位置,这样接下来的read()调用就知道它应该从哪里开始读取下一个数据块。

Note that ppos is unique for both read and write operations, so if we perform read() first and then write(), the data will be written, not at beginning of the file, but exactly where the preceding read() call finished its operation!