Linux系统IO中write原型为  ssize_t write(int  filedes, const void * buff, size_t nbytes) ;

当调用write写数据的时候,调用完成后write直接返回,但是磁盘是个慢速设备,操作系统会将数据保存在内核中的缓冲区中,并负责异步地将数据写至磁盘。当然如果此时系统宕机了则会丢失数据。write是系统调用,每次调用都会陷入内核,所以选取一个合适的块长度buffsize,并尽量减少它的调用可以优化效率。在ANSI C的标准IO中我们调用printf/fprintf/fputs等会以流的方式进行处理,我们只需要写入流中,而不用像write一样选择一个buffsize,因为标准IO库帮我们处理了很多细节,例如缓冲区分配,以优化长度执行IO等。这样的话就会减少wirte/read系统调用的数量,提高效率。但是与此同时会引入另外一个问题:数据拷贝,例如当使用函数fgets和fputs时,通常需要经过两次缓冲区:一次是标准IO缓冲区,还有一次是调用read和write的内核缓冲区。但是总的来说使用标准IO相对于系统IO来说接口简单,且效率相当。

标准IO提供了三种类型的缓冲区:全缓存,行缓存和不带缓存,全缓存只有在缓冲区满时才会主动flush,通常用在对一个磁盘文件IO。行缓存在缓冲区中遇到换行符就会flush,还有一种情况是需要从标准输入输出得到输入数据时也会flush缓冲区,行缓存一般用在交互的终端中。不带缓存则相当于直接 write系统调用输出,标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来。除了默认的flush条件外,显式调用fflush函数和程序正常终止时也会flush缓冲区。我们可以使用setbuf/setvbuf来更改默认的缓冲区长度,参见APUE 5.4节。

在使用标准IO的程序中,当我们将一个标准输出重新定向到一个文件时,会将行缓存变为全缓存,在某些情况下可能会导致一些非预期错误,比如调用printf(“*****\n”)时,当以交互方式运行该程序时,会正常输出。但是当将标准输出重新定向到一个文件时,缓冲区区变为全缓存,printf就不会正常输出,该行数据仍在缓冲区中。如果此时再fork一个子进程,数据空间被复制到子进程中时,该缓冲区数据也被复制到子进程中。接着在子进程中如果输出则会刷新之前在缓冲区的内容,产生一些非预期的输出。

在网络编程中,应该直接使用系统IO,标准IO为提升性能而引入缓冲机制增加了网络应用程序的复杂性。并且,某种意义上说标准IO流是全双工的,能同时执行输入和输出,然而对流的限制和对套接字的限制,有时候会互相冲突。(参见CSAPP P611)

网友评论