使用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\n",
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\n",
36            user_buf, count, *ppos);
37 
38     ret = copy_from_user(kbuf, user_buf, count);
39     if (ret) {
40         pr_err("%s: write error\n", __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\n",
 7           user_buf, count, *ppos);
 8 
 9     ret = copy_from_user(kbuf, user_buf, count);
10     if (ret) {
11         pr_err("%s: write error\n", __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\n",
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\n",
 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\n",
 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\n",
 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\n");
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\n");
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\n",
 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\n");
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\n",
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\n");
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\n",
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\n");
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\n");
23         return -EIO;
24     }
25 
26     *ppos += wrinten;
27 
28     return wrinten;
29 }
效果跟前一個一樣。
 
完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM