zoukankan      html  css  js  c++  java
  • 使用cat读取和echo写内核文件节点的一些问题

    作者

    pengdonglin137@163.com
    彭东林
     

    平台

    busybox-1.24.2
    Linux-4.10.17
    Qemu+vexpress-ca9
     

    概述

    在写驱动的时候,我们经常会向用户空间导出一些文件,然后用户空间使用cat命令去读取该节点,从而完成kernel跟user的通信。但是有时会发现,如果节点对应的read回调函数写的有问题的话,使用cat命令后,节点对应的read函数会被频繁调用,log直接刷屏,而我们只希望read被调用一次,echo也是一样的道理。背后的原因是什么呢?如何解决呢?下面我们以debugfs下的节点读写为例说明一下。
     

    正文

     

    一、read和write的介绍

     
    1、系统调用 read
     
    ssize_t read(int fd, void *buf, size_t count);
    这个函数会从fd表示的文件描述符中读取count个字节到buf缓冲区当中,返回值有下面几种:
    如果返回值大于0,表示实际读到的字节数,返回0的话,表示读到了文件结尾,同时文件的file position也会被更新。实际读到的字节数可能会比count小。
    如果返回-1,表示读取失败,errno会被设置为相应的值。
     
    2、系统调用 write
    ssize_t write(int fd, const void *buf, size_t count);
    这个函数将以buf为首地址的缓冲区当中的count个字节写到文件描述符fd表示的文件当中,返回值:
    返回正整数,表示实际写入的字节数,返回0表示没有任何东西被写入,同时文件位置指针也会被更新
    返回-1,表示写失败,同时errno会被设置为相应的值
     
     
    3、LDD3上对驱动中实现的read回调函数的解释
     
    原型:    ssize_t (*read) (struct file *fp, char __user *user_buf, size_t count, loff_t *ppos);
    fp 被打开的节点的文件描述符
    user_buf表示的是用户空间的一段缓冲区的首地址,从kernel读取的数据需要存放该缓冲区当中
    count表示用户期望读取的字节数
    *ppos表示当前当前文件位置指针的大小,这个值会需要驱动程序自己来更新,初始大小是0
      
    如果返回值等于传递给read系统调用的count参数,则说明所请求的字节数传输成功完成。这是最理想的情况
    如果返回值是正的,但是比count小,则说明只有部分数据传输成功。这种情况下因设备的不同可能有许多原因。大部分情况下,程序会再次读数据。例如,如果用fread函数读数据,这个库函数就会不断调用系统调用,直至所请求的数据传输完毕为止
    如果返回值为0,则表示已经达到了文件尾
    负值意味着发生了错误,该值指明了发生了什么错误,错误码在<linux/errno.h>中定义。比如这样的一些错误:-EINTR(系统调用被中断)或者-EFAULT(无效地址)
     
    4、LDD3上对驱动中实现的write回调函数的解释
     
    原型: ssize_t (*write) (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos);
    fp:被打开的要写的内核节点的文件描述符
    user_buf:表示的是用户空间的一段缓冲区的首地址,其中存放的是用户需要传递给kernel的数据
    count:用户期望写给kernel的字节数
    *ppos:文件位置指针,需要驱动程序自己更新
     
    如果返回值等于count,则完成了所请求数目的字节传输
    如果返回值为正的,但小于count,则这传输了部分数据。程序很可能再次试图写入余下的数据
    如果返回值为0,意味着什么也没有写入。这个结果不是错误,而且也没有理由返回一个错误码。再次重申,标准库会重复调用write
    负值意味着发生了错误,与read相同,有效的错误码定义在<linux/errno.h>中
     
    上面加粗的红色字体引起驱动中的write或者read被反复调用的原因。
     

    二、简略的分析一下read和write系统调用的实现

     
    在用户空间调用read函数后,内核函数vfs_read会被调用:
     1 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
     2 {
     3     ssize_t ret;
     4 
     5     if (!(file->f_mode & FMODE_READ))
     6         return -EBADF;
     7     if (!(file->f_mode & FMODE_CAN_READ))
     8         return -EINVAL;
     9     if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
    10         return -EFAULT;
    11 
    12     ret = rw_verify_area(READ, file, pos, count);
    13     if (!ret) {
    14         if (count > MAX_RW_COUNT)
    15             count =  MAX_RW_COUNT;
    16         ret = __vfs_read(file, buf, count, pos);
    17         if (ret > 0) {
    18             fsnotify_access(file);
    19             add_rchar(current, ret);
    20         }
    21         inc_syscr(current);
    22     }
    23 
    24     return ret;
    25 }
    下面是需要关注的:
    第9行检查用户空间的buf缓冲区是否可以写入
    第14行检查count的大小,这里MAX_RW_COUNT被设置为1个页的大小,这里的值是4KB,也就是一次用户一次read最多获得4KB数据
    第16行调用__vfs_read,这个函数最终会调用到我们的驱动中的read函数,可以看到这个函数的参数跟驱动中的read函数一样,驱动中read返回的数字ret会返回给用户,这里并没有看到更新pos,所以需要在我们的驱动中自己去更新。
     
    用户空间调用write函数后,内核函数vfs_write会被调用:
     1 ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
     2 {
     3     ssize_t ret;
     4 
     5     if (!(file->f_mode & FMODE_WRITE))
     6         return -EBADF;
     7     if (!(file->f_mode & FMODE_CAN_WRITE))
     8         return -EINVAL;
     9     if (unlikely(!access_ok(VERIFY_READ, buf, count)))
    10         return -EFAULT;
    11 
    12     ret = rw_verify_area(WRITE, file, pos, count);
    13     if (!ret) {
    14         if (count > MAX_RW_COUNT)
    15             count =  MAX_RW_COUNT;
    16         file_start_write(file);
    17         ret = __vfs_write(file, buf, count, pos);
    18         if (ret > 0) {
    19             fsnotify_modify(file);
    20             add_wchar(current, ret);
    21         }
    22         inc_syscw(current);
    23         file_end_write(file);
    24     }
    25 
    26     return ret;
    27 }

    这里需要关注:

    第9行,检查用户空间的缓冲区buf是否可以读
    第15行,限制一次写入的数据最多为1页,比如4KB
    第17行的_vfs_write的参数跟驱动中的write的参数一样,__vfs_write的返回值ret也就是用户调用write时的返回值,表示实际写入的字节数,这里也没有看到更新pos的代码,所以需要我们自己在驱动的write中实现
     

    三、简略分析cat和echo的实现

    由于使用的根文件系统使用busybox做的,所以cat和echo的实现在busybox的源码中,如下:
    coreutils/cat.c
    coreutils/echo.c
     
    CAT:
    下面简略分析cat的实现,cat的默认实现采用了sendfile,采用sendfile可以减少不必要的内存拷贝,从而提高读写效率,这就是所谓的Linux的“零拷贝”。为了便于代码分析,可以关闭这个功能,然后cat就会调用read和write实现了:
    Busybox Settings  --->
        General Configuration  --->
            [ ] Use sendfile system call
     
    下面是cat的核心函数:
    以 cat xxx为例其中src_fd就是被打开的内核节点的文件描述符,dst_fd就是标准输出描述符,size是0
     1 static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
     2 {
     3     int status = -1;
     4     off_t total = 0;
     5     bool continue_on_write_error = 0;
     6     ssize_t sendfile_sz;
     7     char buffer[4 * 1024];   // 用户空间缓冲区,4KB大小
     8     enum { buffer_size = sizeof(buffer) };  // 每次read期望获得的字节数
     9 
    10     sendfile_sz = 0;
    11     if (!size) {
    12         size =  (16 * 1024 *1024); // 刚开始,如传入的size是0,这里将size设置为16MB
    13         status = 1; /* 表示一直读到文件结尾,也就是直到read返回0 */
    14     }
    15 
    16     while (1) {
    17         ssize_t rd;
    18         
    19         rd = safe_read(src_fd, buffer, buffer_size);  // 这里调用的就是read, 读取4KB,rd是实际读到的字节数
    20         if (rd < 0) {
    21             bb_perror_msg(bb_msg_read_error);
    22             break;
    23         }
    24  read_ok:
    25         if (!rd) { /* 表示读到了文件结尾,那么结束循环 */
    26             status = 0;
    27             break;
    28         }
    29         /* 将读到的内容输出到dst_fd表示的文件描述符 */
    30         if (dst_fd >= 0 && !sendfile_sz) {
    31             ssize_t wr = full_write(dst_fd, buffer, rd);
    32             if (wr < rd) {
    33                 if (!continue_on_write_error) {
    34                     bb_perror_msg(bb_msg_write_error);
    35                     break;
    36                 }
    37                 dst_fd = -1;
    38             }
    39         }
    40         
    41         total += rd;  // total记录的是读到的字节数的累计值
    42         if (status < 0) { /* 如果传入的size不为0,那么status为-1,直到读到size个字节后,才会退出。如果size为0,这个条件不会满足 */
    43             size -= rd;
    44             if (!size) {
    45                 /* 'size' bytes copied - all done */
    46                 status = 0;
    47                 break;
    48             }
    49         }
    50     }
    51  out:
    52     return status ? -1 : total;  // 当读完毕,status为0,这里返回累计读到的字节数
    53 }

    从上面的分析我们知道如下信息:

    使用cat xxx时,上面的函数传入的size为0,那么上面的while循环会一直进行read,直到出错或者read返回0,read返回0也就是读到文件结尾。最后如果出错,那么返回-1,否则的话,返回读到的累计的字节数。
    到这里,应该就是知道为什么驱动中的read会被频繁调用了吧,也就是驱动中的read的返回值有问题。
     
    ECHO:
    echo的核心函数是full_write
    这里fd是要写的内核节点,buf缓冲区中存放的是要写入的内容,len是buf缓冲区中存放的字节数
     1 ssize_t FAST_FUNC full_write(int fd, const void *buf, size_t len)
     2 {
     3     ssize_t cc;
     4     ssize_t total;
     5 
     6     total = 0;
     7 
     8     while (len) {
     9         cc = safe_write(fd, buf, len);
    10 
    11         if (cc < 0) {
    12             if (total) {
    13                 /* we already wrote some! */
    14                 /* user can do another write to know the error code */
    15                 return total;
    16             }
    17             return cc;  /* write() returns -1 on failure. */
    18         }
    19 
    20         total += cc;
    21         buf = ((const char *)buf) + cc;
    22         len -= cc;
    23     }
    24 
    25     return total;
    26 }

    上面的函数很简单,可以得到如下信息:

    如果write的函数返回值cc小于len的话,会一直调用write,直到报错或者len个字节全部写完。而这里的cc对应的就是我们的驱动中write的返回值。最后,返回实际写入的字节数或者一个错误码。
    到这里,应该也已经清除为什么调用一次echo后,驱动的write为什么会被频繁调用了吧,还是驱动中write的返回值的问题。
     
    知道的上面的原因,下面我们结合一个简单的驱动看看。
     

    四、实例分析

    1、先看两个刷屏的例子

    这个驱动在/sys/kernel/debug生成一个demo节点,支持读和写。

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/debugfs.h>
     4 #include <linux/fs.h>
     5 #include <asm/uaccess.h>
     6 
     7 static struct dentry *demo_dir;
     8 
     9 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
    10 {
    11     char kbuf[10];
    12     int ret, wrinten;
    13 
    14     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
    15         user_buf, count, *ppos);
    16 
    17     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    18 
    19     ret = copy_to_user(user_buf, kbuf, wrinten+1);
    20     if (ret != 0) {
    21         printk(KERN_ERR "read error");
    22         return -EIO;
    23     }
    24 
    25     *ppos += wrinten;
    26 
    27     return wrinten;
    28 }
    29 
    30 static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
    31 {
    32     char kbuf[10] = {0};
    33     int ret;
    34 
    35     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
    36            user_buf, count, *ppos);
    37 
    38     ret = copy_from_user(kbuf, user_buf, count);
    39     if (ret) {
    40         pr_err("%s: write error
    ", __func__);
    41         return -EIO;
    42     }
    43 
    44     *ppos += count;
    45 
    46     return 0;
    47 }
    48 
    49 static const struct file_operations demo_fops = {
    50     .read = demo_read,
    51     .write = demo_write,
    52 };
    53 
    54 static int __init debugfs_demo_init(void)
    55 {
    56     int ret = 0;
    57 
    58     demo_dir = debugfs_create_file("demo", 0444, NULL,
    59         NULL, &demo_fops);
    60 
    61     return ret;
    62 }
    63 
    64 static void __exit debugfs_demo_exit(void)
    65 {
    66     if (demo_dir)
    67         debugfs_remove(demo_dir);
    68 }
    69 
    70 module_init(debugfs_demo_init);
    71 module_exit(debugfs_demo_exit);
    72 MODULE_LICENSE("GPL");

    我们先来看看运行结果:

    先试试写:
    [root@vexpress mnt]# echo 1 > /d/demo
    执行这个命令并不会返回,会卡主,再看看kernel log,已经刷屏:
    [ 1021.547015] user_buf: 00202268, count: 2, ppos: 0
    [ 1021.547181] user_buf: 00202268, count: 2, ppos: 2
    [ 1021.547319] user_buf: 00202268, count: 2, ppos: 4
    [ 1021.547466] user_buf: 00202268, count: 2, ppos: 6
    .... ....
    [ 1022.008736] user_buf: 00202268, count: 2, ppos: 6014
    [ 1022.008880] user_buf: 00202268, count: 2, ppos: 6016
    [ 1022.009012] user_buf: 00202268, count: 2, ppos: 6018
    ... ...
     
    再试试读:
    [root@vexpress mnt]# cat /d/demo
    HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHello... ...
    可以看到,终端被Hello填满了,再看看kernel log,刷屏了:
    [ 1832.074616] user_buf: becb6be8, count: 4096, ppos: 0
    [ 1832.075033] user_buf: becb6be8, count: 4096, ppos: 5
    [ 1832.075240] user_buf: becb6be8, count: 4096, ppos: 10
    [ 1832.075898] user_buf: becb6be8, count: 4096, ppos: 15
    [ 1832.076093] user_buf: becb6be8, count: 4096, ppos: 20
    [ 1832.076282] user_buf: becb6be8, count: 4096, ppos: 25
    [ 1832.076468] user_buf: becb6be8, count: 4096, ppos: 30
    [ 1832.076653] user_buf: becb6be8, count: 4096, ppos: 35
    [ 1832.076841] user_buf: becb6be8, count: 4096, ppos: 40
    ... ...
     
    可以看到规律,对于write,每次的count都是2,因为写下来的是个字符串的"1",ppos以2为台阶递增。此外,可以看到user_buf每次都相同,结合echo源码可以发现,用户的user_buf是在堆上分配的,所以地址比较小
    对于read,每次要读的count都是4KB,ppos是以5为台阶递增,正好是strlen("Hello"),user_buf的值每次都相同,结合cat源码可以发现,用户的user_buf是在栈上分配的,所以地址比较大
    下图是x86系统下Linux进程的进程地址空间的内存布局,这是只是说明一下意思。
     
     
    下面开始分别针对write和read进行修改:
     

    2、对write进行修改

    write版本2:
    既然经过前面的分析,知道write被频繁调用的原因是用户调用write实际写入的字节数小于期望的,而用户的write的返回值来自驱动的write,那么我们直接然write返回count不就可以了吗。
     1 static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10] = {0};
     4     int ret;
     5 
     6     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
     7           user_buf, count, *ppos);
     8 
     9     ret = copy_from_user(kbuf, user_buf, count);
    10     if (ret) {
    11         pr_err("%s: write error
    ", __func__);
    12         return -EIO;
    13     }
    14 
    15     *ppos += count;
    16 
    17     return count;
    18 }

    验证:

    [root@vexpress mnt]# echo 1 > /d/demo
    敲完回车后,立马就返回了,kernel log也只打印了一次:
    [ 2444.363351] user_buf: 00202408, count: 2, ppos: 0
     
    write版本3:
    其实,kernel提供了一个很方便的函数,simple_write_to_buffer,这个函数专门完成从user空间向kernel空间拷贝数据:
    1 static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
    2 {
    3     char kbuf[10] = {0};
    4 
    5     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
    6            user_buf, count, *ppos);
    7 
    8     return simple_write_to_buffer(kbuf, sizeof(kbuf), ppos, user_buf, count);
    9 }
    验证:
    [root@vexpress mnt]# echo 1 > /d/demo
    敲完回车后,立马就返回了,kernel log也只打印了一次:
    [ 2739.984844] user_buf: 00202340, count: 2, ppos: 0
     
    简单看看simple_write_to_buffer的实现:
     1 /**
     2  * simple_write_to_buffer - copy data from user space to the buffer
     3  * @to: the buffer to write to
     4  * @available: the size of the buffer
     5  * @ppos: the current position in the buffer
     6  * @from: the user space buffer to read from
     7  * @count: the maximum number of bytes to read
     8  *
     9  * The simple_write_to_buffer() function reads up to @count bytes from the user
    10  * space address starting at @from into the buffer @to at offset @ppos.
    11  *
    12  * On success, the number of bytes written is returned and the offset @ppos is
    13  * advanced by this number, or negative value is returned on error.
    14  **/
    15 ssize_t simple_write_to_buffer(void *to, size_t available, loff_t *ppos,
    16         const void __user *from, size_t count)
    17 {
    18     loff_t pos = *ppos;
    19     size_t res;
    20 
    21     if (pos < 0)
    22         return -EINVAL;
    23     if (pos >= available || !count)
    24         return 0;
    25     if (count > available - pos)
    26         count = available - pos;
    27     res = copy_from_user(to + pos, from, count);
    28     if (res == count)
    29         return -EFAULT;
    30     count -= res;
    31     *ppos = pos + count;
    32     return count;
    33 }
    34 EXPORT_SYMBOL(simple_write_to_buffer);

    可以看到,最后返回的是count,如果copy_from_user没都拷贝全,将来write还是会被再次调用。

     

    3、对read进行修改

    我们知道read被返回调用的原因是,read返回的值小于用户期望读取的值,对于这里,就是4KB。而对于cat来说,每次read都期望获取4KB的数据,而且在不考虑出错的情况下,只有read返回0,cat才会终止。
     
    read版本2:
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int ret, wrinten;
     5 
     6     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
     7         user_buf, count, *ppos);
     8 
     9     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    10 
    11     ret = copy_to_user(user_buf, kbuf, wrinten+1);
    12     if (ret != 0) {
    13         printk(KERN_ERR "read error");
    14         return -EIO;
    15     }
    16 
    17     *ppos += wrinten;
    18 
    19     return 0;
    20 }

    验证:
    [root@vexpress mnt]# cat /d/demo
    执行回车后,"Hello"却没有输出,但是驱动的read驱动被调用了一次:
    [  118.837456] user_buf: beeb0be8, count: 4096, ppos: 0
    这是什么原因呢?可以看看cat的核心函数bb_full_fd_action,其中,如果read返回0,并不会将读到的内容输出到标准输出上,所以cat的时候什么都没看到。
     
    既然返回0不行,那么返回count,也就是用户期望的4KB,行不行呢?
    read版本3:
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int ret, wrinten;
     5 
     6     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
     7         user_buf, count, *ppos);
     8 
     9     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    10 
    11     ret = copy_to_user(user_buf, kbuf, wrinten+1);
    12     if (ret != 0) {
    13         printk(KERN_ERR "read error");
    14         return -EIO;
    15     }
    16 
    17     *ppos += wrinten;
    18 
    19     return count;
    20 }

    验证:

    [root@vexpress mnt]# cat /d/demo
    ȸT�/mnt/busybox�u0�$@^ξ���вu����ξl����$@����
    可以看到,输出内容中有一些乱七八糟的东西。再看看kernel log,依然刷屏:
    [  339.079698] user_buf: bece4be8, count: 4096, ppos: 0
    [  339.080124] user_buf: bece4be8, count: 4096, ppos: 5
    [  339.085525] user_buf: bece4be8, count: 4096, ppos: 10
    [  339.085886] user_buf: bece4be8, count: 4096, ppos: 15
    [  339.087018] user_buf: bece4be8, count: 4096, ppos: 20
    [  339.098798] user_buf: bece4be8, count: 4096, ppos: 25
    ... ...
     
    什么原因呢?我们知道,如果驱动的read返回4KB,表示用户读到了4KB的数据,但是实际上用户的buffer中只有前5个字节是从kernel读到的,其他的都是用户的buffer缓冲区中的垃圾数据,由于read返回的一直都是4KB,所以会一直read,直到返回0,所以刷屏了。其实,kernel提供了清除用户buffer的函数:clear_user,这样就不会输出乱码了,但是还是会刷屏,
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int ret, wrinten;
     5 
     6     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
     7         user_buf, count, *ppos);
     8 
     9     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    10 
    11     if (clear_user(user_buf, count)) {
    12         printk(KERN_ERR "clear error
    ");
    13         return -EIO;
    14     }
    15 
    16     ret = copy_to_user(user_buf, kbuf, wrinten+1);
    17     if (ret != 0) {
    18         printk(KERN_ERR "read error
    ");
    19         return -EIO;
    20     }
    21 
    22     *ppos += wrinten;
    23 
    24     return count;
    25 }

    上面的这种改动只是不会输出乱码了,但是还是会刷屏。

     
    read版本4:
    那该怎么办呢?我们试试kernel提供的simple_read_from_buffer看看行不行,这个函数专门完成从kernel空间向user空间拷贝数据:
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int wrinten;
     5 
     6     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
     7         user_buf, count, *ppos);
     8 
     9     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    10 
    11     if (clear_user(user_buf, count)) {
    12         printk(KERN_ERR "clear error
    ");
    13         return -EIO;
    14     }
    15 
    16     return simple_read_from_buffer(user_buf, count, ppos, kbuf, wrinten);
    17 }

    验证:

    [root@vexpress mnt]# cat /d/demo
    Hello
    可以看到,cat没有刷屏,确实输出了我们想要的结果,那kernel log呢?
    [  479.457637] user_buf: bec61be8, count: 4096, ppos: 0
    [  479.458268] user_buf: bec61be8, count: 4096, ppos: 5
    还不错,驱动的write被调用了两次,为什么呢? 我们结合simple_read_from_buffer的实现来看看:
     1 /**
     2  * simple_read_from_buffer - copy data from the buffer to user space
     3  * @to: the user space buffer to read to
     4  * @count: the maximum number of bytes to read
     5  * @ppos: the current position in the buffer
     6  * @from: the buffer to read from
     7  * @available: the size of the buffer
     8  *
     9  * The simple_read_from_buffer() function reads up to @count bytes from the
    10  * buffer @from at offset @ppos into the user space address starting at @to.
    11  *
    12  * On success, the number of bytes read is returned and the offset @ppos is
    13  * advanced by this number, or negative value is returned on error.
    14  **/
    15 ssize_t simple_read_from_buffer(void __user *to, size_t count, loff_t *ppos,
    16                 const void *from, size_t available)
    17 {
    18     loff_t pos = *ppos;
    19     size_t ret;
    20 
    21     if (pos < 0)
    22         return -EINVAL;
    23     if (pos >= available || !count)
    24         return 0;
    25     if (count > available - pos)
    26         count = available - pos;
    27     ret = copy_to_user(to, from + pos, count);
    28     if (ret == count)
    29         return -EFAULT;
    30     count -= ret;
    31     *ppos = pos + count;
    32     return count;
    33 }
    34 EXPORT_SYMBOL(simple_read_from_buffer);

    第一次read是ppos是0,读完毕之后,ppos变成了5。我们知道,cat不甘心,因为没有返回0,所以紧接着又调用了一次read,这次的ppos为5,上面的第23行代码生效了,available是5,所以直接返回了0,然后cat就乖乖的退出了。

     
    read版本5:
    因为我们想实现cat的时候,驱动的read只调用一次,同时还要保证cat能输出读到的内容,我们可以做如下修改:
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int wrinten;
     5 
     6     if (*ppos)
     7         return 0;
     8 
     9     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
    10         user_buf, count, *ppos);
    11 
    12     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    13 
    14     if (clear_user(user_buf, count)) {
    15         printk(KERN_ERR "clear error
    ");
    16         return -EIO;
    17     }
    18 
    19     return simple_read_from_buffer(user_buf, count, ppos, kbuf, wrinten);
    20 }

    在第6行,先判断*ppos的值,我们知道第一次调用驱动read时,*ppos是0,读完毕后,*ppos会被更新,第二次*ppos便不为0.

    验证:
    [root@vexpress mnt]# cat /d/demo
    Hello
    用户空间没有刷屏,达到了我们的目的,kernel的log也只有一行:
    [ 1217.948729] user_buf: beb88be8, count: 4096, ppos: 0
    也就是驱动的read确实被调用了一次。其实我们知道,驱动的read还是被调用了两次,只不多第二次没有什么干什么活,直接就返回了,不会影响我们的驱动逻辑。
     
    也可以不用内核提供的接口:
    read版本6:
     1 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
     2 {
     3     char kbuf[10];
     4     int ret, wrinten;
     5 
     6     if (*ppos)
     7         return 0;
     8 
     9     printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld
    ",
    10         user_buf, count, *ppos);
    11 
    12     wrinten = snprintf(kbuf, 10, "%s", "Hello");
    13 
    14     if (clear_user(user_buf, count)) {
    15         printk(KERN_ERR "clear error
    ");
    16         return -EIO;
    17     }
    18 
    19 
    20     ret = copy_to_user(user_buf, kbuf, wrinten);
    21     if (ret != 0) {
    22         printk(KERN_ERR "copy error
    ");
    23         return -EIO;
    24     }
    25 
    26     *ppos += wrinten;
    27 
    28     return wrinten;
    29 }
    效果跟前一个一样。
     
    完。
  • 相关阅读:
    百度病了,必应挂了,Yandex疯了。
    SpringBoot从零单排 ------ 拦截器的使用
    SpringBoot从零单排 ------初级入门篇
    我为什么放弃MySQL?最终选择了MongoDB
    NSURL组成部分详解
    关于MPMoviePlayerController 缓存播放的一些技术准备
    动画系列收藏
    代码简化
    自动循环滚动ScrollView
    iOS WKWebView 使用笔记
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/8012793.html
Copyright © 2011-2022 走看看