作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技术群:4367710
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
=======================================================/proc作为Linux的一个虚拟文件系统,常常用来输出内核中的数据,或与用户层进行交互。但是,不知道各位同学是否曾过这样的一个问题?
在应用层编程时,当一个文件,既有读取者,又有写入者的时候,必须使用文件锁等手段来保证数据的完整性和一致性,避免读取到一半的数据,或者不一致的数据。
那么对于/proc文件,是否存在着这个问题呢?比如,/proc/stat输出的是cpu当前使用情况。作为实时数据,当用cat显示/proc/stat文件时,如果内核正在写入这个文件,如何保证应用层拿到的是准确的数据呢?
想到这个问题,是不是有点可怕?貌似我们在使用/proc文件的时候,从未想过这样的问题。那么是否是因为应用层不需要可以容忍这样的错误,可以容忍这样的不准确的数据呢?还是在内核层有锁来保证读取和写入的原子性呢?如果是前者,或许有的应用不在乎准确的数据,但绝不会所有的应用不需要准确的数据。如果是后者,虽然/proc作为一个特殊的文件系统,但是若是让内核来保证/proc读取和写入的原子性,这无疑是linux对于文件系统处理的一种特例。这不会是一个良好的设计。
让我们多想一步。文件之所以会出现数据不一致的原因,在于读写同时进行。对于/proc文件,我们很自然的想到,内核会定期把最新内核数据写入到proc文件中,如这时恰逢用户读取该文件,则面临数据不一致的问题。这样的想法,完全是建立在应用编程的思路上。当用户读取文件时,通过系统调用,陷入内核态,然后获得文件数据,将其复制到用户地址空间中。注意其中的关键一步,“陷入内核态”。内核作为/proc文件的创建者和维护者,所有用户的读取和写入操作又都会经过内核。这样的话,proc文件完全可以是一个虚假的“文件”。这个文件中实际上没有任何的数据,也不占用任何的空间。用户所有对该文件的操作,完全由内核一手包办。对于用户来说,由于对于内核的充分信任,也就认为了是真正读取和写入了proc文件。
验证一下这个思路:
- [root@fgao-vm-fc13 fgao]# ls -l /proc/stat
- -r--r--r-- 1 root root 0 Jan 2 22:55 /proc/stat
- [root@fgao-vm-fc13 fgao]# file /proc/stat
- /proc/stat: empty
内核并不真正产生一个proc文件。这样的话,当用户访问这个文件时,内核直接将需要的内部数据复制到指定的用户空间,好像用户读取成功了。这样既保证了,用户读取数据的实时性,又提高了效率——定期写文件,即使是在内存中操作,不涉及真正的I/O,也需要有定时器。
但是即使是内核数据,也有一致性的问题。这就需要看看proc是如何实现的。(这方面的文章很多,我就简单说明了。有兴趣的同学,可以自行搜索)。
用户读取proc文件,内核有两种支持方式:1. 调用struct proc_dir_entry->read_proc;2. seq_file的方式。
先说第一种,read_proc的原型为:
- typedef int (read_proc_t)(char *page, char **start, off_t off,
- int count, int *eof, void *data);
这种实现方式,内核会将内部数据复制到page指向的缓冲区中。正如其名,page的缓冲区大小最多只有一个page,所以当内部数据大小大于一个page时,就需要第二个参数start和off来帮助了。通过偏移off,进行多次调用。内部数据输出完毕时,必须将eof设置为1,表示所有数据都已经输出。这种实现,对于内部数据小于一个page的情况,比较适合。由于可以一次输出,只需要对于内部数据有一个锁来保护,就可以保证数据的一致性。当大于一个page时,就相当麻烦了。
第二种方式,seq_file的方式呢。seq_file提供了一个简单的框架。只需要proc的维护者,实现seq_operations中的start,next,stop和show,就可以完成proc文件的输出。其中show函数为proc的实际输出函数,调用一次next,就会有一个show的输出。为保证数据的原子性,内核一般都是在next函数中保证一个独立数据的原子性输出,比如输出一个链表中的一个节点数据,一个hash桶中的数据,等等。
综上所述,/proc文件若采用第一种方式,一般可以提供整个儿文件的数据一致性;如果采用第二种方式,则可以保证文件中每个独立数据的一致性。当然,这都要建立在内核代码部分实现正确的前提下。不排除某些内核代码,在输出proc数据的实现上,采用非常规的方式,导致数据有可能不一致。毕竟linux内核并不提供这一保证,而是将其交给proc的创建者。