背景
在理解 Linux 文件描述符及系统调用接口后,我们需要进一步关注实际 I/O 执行细节。用户空间与内核空间的频繁切换、高速 CPU 与低速外设(磁盘、网络卡等)的速度鸿沟、以及'单次小数据 I/O'带来的硬件调度低效,都会成为制约资源访问性能的关键瓶颈。为了解决这些矛盾,Linux 引入了**缓冲区(Buffer/Cache)**机制 —— 它既是衔接'用户进程'与'底层文件/设备'的核心中间层,也是'一切皆文件'哲学在性能层面的重要延伸。
内核与用户缓冲区
一个进程要打开文件必须要通过 OS 进行打开,操作系统为了管理所打开的文件,都会为这个文件创建一个 file 结构体,内部加载文件的属性和内容。属性加载到文件属性,内容加载到文件缓冲区中。这里的缓冲区指的是文件内核缓冲区。
在 C 语言层面,exit 属于库函数,终止进程时会主动刷新缓冲区。这里的缓冲区指的是 C 语言库提供的缓冲区。
什么是缓冲区
**缓冲区是内存空间的一部分。**也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
简单来说,缓冲区的本质就是一段内存空间。
为什么要引入缓冲区
读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行操作(读、写等),那么每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作,即需要执行一次系统调用。执行一次系统调用将涉及到 CPU 状态的切换,即从用户空间切换到内核空间,实现进程上下文切换,这将损耗一定的 CPU 时间。频繁的磁盘访问对程序的执行效率造成很大的影响。
为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数。再加上计算机对缓冲区的操作快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的 CPU 可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和 CPU 之间,用来缓存数据。它使得低速的输入输出设备和高速的 CPU 能够协调工作,避免低速的输入输出设备占用 CPU,解放出 CPU,使其能够高效率工作。
缓冲类型
标准 I/O 提供了 3 种类型的缓冲区:
- 全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行 I/O 系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。
- 行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准 I/O 库函数将会执行系统调用操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准 I/O 库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行 I/O 系统调用操作,默认行缓冲区的大小为 1024。
- 无缓冲区:无缓冲区是指标准 I/O 库不对字符进行缓存,直接调用系统调用。标准出错流 stderr 通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。
除了上述列举的默认刷新方式,下列特殊情况也会引发缓冲区的刷新:缓冲区满时;执行 fflush 语句;
验证 stderr 是否带缓冲区:
int main() {
close(2);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
perror("hello world");
close(fd);
return ;
}


