前言
在 Linux 系统中,I/O 操作背后有一个关键概念——缓冲区。缓冲区是数据的临时存放的地方,能有效提升 I/O 效率。本文将带您了解 Linux 系统中的缓冲区概念

1. 缓冲区是什么
缓冲区是是什么,有谁来提供?
缓冲区本质就是块内存空间;
缓冲区可以根据层次分为3种:
- 用户缓冲区:程序员在程序中手动定义的缓冲区,通常使用数组或容器来存储和管理数据。
- 库缓冲区:编程语言标准库提供的缓冲区,比如: C 标准库中的
printf、scanf等函数,C++ 中的std::cout、std::cin也有缓冲机制。 - 系统缓冲区:由操作系统管理的缓冲区,存在于内核层。比如:文件 I/O 操作时,操作系统会为打开的文件创建系统缓冲区。
观察现象:
printf("hello world!");
sleep(3);
执行这两条语句会发现,需要程序运行结束后才能刷新出来,而不是理解就打印出来;
这块内存是怎么来的?
在Linux中,无论是用户自己的C程序,还是C标准库,以及Linux操作系统,它们都是用C语言写的,我们可以把它们简单理解为就是malloc申请的;
2. 为什么要存在缓冲区?
其实就是为了提高效率;缓冲的思想可以运用的面很广,是提高效率的有效手段;
或许在一些简单的程序中感受不出来缓冲区的意义,举个例子:
以菜鸟驿站为例,比如:你想送你朋友一本书,但是你身处在杭州,你朋友在北京,自己亲自跑到山东送给他?肯定不是这样效率太低,当然是先把书交给菜鸟驿站,驿站帮你打包送出,当你把书交给驿站后,就可以认为书已经送出(后续的送书任务不需要你做);

当你把书交给快递点后,站点就立即派专车运算吗?这样成本高,那么多快件,每件都单独运送效率也低,他会积攒一些快件,等到一定数量再送;
缓冲区的作用就类似菜鸟驿站,目的就是为了提高效率;
缓冲区有暂存数据的功能,就必定有一定的刷新方式:
- 无缓冲(立即刷新)
- 行缓冲(行刷新)
- 全缓冲(缓冲区满了再刷新)
特殊情况:强制刷新,进程退出时,一般需要进行缓冲区刷新;
一般的显示器文件,都是行刷新(行缓冲)对于磁盘文件,全缓冲(缓冲写满再刷新)
3. C库的缓冲区
虽然无法通过源码查看,但是可以通过现象来研究一下C库的缓冲区;
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
// C库接口
fprintf(stdout, "C: hello fprintf\n");
printf("C: hello printf\n");
fputs("C: hello puts\n", stdout);
const char* str = "system call: hello write\n";
// 系统调用
write(1, str, strlen(str));
fork();
return 0;
}
直接运行:

输出重定向:
现象解释:
- 直接向显示器打印时,显示器的刷新方式是行刷新,而代码中所有的字符串输出都有\n,fork之前数据就已经被刷新,包括系统调用;
- 重定向到log.txt,本质是向磁盘文件中写入,数据的刷新方式变成了全缓冲;全缓冲意味着缓冲区变大,写入的简单数据不足以把缓冲区写满,fork执行的时候数据依旧在缓冲区中;
- 系统调用输出一次,说明目前我们所说的缓冲区,与操作系统是没有关系的(系统调用不受影响);他只能和C语言本身有关
- 进程退出时,一般都要刷新缓冲区,即使数据没有满足刷新条件
- 进程在缓冲区的数据依然属于进程,fork(创建子进程)执行完之后,立马退出,任意一个进程退出时,刷新缓冲区,就要发生写时拷贝,所以数据就输出了两份;
梳理一下重定向的流程:

printf把数据加载到C语言缓冲区就返回了,如果printf频繁的向文件中写入数据,就会导致printf的效率变低,通过缓冲区积攒一部分数据后再写入,可以有效的提高的printf的调用效率 ;
执行结束,进程退出时,通过进程中的文件描述符表1位置文件描述对象的写方法,将数据写到文件缓冲区(从C缓冲区写到OS的工作就叫作刷新);内核负责在适当的时机刷新数据,将缓冲区内容写入磁盘;
现在再来看看C库中的IO接口:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
FILE *fopen(const char *pathname, const char *mode);
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
都有FILE*类型,FILE是一个结构体,内部封装了文件描述符,现在还可以推出,他内部还包含了缓冲区;
如果一个进程它同时打开5个文件,那它就会有5个FILE对象,每个FILE对象都会包含一个缓冲区;并且在底层,每个打开的文件都有自己的文件描述对象 struct file,每个文件也都有自己的文件缓冲区;所以在进行这些文件读写时,每个文件都互相不受影响;
查看一下C库中FILE结构体:
在Centos7下可以查看:
grep -n 'struct _IO_FILE {' /usr/include/libio.h
vim /usr/include/libio.h +246

以上便是本文的全部内容,希望对你有所帮助,最后感谢阅读!
1216

被折叠的 条评论
为什么被折叠?



