span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }.cm-searching {background: #ffa; background: rgba(255, 255, 0, .4);}.cm-force-border { padding-right: .1px; }@media print { .CodeMirror div.CodeMirror-cursors {visibility: hidden;}}.cm-tab-wrap-hack:after { content: ""; }span.CodeMirror-selectedtext { background: none; }.CodeMirror-activeline-background, .CodeMirror-selected {transition: visibility 0ms 100ms;}.CodeMirror-blur .CodeMirror-activeline-background, .CodeMirror-blur .CodeMirror-selected {visibility:hidden;}.CodeMirror-blur .CodeMirror-matchingbracket {color:inherit !important;outline:none !important;text-decoration:none !important;}
-->
li {list-style-type:decimal;}ol.wiz-list-level2 > li {list-style-type:lower-latin;}ol.wiz-list-level3 > li {list-style-type:lower-roman;}blockquote {padding:0 12px;padding:0 0.75rem;}blockquote > :first-child {margin-top:0;}blockquote > :last-child {margin-bottom:0;}img {border:0;max-width:100%;height:auto !important;margin:2px 0;}table {border-collapse:collapse;border:1px solid #bbbbbb;}td, th {padding:4px 8px;border-collapse:collapse;border:1px solid #bbbbbb;min-height:28px;word-break:break-all;box-sizing: border-box;}.wiz-hide {display:none !important;}
-->

作者

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

这里需要关注:

第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
 static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size)
{
int status = -;
off_t total = ;
bool continue_on_write_error = ;
ssize_t sendfile_sz;
char buffer[ * ]; // 用户空间缓冲区,4KB大小
enum { buffer_size = sizeof(buffer) }; // 每次read期望获得的字节数 sendfile_sz = ;
if (!size) {
size = ( * *); // 刚开始,如传入的size是0,这里将size设置为16MB
status = ; /* 表示一直读到文件结尾,也就是直到read返回0 */
} while () {
ssize_t rd; rd = safe_read(src_fd, buffer, buffer_size); // 这里调用的就是read, 读取4KB,rd是实际读到的字节数
if (rd < ) {
bb_perror_msg(bb_msg_read_error);
break;
}
read_ok:
if (!rd) { /* 表示读到了文件结尾,那么结束循环 */
status = ;
break;
}
/* 将读到的内容输出到dst_fd表示的文件描述符 */
if (dst_fd >= && !sendfile_sz) {
ssize_t wr = full_write(dst_fd, buffer, rd);
if (wr < rd) {
if (!continue_on_write_error) {
bb_perror_msg(bb_msg_write_error);
break;
}
dst_fd = -;
}
} total += rd; // total记录的是读到的字节数的累计值
if (status < ) { /* 如果传入的size不为0,那么status为-1,直到读到size个字节后,才会退出。如果size为0,这个条件不会满足 */
size -= rd;
if (!size) {
/* 'size' bytes copied - all done */
status = ;
break;
}
}
}
out:
return status ? - : total; // 当读完毕,status为0,这里返回累计读到的字节数
}

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

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

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

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

四、实例分析

1、先看两个刷屏的例子

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

#include <linux/init.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static struct dentry *demo_dir;

static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[10];
int ret, wrinten;

printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos);

wrinten = snprintf(kbuf, 10, "%s", "Hello");

ret = copy_to_user(user_buf, kbuf, wrinten+1);
if (ret != 0) {
printk(KERN_ERR "read error");
return -EIO;
}

*ppos += wrinten;

return wrinten;
}

static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[10] = {0};
int ret;

printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos);

ret = copy_from_user(kbuf, user_buf, count);
if (ret) {
pr_err("%s: write error\n", __func__);
return -EIO;
}

*ppos += count;

return 0;
}

static const struct file_operations demo_fops = {
.read = demo_read,
.write = demo_write,
};

static int __init debugfs_demo_init(void)
{
int ret = 0;

demo_dir = debugfs_create_file("demo", 0444, NULL,
NULL, &demo_fops);

return ret;
}

static void __exit debugfs_demo_exit(void)
{
if (demo_dir)
debugfs_remove(demo_dir);
}

module_init(debugfs_demo_init);
module_exit(debugfs_demo_exit);
MODULE_LICENSE("GPL");

 #include <linux/init.h>
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <asm/uaccess.h> static struct dentry *demo_dir; static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[];
int ret, wrinten; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); wrinten = snprintf(kbuf, , "%s", "Hello"); ret = copy_to_user(user_buf, kbuf, wrinten+);
if (ret != ) {
printk(KERN_ERR "read error");
return -EIO;
} *ppos += wrinten; return wrinten;
} static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[] = {};
int ret; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); ret = copy_from_user(kbuf, user_buf, count);
if (ret) {
pr_err("%s: write error\n", __func__);
return -EIO;
} *ppos += count; return ;
} static const struct file_operations demo_fops = {
.read = demo_read,
.write = demo_write,
}; static int __init debugfs_demo_init(void)
{
int ret = ; demo_dir = debugfs_create_file("demo", , NULL,
NULL, &demo_fops); return ret;
} static void __exit debugfs_demo_exit(void)
{
if (demo_dir)
debugfs_remove(demo_dir);
} module_init(debugfs_demo_init);
module_exit(debugfs_demo_exit);
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不就可以了吗。
 static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[] = {};
int ret; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); ret = copy_from_user(kbuf, user_buf, count);
if (ret) {
pr_err("%s: write error\n", __func__);
return -EIO;
} *ppos += count; return count;
}

验证:

[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空间拷贝数据:
 static ssize_t demo_write (struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[] = {}; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); return simple_write_to_buffer(kbuf, sizeof(kbuf), ppos, user_buf, count);
}
验证:
[root@vexpress mnt]# echo 1 > /d/demo
敲完回车后,立马就返回了,kernel log也只打印了一次:

[ 2739.984844] user_buf: 00202340, count: 2, ppos: 0

 
简单看看simple_write_to_buffer的实现:
 /**
* simple_write_to_buffer - copy data from user space to the buffer
* @to: the buffer to write to
* @available: the size of the buffer
* @ppos: the current position in the buffer
* @from: the user space buffer to read from
* @count: the maximum number of bytes to read
*
* The simple_write_to_buffer() function reads up to @count bytes from the user
* space address starting at @from into the buffer @to at offset @ppos.
*
* On success, the number of bytes written is returned and the offset @ppos is
* advanced by this number, or negative value is returned on error.
**/
ssize_t simple_write_to_buffer(void *to, size_t available, loff_t *ppos,
const void __user *from, size_t count)
{
loff_t pos = *ppos;
size_t res; if (pos < )
return -EINVAL;
if (pos >= available || !count)
return ;
if (count > available - pos)
count = available - pos;
res = copy_from_user(to + pos, from, count);
if (res == count)
return -EFAULT;
count -= res;
*ppos = pos + count;
return count;
}
EXPORT_SYMBOL(simple_write_to_buffer);

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

 

3、对read进行修改

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

static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)

{
char kbuf[10];
int ret, wrinten;

printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos);

wrinten = snprintf(kbuf, 10, "%s", "Hello");

ret = copy_to_user(user_buf, kbuf, wrinten+1);
if (ret != 0) {
printk(KERN_ERR "read error");
return -EIO;
}

*ppos += wrinten;

return验证:

[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:
 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[];
int ret, wrinten; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); wrinten = snprintf(kbuf, , "%s", "Hello"); ret = copy_to_user(user_buf, kbuf, wrinten+);
if (ret != ) {
printk(KERN_ERR "read error");
return -EIO;
} *ppos += wrinten; return count;
}

验证:

[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,这样就不会输出乱码了,但是还是会刷屏,
 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[];
int ret, wrinten; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); wrinten = snprintf(kbuf, , "%s", "Hello"); if (clear_user(user_buf, count)) {
printk(KERN_ERR "clear error\n");
return -EIO;
} ret = copy_to_user(user_buf, kbuf, wrinten+);
if (ret != ) {
printk(KERN_ERR "read error\n");
return -EIO;
} *ppos += wrinten; return count;
}

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

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

验证:

[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的实现来看看:
 /**
* simple_read_from_buffer - copy data from the buffer to user space
* @to: the user space buffer to read to
* @count: the maximum number of bytes to read
* @ppos: the current position in the buffer
* @from: the buffer to read from
* @available: the size of the buffer
*
* The simple_read_from_buffer() function reads up to @count bytes from the
* buffer @from at offset @ppos into the user space address starting at @to.
*
* On success, the number of bytes read is returned and the offset @ppos is
* advanced by this number, or negative value is returned on error.
**/
ssize_t simple_read_from_buffer(void __user *to, size_t count, loff_t *ppos,
const void *from, size_t available)
{
loff_t pos = *ppos;
size_t ret; if (pos < )
return -EINVAL;
if (pos >= available || !count)
return ;
if (count > available - pos)
count = available - pos;
ret = copy_to_user(to, from + pos, count);
if (ret == count)
return -EFAULT;
count -= ret;
*ppos = pos + count;
return count;
}
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能输出读到的内容,我们可以做如下修改:
 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[];
int wrinten; if (*ppos)
return ; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); wrinten = snprintf(kbuf, , "%s", "Hello"); if (clear_user(user_buf, count)) {
printk(KERN_ERR "clear error\n");
return -EIO;
} return simple_read_from_buffer(user_buf, count, ppos, kbuf, wrinten);
}

在第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:
 static ssize_t demo_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
char kbuf[];
int ret, wrinten; if (*ppos)
return ; printk(KERN_INFO "user_buf: %p, count: %d, ppos: %lld\n",
user_buf, count, *ppos); wrinten = snprintf(kbuf, , "%s", "Hello"); if (clear_user(user_buf, count)) {
printk(KERN_ERR "clear error\n");
return -EIO;
} ret = copy_to_user(user_buf, kbuf, wrinten);
if (ret != ) {
printk(KERN_ERR "copy error\n");
return -EIO;
} *ppos += wrinten; return wrinten;
}
效果跟前一个一样。
 
完。

使用cat读取和echo写内核文件节点的一些问题的更多相关文章

  1. 【转】忙里偷闲写的小例子---读取android根目录下的文件或文件夹

    原文网址:http://www.cnblogs.com/wenjiang/p/3140055.html 最近几天真的是各种意义上的忙,忙着考试,还要忙着课程设计,手上又有外包的项目,另一边学校的项目还 ...

  2. 忙里偷闲写的小例子---读取android根目录下的文件或文件夹

    最近几天真的是各种意义上的忙,忙着考试,还要忙着课程设计,手上又有外包的项目,另一边学校的项目还要搞,自己的东西还在文档阶段,真的是让人想死啊!! 近半个月来,C#这方面的编码比较多,android和 ...

  3. 如何用nfs命令烧写内核和文件系统(网络下载文件到nandflash)(未完)

    使用tftp下载烧写 a.设uboot里的ip地址 set ipaddr 192.168.1.17(uboot的ip设置成同网段) set serverip 192.168.1.5(电脑本机作为服务i ...

  4. linux通用GPIO驱动,写GPIO文件不立即生效问题解决

    Linux开发平台实现了通用GPIO的驱动,用户通过,SHell或者系统调用能控制GPIO的输出和读取其输入值.其属性文件均在/sys/class/gpio/目录下,该目录下有export和unexp ...

  5. Linux cat命令详解(连接文件并打印到标准输出设备上)

    cat:连接文件并打印到标准输出设备上 一.命令格式: cat [-AbeEnstTuv] [--help] [--version] filename 二.参数说明: -n 或 --number:由 ...

  6. 荣品RP4412开发板烧写内核cannot load出错的原因

    问:荣品RP4412开发板烧写必须要配置Xmanager吗? 现在我烧写内核出现这个错误是什么原因呢? 答:4412文件夹下没有zImage这个文件, 你打开4412这个文件夹. 你都拼写错了, zI ...

  7. PLSQL_PLSQL读和写XML文件方式(案例)

    2012-05-01 Created By BaoXinjian

  8. bootstrap 中是通过写less文件来生成css文件,用什么工具来编写呢?

    bootstrap 中是通过写less文件来生成css文件,用什么工具来编写呢? 如果用sublime的话如何实现代码保存后浏览器刷新成最新的代码样式? 或者有什么其他好用的工具? 从网上找了很多方法 ...

  9. Python写UTF8文件,UE、记事本打开依然乱码的问题

    Python写UTF8文件,UE.记事本打开依然乱码的问题 Leave a reply 现象:使用codecs打开文件,写入UTF-8文本,正常无错误.用vim打开正常,但记事本.UE等打开乱码. 原 ...

随机推荐

  1. GaN助力运营商和基站OEM实现5G sub-6GHz和mmWave大规模MIMO

    到2021年,估计全球会有更多的人拥有移动电话(55亿),将超过用上自来水的人数(53亿).与此同时,带宽紧张的视频应用将进一步增加对移动网络的需求,其会占移动流量的78%.使用大规模多输入多输出(M ...

  2. OpenLayers 3 之 地图图层数据来源(ol.source)详解

    原文地址 source 是 Layer 的重要组成部分,表示图层的来源,也就是服务地址.除了在构造函数中制定外,可以使用 layer.setSource(source) 稍后指定.一.包含的类型 ol ...

  3. Android: 详解触摸事件如何传递

    当视图的层次结构比较复杂的时候,触摸事件的响应流程也变得复杂. 举例来说,你也许有一天想要制作一个手势极其复杂的 Activity 来折磨你的用户,你经过简单思索,认为其中应该包含一个 PageVie ...

  4. django-xadmin后台开发

    先通过pip命令行安装django<=1.9版本 示例:pip install django==1.9 从https://github.com/sshwsfc/xadmin下载xadmin源码解 ...

  5. maven dependencies 报错

    maven配置的环境变量有问题: 用最新的maven替换系统默认的setting.xml文件即可

  6. 补充NTP知识的初中高

    前言 网上流传阿里穆工对NTP知识梳理的初级和中级版本.我从时钟服务器厂商在实践中的经验对穆工的文档进行再次整理和补充,希望对使用此设备的客户和对此有兴趣的同学给出一些指引. 个人认为对知识的了解应该 ...

  7. hdu 1754 线段树(单点替换 区间最值)

    Sample Input5 61 2 3 4 5Q 1 5 //1-5结点的最大值U 3 6 //将点3的数值换成6Q 3 4Q 4 5U 2 9Q 1 5 Sample Output5659 # i ...

  8. oracle的sql语句大小写

    我相信大家都知道,oracle数据库是区分大小写的,而且oracle的默认为大写的,也就是说你在sql脚本上面写的sql语句,oracle运行的时候,它会自动转化为大写的.注意一下,我这里举例子的计算 ...

  9. 【LOJ】#2532. 「CQOI2018」社交网络

    题解 基尔霍夫矩阵,外向树是入度矩阵-邻接矩阵 必须删掉第一行第一列然后再求行列式 代码 #include <bits/stdc++.h> #define fi first #define ...

  10. 100BASE-TX / 100BASE-T4/100BASE-FX

    IEEE标准共有以下几种:10BASE-5:粗缆.最大传输距离500米,使用AUI连接器连接或使用收发器电缆和收发器(MAU)进行连接.10BASE-2:细缆.实际传输距离为185米,使用BNC连接器 ...