10.【Linux系统编程】缓冲区详解——库缓冲区 VS 内核缓冲区
发布时间:2026/10/6 18:36:08 锦皓数字建站

目录1. 引言1.1 情况1printf完不使用close1.2 情况2printf完使用close1.3 用户IO操作在系统层调用方式2. 缓冲区2.1 什么是缓冲区2.2 为什么要引入缓冲区机制2.3 缓冲区工作流程2.4 刷新方式缓冲类型2.4.0 未刷新缓冲区2.4.1 fflush强制刷新缓冲区2.4.2 stderr刷新条件满足→立即刷新2.5 库函数IO和系统调用IOprintf等write等2.6 FILE结构体2.7 简单设计一下libc库1. 引言1.1 情况1printf完不使用close// stream.c#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includesys/types.h#includesys/stat.h#includefcntl.h#includecstdlibintmain(){close(1);// fd 1intfdopen(log.txt,O_CREAT|O_WRONLY|O_APPEND,0666);// 默认stdout输出,但是将1关闭了所以此时log.txt的文件描述符fd为1stdout 1// 库函数printf(fd:%d\n,fd);printf(hello world\n);printf(hello world\n);printf(hello world\n);//系统调用constchar*msghello write\n;write(fd,msg,strlen(msg));// 没敢写close// close(fd);return0;}运行查看$catlog.txt hellowritefd:1 hello world hello world hello world1.2 情况2printf完使用close// stream.c#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includesys/types.h#includesys/stat.h#includefcntl.h#includecstdlibintmain(){close(1);// fd 1intfdopen(log.txt,O_CREAT|O_WRONLY|O_APPEND,0666);// 默认stdout输出,但是将1关闭了所以此时log.txt的文件描述符fd为1stdout 1// 库函数printf(fd:%d\n,fd);printf(hello world\n);printf(hello world\n);printf(hello world\n);//系统调用constchar*msghello write\n;write(fd,msg,strlen(msg));// 没敢写closeclose(fd);return0;}运行查看$catlog.txt hellowrite1.3 用户IO操作在系统层调用方式为什么使用close之后printf的内容就不能输出到log.txt了而write始终都可以输出到log.txt呢因为C语言标准库的缓冲区的存在缓冲区的工作流程程序写入数据 → 暂存到缓冲区 → 满足刷新条件1.显式操作 / 2.缓冲类型 / 3.程序退出→ 刷新到硬件设备2. 缓冲区2.1 什么是缓冲区缓冲区是内存空间的一部分。也就是说在内存空间中预留了一定的存储空间这些存储空间用来缓冲输入或输出的数据这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备分为输入缓冲区和输出缓冲区。缓冲区在文件部分分为用户级缓冲区库缓冲区、内核级缓冲区2.2 为什么要引入缓冲区机制读写文件时如果不会开辟对文件操作的缓冲区直接通过系统调用对磁盘进行操作(读、写等)那么每次对文件进行一次读写操作时都需要使用读写系统调用来处理此操作即需要执行一次系统调用执行一次系统调用将涉及到CPU状态的切换即从用户空间切换到内核空间实现进程上下文的切换这将损耗一定的CPU时间频繁的磁盘访问对程序的执行效率造成很大的影响。为了减少使用系统调用的次数提高效率我们就可以采用缓冲机制。比如我们从磁盘里取信息可以在磁盘文件进行操作时可以一次从文件中读出大量的数据到缓冲区中以后对这部分的访问就不需要再使用系统调用了等缓冲区的数据取完后再去磁盘中读取这样就可以减少磁盘的读写次数再加上计算机对缓冲区的操作快于对磁盘的操作故应用缓冲区可大大提高计算机的运行速度。又比如我们使用打印机打印文档由于打印机的打印速度相对较慢我们先把文档输出到打印机相应的缓冲区打印机再自行逐步打印这时我们的CPU可以处理别的事情。可以看出缓冲区就是一块内存区它用在输入输出设备和CPU之间用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作避免低速的输入输出设备占用CPU解放出CPU使其能够高效率工作。2.3 缓冲区工作流程程序写入数据 → 暂存到缓冲区 → 满足刷新条件缓冲类型 / 显式操作 / 程序退出 → 刷新到硬件设备。2.4 刷新方式缓冲类型程序一般有三种刷新方式用户缓冲区的刷新方式内核缓冲区的刷新有另一套机制强制刷新执行flush语句刷新条件满足即缓冲类型包括下述三种进程结束标准I/O提供了3种类型的缓冲方式。加粗即为刷新条件全缓冲这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。行缓冲在行缓冲情况下当在输入和输出中遇到换行符时只对显示器适用标准I/O库函数将会执行系统调用操作。当所操作的流涉及一个终端时例如标准输入和标准输出使用行缓冲方式。因为标准I/O库每行的缓冲区长度是固定的所以只要填满了缓冲区即使还没有遇到换行符也会执行I/O系统调用操作默认行缓冲区的大小为1024。无缓冲无缓冲输出的核心是 “绕过标准 I/O 库的缓存直接调用系统调用”。利用默认无缓冲的stderr流通过fprintf(stderr, ...)等函数用setvbuf将指定流如stdout设置为_IONBF模式直接使用系统调用如write完全不依赖标准I/O库。2.4.0 未刷新缓冲区#includestdio.h#includestring.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.hintmain(){close(1);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd0){perror(open);return0;}printf(hello world: %d\n,fd);close(fd);return0;}我们本来想使用重定向思维让本应该打印在显示器上的内容写到“log.txt”文件中但我们发现程序运行结束后文件中并没有被写入内容# cat log.txt空这是由于我们将1号描述符重定向到磁盘文件后缓冲区的刷新方式成为了全缓冲。而我们写入的内容并没有填满整个缓冲区导致并不会将缓冲区的内容刷新到磁盘文件中。当然也可以通过写满缓冲区来验证是否时这个原因这里我们所说的缓冲区是指语言层面的缓冲区即C的库缓冲区而使用的close是系统调用系统调用不会检测C库缓冲区是否有数据没有刷新所以直接使用close关闭文件则会导致数据没有刷新到log.txt文件中。如果我们使用C的库函数fclose那么fclose会检测库缓冲区是否有数据没有刷新那么就会将数据刷新到log.txt里面。也可以使用fflush强制刷新下缓冲区。2.4.1 fflush强制刷新缓冲区#includestdio.h#includestring.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.hintmain(){close(1);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd0){perror(open);return0;}printf(hello world: %d\n,fd);fflush(stdout);close(fd);return0;}还有一种解决方法刚好可以验证一下stderr是不带缓冲区的代码如下2.4.2 stderr刷新条件满足→立即刷新#includestdio.h#includestring.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.hintmain(){close(2);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd0){perror(open);return0;}perror(hello world);close(fd);return0;}这种方式便可以将2号文件描述符重定向至文件由于stderr没有缓冲区“hello world”不用fflush就可以写入文件2.5 库函数IO和系统调用IOprintf等write等因为IO相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过fd访问的。所以C库当中的FILE结构体内部必定封装了fd。来段代码在研究一下#includestdio.h#includestring.hintmain(){constchar*msg0hello printf\n;constchar*msg1hello fwrite\n;constchar*msg2hello write\n;printf(%s,msg0);fwrite(msg1,strlen(msg0),1,stdout);write(1,msg2,strlen(msg2));fork();return0;}运行出结果helloprintfhello fwrite hellowrite但如果对进程实现输出重定向呢./hello file 我们发现结果变成了hellowritehelloprintfhello fwrite helloprintfhello fwrite我们发现printf和fwrite库函数都输出了2次而write只输出了一次系统调用。为什么呢肯定和fork有关一般C库函数写入文件时是全缓冲的而写入显示器是行缓冲。printf、fwrite库函数会自带缓冲区进度条例子就可以说明当发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据就不会被立即刷新。但是进程退出之后会统一刷新写入文件当中。但是fork的时候父子数据会发生写时拷贝所以当你父进程准备刷新的时候子进程也就有了同样的一份数据随即产生两份数据。write没有变化说明没有所谓的缓冲。综上printf、fwrite库函数会自带缓冲区而write系统调用没有带缓冲区。另外我们这里所说的缓冲区都是用户级缓冲区。其实为了提升整机性能OS也会提供相关内核级缓冲区不过不再我们讨论范围之内。那这个缓冲区谁提供呢printf、fwrite是库函数write是系统调用库函数在系统调用的“上层” 是对系统调用的“封装”但是write没有缓冲区而printf、fwrite有足以说明该缓冲区是二次加上的又因为是C所以由C标准库提供。2.6 FILE结构体如果有兴趣可以看看FILE结构体:typedef struct _IO_FILE FILE; 在/usr/include/stdio.h// 在/usr/include/libio.hstruct_IO_FILE{int_flags;/* High-order word is _IO_MAGIC; rest is flags.*/#define_IO_file_flags_flags//缓冲区相关/* The following pointers correspond to the C streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char*_IO_read_ptr;/* Current read pointer */char*_IO_read_end;/* End of get area. */char*_IO_read_base;/* Start of putbackget area. */char*_IO_write_base;/* Start of put area. */char*_IO_write_ptr;/* Current put pointer. */char*_IO_write_end;/* End of put area. */char*_IO_buf_base;/* Start of reserve area. */char*_IO_buf_end;/* End of reserve area. *//* The following fields are used to support backing up and undo. */char*_IO_save_base;/* Pointer to start of non-current get area. */char*_IO_backup_base;/* Pointer to first valid character of backup area*/char*_IO_save_end;/* Pointer to end of non-current get area. */struct_IO_marker*_markers;struct_IO_FILE*_chain;int_fileno;//封装的文件描述符#if0int_blksize;#elseint_flags2;#endif_IO_off_t _old_offset;/* This used to be _offset but its too small. */#define__HAVE_COLUMN/* temporary *//* 1column number of pbase(); 0 is unknown. */unsignedshort_cur_column;signedchar_vtable_offset;char_shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t*_lock;#ifdef_IO_USE_OLD_IO_FILE};2.7 简单设计一下libc库mystdio的实现
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。