Linux系统之----文件及缓冲区
1.理解Linux下一切皆是文件
1.1 基本含义
在 Linux 系统中,“一切皆是文件”是一种重要的设计理念。文件不仅是传统意义上的磁盘上的文件,还包括设备、管道、套接字等。不仅如此,像键盘、显示器、网卡、鼠标、磁盘等硬件设备都被当作文件来处理。
1.2 文件操作函数的统一性
假设我们打开了一系列文件或者进程,此时我们就要进入我们的task_struct里面,有个参数叫*files,而这个*files就包含了一系列结构体,如files_struct,里面会有文件标识符,同时由于我们的键盘,鼠标等等都可以被视作文件,那自然就有一个结构体来存储内容和属性,因为文件就是由内容和属性所构成的,因此,倘若我要读取我的键盘文件,那我就调用read函数,要写就调用write函数,对于鼠标文件,显示器文件也是同理,因此,在系统中,还有一个struct file_operations 结构,它包含了文件操作的各种函数指针,如 read、write、open、release 等。对于不同的文件类型(包括设备文件),只要实现了对应的文件操作函数,就可以通过统一的接口进行操作。
但是有一点要注意的是,不同的设备有不同的访问方式,例如,键盘、鼠标等输入设备可以被看作是只读的文件,因为它们主要是向系统提供输入;而显示器可以看作是只写的文件,因为它主要是接收系统输出的内容。对于磁盘等存储设备,既可以读也可以写。对这些设备的操作(如从键盘读取输入、向显示器写输出、读写磁盘等)都是通过文件操作函数(如 read、write 等)来实现的。
2.缓冲区
2.1 什么是缓冲区
缓冲区本质上是一块内存区域,用于临时存放数据。它在数据从一个地方传输到另一个地方时起到暂时存储的作用。例如,在文件读写操作中,数据通常会先从磁盘读取到缓冲区,然后再从缓冲区拷贝到用户空间的进程内存中;或者从用户空间的进程内存拷贝到缓冲区,最后写入磁盘。
2.2 为什么要有这个缓冲区
1. 减少系统调用次数
每次进行系统调用时,CPU需要从用户态切换到内核态,这涉及到保存当前进程的状态、切换到内核模式、执行系统调用相关的代码,然后再切换回用户态。这些操作需要消耗额外的CPU时间。
而通过引入缓冲区,在磁盘文件操作时,系统可以一次性从磁盘读取大量的数据到缓冲区中。后续对这部分数据的访问就可以直接在缓冲区中进行,无需再次进行系统调用。这样大大减少了系统调用的次数,从而减少了CPU时间的消耗。
举例:假设一个程序需要频繁地读取小块数据,如果没有缓冲区,每次读取都需要进行系统调用。而有了缓冲区,程序可以一次性读取一大块数据到缓冲区,之后的多次读取操作都在缓冲区中进行,大大提高了效率。
2. 提高I/O操作效率
磁盘读写效率低:磁盘的读写速度远低于内存的读写速度。如果每次对文件进行读写操作都直接访问磁盘,频繁的磁盘I/O操作会严重影响程序的执行效率。
缓冲区的高效性:计算机对缓冲区(内存区域)的操作速度远远快于对磁盘的操作速度。通过在内存中设置缓冲区,数据在内存中进行缓存,使得对数据的访问速度大幅提高。
数据合并与批量处理:缓冲区机制可以将多次小的读写操作合并为一次大的读写操作。例如,当多个进程需要写入少量数据时,这些数据可以先存储在缓冲区中,等到缓冲区填满后再一次性写入磁盘,减少了磁盘的读写次数,提高了整体的I/O效率。
2.3 三种类型的缓冲区
2.3.1 全缓冲
特点 :当使用全缓冲时,标准 I/O 库会为文件关联一个缓冲区,通常是固定大小的块。输出操作会先将数据写入缓冲区,当缓冲区被填满时,才会将缓冲区中的数据一次性写入文件;输入操作则是先从文件中读取一块数据到缓冲区,之后从缓冲区中逐个读取数据,当缓冲区中的数据被读取完毕后,才会再次从文件中读取数据填充缓冲区。
巧记:可以类比为发快递,这一车满了才能发货,等到了收货地之后,车上的货全卸下来之后才能开走拉下一批货,我这个车就可以类比为全缓冲区
适用场景 :一般用于文件读写操作,特别是对于非终端设备的文件访问,如磁盘文件等。因为对于这类文件,数据的读写通常是以块为单位进行的,使用全缓冲可以减少对文件的频繁读写操作,提高 I/O 效率。例如,当我们打开一个磁盘文件进行读写时,如果使用全缓冲模式,每次写入数据时会先写入缓冲区,当缓冲区满时才会真正写入磁盘文件,这样可以减少磁盘 I/O 次数,提高写入速度。
2.3.2 行缓冲
特点 :行缓冲也是使用缓冲区来存储数据,但它的缓冲区大小通常和一行文本的长度相关。输出时,当遇到换行符时,缓冲区中的数据会被立即写入文件;输入时,从文件中读取数据到缓冲区,直到读取到换行符为止。
适用场景 :主要用于终端输入输出操作,如标准输入(stdin)、标准输出(stdout)等。因为终端的交互通常是逐行进行的,使用行缓冲可以更好地匹配用户的输入输出习惯,提高交互的效率和便捷性。例如,在命令行中输入命令时,只有当我们按下回车键(换行符)后,输入的命令才会被发送给程序进行处理,这就是行缓冲的体现。
2.2.3 不带缓冲
特点 :不带缓冲的 I/O 操作没有缓冲区,所有的读写操作都是直接对文件进行的。每次调用读写函数都会直接与文件进行数据交换,没有中间的缓冲环节。
适用场景 :用于对时间要求严格非常的操作,或者需要直接控制文件读写的情况。由于没有缓冲区的开销,不带缓冲的 I/O 操作能够提供更精确的读写控制和更低的延迟。例如,在一些对实时性要求极高的嵌入式系统中,或者在直接操作硬件设备时,可能会使用不带缓冲的 I/O 操作来确保数据的及时传输和处理。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。
除了上述默认方式外,还有那些刷新的方式呢?
2.2.4 特殊方式
第一个就是缓冲区满时,这个不用过多解释
第二个就是执行flush语句时;
我们看如下代码:
# include <stdio.h>
# include <string.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) {perror("open");return 0;}printf("hello world: %d\n", fd);close(fd);return 0;
}
执行上述代码后,我们发现并没有刷新出我们的想要的内容!
# include <stdio.h>
# include <string.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
int main() {close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) {perror("open");return 0;}printf("hello world: %d\n", fd);fflush(stdout);close(fd);return 0;
}
结果符合预期:

同理方法,可以验证一下stderr没有缓冲区!