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文件所对应的文件结构,
buffer和
count标示
用户空间的缓冲区(需要谨慎对待),
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
阅读(2360) | 评论(5) | 转发(0) |