一、用户缓冲区和系统缓冲区
缓冲区的概念确实可以分为多个层次,其中最常见的两个层次是用户缓冲区和系统缓冲区。
这里的用户缓冲区和系统缓冲区都包括输入输出缓冲区。
1、用户缓冲区(User-space Buffer)
用户缓冲区是指由用户程序(如C语言程序)在用户空间(即非内核空间)中分配和管理的内存区域。在C语言中,当使用标准I/O库(如stdio.h
中定义的函数)进行文件操作时,标准I/O库会自动为每个打开的文件流(FILE*
)分配一个用户缓冲区。这个缓冲区用于暂存读取或写入的数据,以减少对系统调用的依赖,从而提高I/O效率。我们下面将对这个缓冲区进行详细的讲解。
用户缓冲区的特点:
-
由用户程序控制,通常由C标准I/O库管理。
-
用于减少系统调用的次数,提高I/O效率。
-
可以通过
setvbuf
或setbuf
函数来设置缓冲区的大小和类型(全缓冲、行缓冲、无缓冲)。
2、系统(内核)缓冲区(Kernel-space Buffer)
系统缓冲区是指由操作系统内核在系统空间中分配和管理的内存区域。当用户程序通过系统调用(如read
、write
)进行文件I/O操作时,操作系统会在内核中为这些操作分配缓冲区。系统缓冲区用于暂存从磁盘读取的数据或准备写入磁盘的数据,以减少对物理设备的直接访问,提高I/O性能。
系统缓冲区的特点:
-
由操作系统内核控制,用户程序无法直接访问。
-
用于减少对物理设备的访问次数,提高I/O效率。
-
在某些情况下,用户程序需要显式地调用
fsync
、fdatasync
或sync
等函数来确保数据被写入磁盘,以避免数据丢失。
3、总结
用户缓冲区和系统缓冲区都是为了提高I/O操作的效率而设计的,但它们位于不同的内存空间,由不同的实体管理。用户缓冲区是用户程序的一部分,而系统缓冲区是操作系统内核的一部分。在实际的文件I/O操作中,这两种类型的缓冲区可能会同时存在,共同协作以优化性能。例如,用户程序可能首先将数据写入用户缓冲区,然后由C标准I/O库将数据从用户缓冲区转移到系统缓冲区(这里是通过调用系统接口),最终由操作系统内核将数据写入磁盘。
二、文件缓冲区
1、介绍
缓冲文件系统(Buffered I/O)是标准C(ANSI C)中处理文件输入输出的一种机制。它是C语言标准库提供的一种文件处理方式,旨在提高文件读写的效率,同时减少对系统调用接口的调用次数。缓冲文件系统通过在内存中为每一个正在使用的文件自动开辟一个文件缓冲区来实现这一目的。
Word文档在编辑时创建的带有 "~$" 前缀的同名文件是一个临时文件,它就是作为一种缓冲机制来提高数据处理效率。
磁盘的访问速度是小于RAM的,当我们在对Word文档进行编辑时,数据其实是先保存在这个临时文件中的,然后在我们按下Ctrl + S(即完成编辑并保存文档时),Word才会将临时文件中的数据写入原始文档。这种一次性写入原始文件而不是多次写入可以减少磁盘损耗并提高性能。
这里的这个临时文件就可以看做一种缓冲区的技术。
2、缓冲区的刷新策略
缓冲区的刷新策略是操作系统或编程语言库用来管理输入输出(I/O)缓冲区的一种机制。不同的刷新策略适用于不同的I/O场景,以优化性能和效率。以下是三种缓冲区刷新策略的详细解释:
1)立即刷新(无缓冲,Unbuffered)
特点:数据一旦产生就立即被写入到目的地,每次写入操作都会立即发送到目标设备,不使用缓冲区来暂存数据。
适用场景:适用于需要立即看到输出结果的场景,比如标准错误输出(stderr)通常是无缓冲的,以便错误信息可以立即显示给用户,或者对于数据一致性要求极高的应用。
由于每次操作都需要与设备交互,这种策略可能会导致较低的I/O效率。在C语言中,可以使用setbuf(stdout, NULL)
来关闭标准输出的缓冲。
2) 行刷新(行缓冲,Line Buffered)
特点:在这种策略下,缓冲区会暂存数据,当缓冲区中遇到换行符(\n
)时,或者缓冲区满时,数据会被写入到目的地。
适用场景:适用于交互式程序,如终端I/O,因为用户通常期望在输入换行符后看到输出。比如向终端或控制台输出文本。
在C语言中,标准输出(stdout
)默认是行缓冲的,当程序输出换行符时,数据会被立即写入到终端。行缓冲可以提高效率,因为它减少了与设备的交互次数,同时保持了良好的用户体验,因为用户可以看到按行输出的内容。
3)全缓冲(Fully Buffered)
特点:只有当缓冲区满时,直到缓冲区满、程序显式调用刷新函数(如 fflush
)、或者程序结束时,数据才会被写入到目的地。
适用场景:适用于大量数据传输的场景,如向磁盘文件写入数据,因为这样可以减少磁盘I/O操作的次数,提高效率。
在C语言中,文件I/O默认是全缓冲的,可以使用setvbuf
函数来设置缓冲区的大小和类型。全缓冲可以最大化I/O效率,因为它允许操作系统或库函数以最优的方式与设备交互,减少了小数据块的频繁写入。
3、证明缓冲区的存在
我们可以通过下面的代码来证明缓冲区是存在的:
#include <stdio.h>
#include <Windows.h>
int main() {
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return -1;
}
fputs("abcdef", pf);//写入内容,实际上会存入输出缓冲区
printf("下面会暂停,但是缓冲区不会刷新,所以打开文件会发现文件没有内容\n\n");
system("pause");//暂停,这时可以打开文件,发现文件内没有内容,表明缓冲区是存在的
fflush(pf);//刷新缓冲区,这时缓冲区中的内容会写入文件中
printf("下面会暂停,缓冲区在上面的fflush语句就已经刷新,所以打开文件会发现文件有内容了\n\n");
system("pause");//暂停,这时可以打开文件,发现文件内出现内容,表明fflush刷新缓冲区
fclose(pf);//实际上,fclose也会刷新缓冲区
pf = NULL;
return 0;
}
运行结果: