继续学习《unix环境编程》。

第五章 讲标准I/O库。

1 引言

标准I/O库提供一组通用、跨平台的输入/输出函数,确保了代码的可移植性。

(1)缓存管理:标准I/O库管理读写缓冲区,减少了直接系统调用的次数。

当使用fgetc从文件读取单个字符时,可能一次性从磁盘读取多个字符到缓冲区,下次读取时直接从缓冲区获取,减少了昂贵的磁盘I/O操作。

(2)格式化输入输出:提供了格式化读写函数,如printf、scanf,使得数据的输入输出更加灵活和方便。

(3)错误处理:通过全局变量errno报告错误,并提供了函数如perror来打印错误信息。

(4)流抽象:标准I/O库通过流(stream)的概念抽象了底层的文件描述符和系统调用细节。

2 流和FILE对象

   在较低级别的I/O操作中,文件操作基于文件描述符,直接关联到内核的文件表项执行读写。标准I/O库引入了更高层次的抽象“流”,使用FILE结构操作。流是对文件描述符的封装,有更多的功能,如格式化读写、缓冲管理等。当使用fopen函数打开或创建一个文件时,标准I/O库打开一个文件并获取了文件描述符,创建了一个FILE对象,包含了管理流所需的所有信息,比如底层的文件描述符、指向缓冲区的指针、缓冲区的大小、当前缓冲区中字符的数量、错误状态标志等。只需通过fopen返回的FILE*指针(文件指针)与标准I/O函数交互,如fread, fwrite, fscanf, fprintf等进行读写操作。

3 标准输入、标准输出和标准出错


每个进程默认关联三个标准的输入/输出流

(1)标准输入:进程可以通过标准输入流读取数据(键盘或其他输入源)。文件描述符对应于STDIN_FILENO,值为0。

(2)标准输出:输出信息到屏幕(指定输出设备)。在文件描述符层面对应STDOUT_FILENO,值为1。

(3)标准错误:输出错误信息和诊断消息,可以区分正常输出和错误信息,对应文件描述符是STDERR_FILENO,其值为2。

这三个标准流还可以通过预定义的文件指针来引用,这些文件指针定义在<stdio.h>头文件中:

stdin:指向标准输入流的文件指针。
stdout:指向标准输出流的文件指针。
stderr:指向标准错误流的文件指针。

4 缓存


标准I/O库提供三种类型的缓存策略:全缓存、行缓存 、无缓存

(1)全缓存

   在数据积累到一定程度后再执行I/O操作,减少系统调用的频率,适用于磁盘文件等I/O密集型操作(在第四章sync函数中讲过这个策略)。当缓存填满时自动执行I/O,或显式调用fflush函数手动刷新。首次使用时,标准I/O函数会自动分配缓存空间。

适合大量数据的读写,减少系统调用开销,提高效率。

(2)行缓存

换行符执行I/O操作,适用于交互式应用,如终端输入输出。但是缓存大小固定,即使未写入换行符,缓存满也会执行I/O。从无缓存或需要从内核读取数据的行缓存流读取时,会导致所有行缓存输出流刷新。

   如果从一个无缓存的流中读取数据,或者从一个行缓存的流中读取数据,但该流需要先从内核获取数据(即该流中的数据尚未被完全缓存且需要请求更多数据),那么操作系统会触发一个动作:它不仅处理当前正在进行的读取操作,还会立即将所有行缓存的输出流中的数据刷新到它们的目的地。这一过程可能被视为一种“阻塞点”或同步事件,因为它需要等待数据从慢速的存储介质(如磁盘)加载到内存。在此期间,为了确保输出的及时性和完整性,系统会先刷新其他行缓存的输出流,确保所有已准备好的输出信息不会延迟。

(3)  无缓存

 数据立即传递给内核,不经过任何缓冲,适用于实时性要求高的错误信息输出如sterr。使用标准I/O函数写入无缓存流相当于直接调用write系统调用,数据立即发送到目的地。确保数据即时处理,适合对实时性和确定性要求极高的场景,如错误报告和日志记录。

在处理高吞吐量I/O或者实时数据时,理解和控制缓存行为对于优化程序性能很重要。

待......