分类: LINUX
2011-12-27 10:03:35
++++++APUE读书笔记-03文件输入输出(3)++++++
8、I/O的效率
================================================
对于如下代码片断:
#include "apue.h"
#define BUFFSIZE 4096
int main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
这个代码的解释详细参见参考资料。对于这里的read和write调用,其BUFFSIZE表示每次调用的时候,尝试读、写的字节数目。这个BUFFSIZE取不同的值,会导致read和write调用次数的不同,一般来说,BUFFSIZE取值越大,则调用次数越少,调用次数越少,则消耗的系统时间越小。但是当BUFFSIZE大到一定程度的时候,就不会对I/O效率有更多的改善了。下面给出的一个表格,对比了各种BUFFSIZE对I/O效率的影响。
通过不同的缓存大小进行read所消耗的时间
+-------------------------------------------------------------------------------------------+
| BUFFSIZE | User CPU (seconds) | System CPU (seconds) | Clock time (seconds) | #loops |
|----------+--------------------+----------------------+----------------------+-------------|
| 1 | 124.89 | 161.65 | 288.64 | 103,316,352 |
|----------+--------------------+----------------------+----------------------+-------------|
| 2 | 63.10 | 80.96 | 145.81 | 51,658,#176 |
|----------+--------------------+----------------------+----------------------+-------------|
| 4 | 31.84 | 40.00 | 72.75 | 25,829,088 |
|----------+--------------------+----------------------+----------------------+-------------|
| 8 | 15.17 | 21.01 | 36.85 | 12,914,544 |
|----------+--------------------+----------------------+----------------------+-------------|
| 16 | 7.86 | 10.27 | 18.76 | 6,457,272 |
|----------+--------------------+----------------------+----------------------+-------------|
| 32 | 4.13 | 5.01 | 9.76 | 3,228,636 |
|----------+--------------------+----------------------+----------------------+-------------|
| 64 | 2.11 | 2.48 | 6.76 | 1,614,318 |
|----------+--------------------+----------------------+----------------------+-------------|
| 128 | 1.01 | 1.27 | 6.82 | 807,159 |
|----------+--------------------+----------------------+----------------------+-------------|
| 256 | 0.56 | 0.62 | 6.80 | 403,579 |
|----------+--------------------+----------------------+----------------------+-------------|
| 512 | 0.27 | 0.41 | 7.03 | 201,789 |
|----------+--------------------+----------------------+----------------------+-------------|
| 1,024 | 0.17 | 0.23 | 7.84 | 100,894 |
|----------+--------------------+----------------------+----------------------+-------------|
| 2,048 | 0.05 | 0.19 | 6.82 | 50,447 |
|----------+--------------------+----------------------+----------------------+-------------|
| 4,096 | 0.03 | 0.16 | 6.86 | 25,223 |
|----------+--------------------+----------------------+----------------------+-------------|
| 8,192 | 0.01 | 0.18 | 6.67 | 12,611 |
|----------+--------------------+----------------------+----------------------+-------------|
| 16,384 | 0.02 | 0.18 | 6.87 | 6,305 |
|----------+--------------------+----------------------+----------------------+-------------|
| 32,768 | 0.00 | 0.16 | 6.70 | 3,152 |
|----------+--------------------+----------------------+----------------------+-------------|
| 65,536 | 0.02 | 0.19 | 6.92 | 1,576 |
|----------+--------------------+----------------------+----------------------+-------------|
| 131,072 | 0.00 | 0.16 | 6.84 | 788 |
|----------+--------------------+----------------------+----------------------+-------------|
| 262,144 | 0.01 | 0.25 | 7.30 | 394 |
|----------+--------------------+----------------------+----------------------+-------------|
| 524,288 | 0.00 | 0.22 | 7.35 | 198 |
+-------------------------------------------------------------------------------------------+
(我们可以这样理解,使用read或者write等系统调用直接对文件操作,如果我们自己知道那个临界的BUFFERSIZE,那么就能够找到最优效率的参数来调用它;而不使用系统调用且使用标准库函数进行读写的话,标准库函数中自动设置了一个比较通用的BUFFERSIZE,不用要求我们自己设置大小了,这样可以得到较优的读写效率,这也是库函数和系统调用的一个不同)。
总之,直接使用系统调用的read和write进行文件输入输出操作,没有自动指定缓存大小(需要手动设置每次读取的大小);而使用库函数的话就有缓存了,缓存的大小设置为和磁盘块大小一样最省时间。也就是说,一次read的数据如果是在磁盘块大小之内的话,时间是差不多的,所以最好把缓存设置为和磁盘块一样大小。
另外,一般读写文件的时候,操作系统会自动尝试把文件缓存到内核中,这样下次操作同样文件的时候会比较快一些,所以测试文件操作时间的时候使用不同的文件会比较准确。这也是coredump的来源。使用sync可以将缓存的数据刷新到磁盘上面。有许多类型的sync,有的只刷新文件数据,有的连文件属性也刷新了。
参考:
9、文件共享
================================================
内核用于维护进程打开的文件的相关数据结构大致有三个:
a)每个进程的进程表中有一个包含所有打开的文件描述符号信息的表,这个表中每一项主要包含:一个文件描述符号的标记以及一个文件符号描述表指针。
b)文件符号描述表,由前面的进程表中指针来指向它,主要包含:一个状态标记,一个偏移,以及一个文件虚拟节点地址。如果多个进程打开同样文件,也会导致有多个这样的表,但是虚拟节点只有一个,这样是保证每个进程有自己正确的文件读取偏移量。
c)文件虚拟节点:描述文件大小,虚拟节点信息和索引信息等。用于在计算机上支持不同的文件系统。对于虚拟节点和索引,v-node用来在一个计算机系统上面支持多个类型的文件系统。Sun将它称作虚拟文件系统,并且将i-node中和文件系统相独立的部分叫做v-node。随着对Sun的网络文件系统(NFS)的支持的加入,v-node在不同的厂商实现中流行起来。具体的发展情况,请参考原书参考资料,Linux并不是讲数据分成了i-node和v-node,它是用一个和文件系统无关的i-node以及和一个文件系统相关的i-node。尽管实现有所不同,其概念和v节点类似,在第4章14节中我们会讨论i-node。
注意,上面的文件描述符号标记和状态标记是不一样的。文件描述符号标记例如close-on-exec,意思是使用exec的时候是否关闭之前的文件符号;状态标记有例如:读写属性,追加,同步写属性,阻塞等;
下图描述一个打开了两个文件的进程的这些数据结构的情况。
打开两个文件的进程的相关数据结构关系
v-node table
process table entry file table -->+------------------+
+------------------+ --------->+-------------------+ / | v-node |
| fd file | / |file status flags | / | information |
| flags pointer|/ +-------------------| / +------------------+
| +----+-----+ / |current file offset| / | i-node |
| fd0 | | |/| +-------------------+ / | information |
| +----+-----+ | | v-node pointer |/ + - - - - - - - - -+
| fd1 | | |----------\ +-------------------+ | current file size|
| +----+-----+ | \ +------------------+
| fd2 | | | | ->+-------------------+
| +----+-----+ | |file status flags | ---->+------------------+
| | | | +-------------------| / | v-node |
| | ...... | | |current file offset| / | information |
| | | | +-------------------+ / +------------------+
| +----------+ | | v-node pointer |/ | i-node |
| | +-------------------+ | information |
+------------------+ + - - - - - - - - -+
| current file size|
+------------------+
上图中,一个文件在标准输入上(文件描述符号为0)打开,另外一个在标准输出上(文件描述符号为1)打开。
下图给出了两个不同进程打开同一个文件的情况。
两个独立进程打开同一个文件时内核的相关数据结构
process table entry
+------------------+
| fd file |
| flags pointer|
| +----+-----+ |
| fd0 | | | |
| +----+-----+ |
| fd..| ...... | |
| +----+-----+ | file table
| fd3 | | |------------->+-------------------+ v-node table
| +----+-----+ | |file status flags | ---->+------------------+
| | | | +-------------------| / +->| v-node |
| | ...... | | |current file offset| / | | information |
| | | | +-------------------+ / | +------------------+
| +----------+ | | v-node pointer |/ | | i-node |
| | +-------------------+ | | information |
+------------------+ | + - - - - - - - - -+
| | current file size|
| +------------------+
process table entry ---->+-------------------+ /
+------------------+ / |file status flags | /
| fd file | / +-------------------| /
| flags pointer| / |current file offset| /
| +----+-----+ | / +-------------------+ /
| fd0 | | | | / | v-node pointer |/
| +----+-----+ | / +-------------------+
| fd..| ...... | |/
| +----+-----+ /
| fd4 | | |/|
| +----+-----+ |
| | | |
| | ...... | |
| | | |
| +----------+ |
| |
+------------------+
上图中,第一个进程的文件描述符号3和第二个进程的文件描述符号4表示同一个文件。可知,每个进程有它自己的文件表,但是每个文件只有一个v-node表。每个进程有一个文件表的原因是这样可以让每个进程有它自己的文件当前偏移。
如果打开多个文件想要使用一个文件符号描述表,那么用dup.使用fork好像就是达到这个效果的(见第8章3节)。如果一个进程打开多个文件呢?
使用dup的时候,会使得进程中多个文件描述符号信息的表项指向一个文件符号描述表项。使用fcntl一样,不过fcntl有可能不是原子的,之前需要别的操作才行,而dup只需要一个函数,是原子的,后面会提到原子操作。
参考: