【Linux】基础IO(3)
1. 缓冲区
1.1 什么是缓冲区
缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
1.2 为什么要引入缓冲区机制
读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调⽤对磁盘进行操作(读、写等),那么每次对文件进行⼀次读写操作时,都需要使⽤读写系统调用来处理此操作,即需要执行⼀次系统调⽤,执⾏⼀次系统调⽤将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗⼀定的CPU时间,频繁的磁盘访问对程序的执⾏效率造成很⼤的影响。
为了减少使⽤系统调⽤的次数,提⾼效率,我们就可以采⽤缓冲机制。比如我们从磁盘⾥取信息,可以在磁盘⽂件进行操作时,可以⼀次从⽂件中读出⼤量的数据到缓冲区中,以后对这部分的访问就不需要再使⽤系统调⽤了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作⼤ 快于对磁盘的操作,故应⽤缓冲区可⼤ 提⾼计算机的运⾏速度。
又⽐如,我们使⽤打印机打印⽂档,由于打印机的打印速度相对较慢,我们先把⽂档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是⼀块内存区,它⽤在输⼊输出设备和CPU之间,⽤来缓存数据。它使得低速的输⼊输出设备和高速的CPU能够协调⼯作,避免低速的输⼊输出设备占⽤CPU,解放出CPU,使其能够⾼工作效率。
1.3 缓冲类型
标准I/O提供了3种类型的缓冲区。
全缓冲区:这种缓冲方式要求填满整个缓冲区后才进⾏I/O系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。行缓冲区:是标准 I/O 库针对终端相关流(如标准输入、标准输出)采用的缓冲方式。其工作机制为:当输入或输出过程中遇到换行符
\n
时,标准 I/O 库会执行系统调用,将缓冲区数据实际传输;同时,由于行缓冲区有固定默认大小(1024 字节),若数据填满缓冲区,即使未遇到换行符,也会触发系统调用完成数据传输。⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进行缓存,直接调用系统调用。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。
除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:
1. 缓冲区满时;2. 执⾏flush语句;3. 进程结束
例子:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <unistd.h>int main()
{// 库函数printf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char *s = "hello fwrite\n";fwrite(s, strlen(s), 1, stdout);// 系统调用const char *ss = "hello write\n";write(1, ss, strlen(ss));//???fork();return 0;
}
场景 1:直接终端运行(./newcode
)
输出:
hello printf
hello fprintf
hello fwrite
hello write
printf
/fprintf
/fwrite
:因输出到终端,stdout
是行缓冲,且内容带\n
,所以立即刷新缓冲区,直接输出。write
:系统调用,无缓冲,直接输出。fork
:此时缓冲区已被 “行缓冲 +\n
” 刷空,子进程复制后无残留数据,退出时不额外输出。
场景 2:重定向到文件(./newcode > log.txt
)
输出(cat log.txt
结果):
hello write
hello printf
hello fprintf
hello fwrite
hello printf
hello fprintf
hello fwrite
原因:
- 重定向后缓冲变化:
stdout
重定向到文件,变为全缓冲(需进程退出才刷新)。 fork
前的输出操作:printf
/fprintf
/fwrite
:内容进入用户空间全缓冲(未刷新,因为缓冲区没满、没fflush
);write
:系统调用,无缓冲,直接写入文件,所以最先输出。
fork
与子进程复制:- 父进程执行
fork
,子进程复制父进程的全缓冲缓冲区(此时printf
/fprintf
/fwrite
的内容还在缓冲区里)。
- 父进程执行
- 进程退出刷新:
- 父进程退出时,刷新自己的缓冲区 → 输出
hello printf
/hello fprintf
/hello fwrite
; - 子进程退出时,也会刷新复制来的缓冲区 → 再次输出
hello printf
/hello fprintf
/hello fwrite
。
- 父进程退出时,刷新自己的缓冲区 → 输出
综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这
⾥所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调⽤,库函数在系统调用
的“上层”, 是对系统调⽤的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,⾜以
说明,该缓冲区是⼆次加上的,⼜因为是C,所以由C标准库提供