与外围设备交换数据意味着向其发送数据或从其接收数据,为此,我们已经看到,我们必须使用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!