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. MongoDB aggregate 运用篇(转)

    http://www.cnblogs.com/qq78292959/p/4440679.html 最近一直在用mongodb,有时候会需要用到统计,在网上查了一些资料,最适合用的就是用aggregat ...

  2. Android工程方法数超过65535的解决办法

    Error:Execution failed for task ':ttt:transformClassesWithDexForDebug'.com.android.build.api.transfo ...

  3. Java 把异常传递给控制台

    最简答而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台,对于简单的程序可以像这样: package exceptions; //: exceptions/MainExce ...

  4. nginx log 错误502 upstream sent too big header while reading response header from upstream

    cookies的值超出了范围我是说 看看了一下日志 错误502 upstream sent too big header while reading response header from upst ...

  5. net mvc cms

    .NET作品集:linux下的.net mvc cms   cms程序架构 本程序是主要是用于企业网站开发的,也可以做博客程序,程序是从之前上一篇的.net 博客程序改进过来的,主要技术由webfor ...

  6. 【洛谷】P4198 楼房重建

    题解 我们转而维护每个点的斜率,显然一个楼房能被看见它就是一个前缀最大值,斜率比较为了节约精度可以用向量替代 我们每个区间维护被看到的楼房的个数,和楼房的最大值,叶子节点在有楼房时,值为1 那么考虑合 ...

  7. Windows下PHP多线程扩展pthreads的安装

    pthreads扩展安装步骤 1.查看phpinfo() 获取PHP版本号及位数(x86表示32位,x64表示64位).编译器版本.PHP配置文件加载所在位置等.如下图所示: 2.pthreads扩展 ...

  8. max 基础知识

    MDX基本语法 MD询语句的结构及语法 MDX查询示例 基本的MDX SELECT语句包含一SELELCT字句和一个FROM字句,以及一个可选的WHERE子句.如下 SELECT {[Measures ...

  9. win10下sublime text3 使用view in browser的快捷鍵添加方式

    key-binding-user,于是,我在那里输入: [  { "keys": ["ctrl+b"], "command": " ...

  10. ViewPager 无限循环

    Overview 我们在使用ViewPager来制作图片轮播的时候,常常为ViewPager不能一直无限循环的问题所苦恼.对于这个问题,目前从网上找到了两个思路来解决: 将 ViewPager 的Co ...