Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2000389
  • 博文数量: 369
  • 博客积分: 10093
  • 博客等级: 上将
  • 技术积分: 4271
  • 用 户 组: 普通用户
  • 注册时间: 2005-03-21 00:59
文章分类

全部博文(369)

文章存档

2013年(1)

2011年(2)

2010年(10)

2009年(16)

2008年(33)

2007年(146)

2006年(160)

2005年(1)

分类: LINUX

2007-05-18 23:23:10

proc文件系统凭借其编程接口的“简单易学”,和很多书籍和参考资料都把它放在较为靠前的位置(很多人看书都不能坚持到底),成为了那些需要快速开发的私有内核程序的“首选”内核空间和用户空间通信接口,被广泛地应用于实际编程中。但是我们中大多都忽视了其“简单”中由于种种细节所造成的“复杂”性,尤其在用它传输大量数据的时候。本文将努力详尽地分析它的细节,给大家一个参考。

首先,可以肯定的是proc文件系统是支持大文件读写的。通常情况下,我们都是先通过函数create_proc_entry()创建一个文件记录,然后将自己实现的读函数read_proc、写函数write_proc和文件相关数据data赋值给刚才新建的文件记录。如下所示[1]:

        entry = create_proc_entry("aint", 0644, myprocroot);
        if (entry) {
                entry->data = &int_var;
                entry->read_proc = &int_read_proc;
                entry->write_proc = &int_write_proc;
        }


其中read_proc和write_proc的原型分别如下:

typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);


其参数分别表示:

page: 因为proc文件系统读操作实现的是:在读的时候首先向系统申请一个内存页,然后将这个内存页的起始地址作为第一个参数(page)调用上面的函数,接着再将由上面的函数写入page里面的内容复制到用户空间的缓冲区,所以需要校验由page和count所标示的地址是否是合理的用户空间地址,实际上校验是无论如何也通过不了的,因为它切切实实地是内核空间的地址。

start: 这个参数有些特殊,LDD3(Linux Device Driver 3rd edition)称其的出现和用法为hack。start的用法也确实有些诡异,读过内核里面的相关注释和源码后先总结如下:
  • 忽视start参数或把start赋值为空(NULL):这时的proc是不支持大于一个内存页大小的数据传输到用户空间的,通常和它一并忽略的还有off和count参数,这个时候内核程序员还需明白page所指的内存空间为一个内存页,应该将全部内容直接写到其中,否则off不为0的read调用可能得到错误的数据或得不到数据。
  • 将start赋值为比page的地址值小的数:这种情况下,off所代表的意义将和start重合,可以表示记录的索引号,而不是确切的字节偏移,甚至可以是程序员自己定义的任意值。
  • 将start赋值为由page所标示的内存页所在的地址空间中的一个地址值:这个时候off表示字节偏移,start表示返回的缓冲区的起始地址,通常直接赋值为page。
在上面三种用法中,第二种曾经比较常用,也比较好用,但是hack的方法所带来的不一致严重影响了人们对它的理解,所以现在的内核鼓励用更好的seq_file API[2]向用户空间输出面向记录的数据。

off: 可以表示多种意图,具体参见上面对参数start的解释。

count: 表示这次调用用户期待的数据量,一般小于页大小。可以被忽略。

eof: 将*eof置为`1'表示已经读到了文件的结尾,换句话说这可能是最后一次返回数据,以后再也没有多余的数据可以返回。如果你忽略了count,那么也请你同时忽略它。

data: proc文件记录中的数据成员data的值。

返回值: 统一表示写到page所标示的缓冲区的字节数。

怎么样,由start所引发的三种工作模式,确实够让人头晕的吧?如果感觉我说的还是不甚明白(我都感觉自己在说绕口令),建议自己去阅读内核的源码,那才是程序员间相互交流的语言。

typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);


write_proc_t就要比read_proc_t简洁许多了,参数意义也简单明了,file就是这个proc文件所对应的文件结构,buffercount标示用户空间的缓冲区(需要谨慎对待),data和read_proc_t中的data同义,都是表示proc文件记录中的数据成员data的值。你可能已经发现它的参数中没有off选项了,不错,它确实不区分对同一个文件连续调用的write,所以为了避免数据截断,你有以下三种选择:
  • 不用这个接口,转用更加底层的file_operation接口中的write[3]。
  • 一次性将所有的数据通过write系统调用写入proc文件。
  • 自己在data所指的自定义结构中保存偏移信息,不过这个时候你还需要保证对data所指数据操作的原子性。
也许你也会想到直接操纵file结构中的off_set属性,不过很遗憾,此路不通。原因请看内核中的下列代码:

        loff_t pos = file_pos_read(file);
        ret = vfs_write(file, buf, count, &pos);
        file_pos_write(file, pos);


我想这就勿需再解释什么了。

看到这里,我想你也该同意我刚才所说的话了吧?回想一下自己以前的代码中是否有上面提到的细节问题,如果有赶紧去改正...

注意

proc文件的get_info()接口本文并未提及,原因是太古老了。

参考资料:

[1] 在 Linux 下用户空间与内核空间数据交换的方式:procfs
[2] 在 Linux 下用户空间与内核空间数据交换的方式:seq_fs
[3] Manage /proc file with standard filesystem
阅读(2303) | 评论(5) | 转发(0) |
0

上一篇:趋于平淡

下一篇:夏眠

给主人留下些什么吧!~~