当前位置: 首页 > news >正文

Linux 文件缓冲区

我们要理解文件缓冲区 我们先来看一段代码

int main()
{
const char *fstr = "fwrite_fstr ";
const char *str = "fwrite_str\n\n";
const char *str1="操作系统的接口带\n\n";
const char *fstr1="操作系统的接口不带\n";
const char *fstr2 = "write_fstr ";
const char *str2="write_str\n\n";printf("C语言的接口不带\n ");
printf("printf ");
fprintf(stdout, " fprintf ");
fwrite(fstr, strlen(fstr), 1, stdout);printf("C语言的接口带\n\n");
printf("printf\n");
fprintf(stdout, " fprintf\n");
fwrite(str, strlen(str), 1, stdout);write(1, str1, strlen(str1));
write(1, str2, strlen(str2));write(1, fstr1, strlen(fstr1));
write(1, fstr2, strlen(fstr2));fork();
return 0;
}

Linux 中 IO 分为带缓冲的标准 IO(如 printf、fprintf、fwrite)和无缓冲的系统调用 IO(如 write),且缓冲行为会因输出目标(终端 / 文件)变化:

IO 类型缓冲类型刷新时机
标准 IO (printf 等)行缓冲(输出到终端时)遇到 \n、缓冲满、程序结束时刷新
标准 IO (printf 等)全缓冲(输出到文件时)缓冲满、程序结束时刷新(\n 不触发刷新)
系统调用 (write)无缓冲调用后立即输出,无缓冲延迟

1.    ./test

1. 标准 IO 的 “行缓冲” 导致内容 “攒批输出”

printf、fprintf、fwrite属于带缓冲的标准 IO,在终端输出时采用行缓冲策略—— 只有遇到\n、缓冲区满或程序结束时才会刷新(输出到终端)。

第一块标准 IO 代码(“C 语言的接口不带” 部分):printf、fprintf、fwrite都没有显式\n,因此内容会先存入内存缓冲区,不立即输出。

第二块标准 IO 代码(“C 语言的接口带” 部分):printf("printf\n"); 包含\n,触发行缓冲刷新,此时会将第一块的缓冲内容 + 第二块的printf\n 一起输出,形成 “C语言的接口不带\n printf fprintf fwrite_fstr C语言的接口带\nprintf” 的连续内容。

第二块的fprintf(stdout, " fprintf\n"); 因前面的\n已刷新缓冲,且自身带\n,所以会立即输出,显示为 “ fprintf”。

2. 系统调用write的 “无缓冲” 导致内容 “实时输出”

write是无缓冲的系统调用,调用后会立即将数据输出到文件描述符(此处stdout对应终端),不受行缓冲限制。

“操作系统的接口带” 部分:write(1, str1, ...) 和 write(1, str2, ...) 会立即输出,其中str包含\n,所以 “fwrite_str\n” 会换行显示。

“操作系统的接口不带” 部分:write(1, fstr1, ...) 和 write(1, fstr2, ...) 也会立即输出,无延迟。

3. fork()

fork会复制父进程的地址空间(包括stdout的缓冲区)。

但在本代码中,fork执行前,所有标准 IO 的缓冲区已因\n被刷新,write也已直接输出,因此父子进程的缓冲区为空,程序退出时无额外重复输出。

4.如果前面的没有\n刷新会怎么样???

int main()
{printf("printf只输出一次 ");fflush(NULL);fork();return 0;
}

fork之后会有父子两个进程

当父子进程执行到return 0正常退出时,标准 IO 库会自动触发「缓冲区刷新」(这是标准 IO 的退出清理机制)
当父子进程各自return0、触发stdout缓冲区刷新时,需要修改缓冲区的状态(这里就是把缓冲区内容输入到文件中)—— 这个「修改操作」会触发 写实拷贝;
所以这个时候子进程会拷贝一份和父进程一模一样的缓冲区 

 然后再刷新这个缓冲区 把内容输到文件中(这个步骤底层是调用write函数实现的)

这也是为什么"printf只输出一次 "这个字符串会被打印两次了

2.   ./test>log.txt

重定向(./test>log.txt)的影响 当输出重定向到文件时,标准 IO 的缓冲类型从 “行缓冲” 变为 “全缓冲”,而系统调用 write 的无缓冲特性不受影响。

这直接导致输出顺序和可见性变化

系统调用 write:无缓冲,调用后立即将内容写入文件,所以 hello write 会先出现在 log.txt 中。

标准 IO (printf等):全缓冲模式下,若输出没有 \n 且缓冲未 “满”,会等到程序结束时才一次性刷新。

因此,标准 IO 的内容会在 write 之后输出,顺序与直接运行时(终端行缓冲)相反。

代码中所有标准 IO 操作(printf等)未调用fflush,且输出量未填满缓冲区,因此在 fork 执行前,所有标准 IO 内容都暂存在父进程的stdout缓冲区中,未写入文件。

fork之后

当父子进程执行到return 0正常退出时,标准 IO 库会自动触发「缓冲区刷新」(这是标准 IO 的退出清理机制)

当父子进程各自return0、触发stdout缓冲区刷新时,需要修改缓冲区的状态(这里就是把缓冲区内容输入到文件中)—— 这个「修改操作」会触发 写实拷贝

所以这个时候子进程会拷贝一份和父进程一模一样的缓冲区 

然后再刷新这个缓冲区 把内容输到文件中(这个步骤底层是调用write函数实现的

这也是为什么 C语言的接口printf fprintf这些会被打印两次 并且位于write后面

那么我们可以主动刷新缓冲区吗 答案是可以的

3.fflush

主动刷新缓冲区的函数是fflush

当缓冲区满、遇到换行符(行缓冲,如 stdout)、或调用 fclose 时,stdio 库会自动调用 write,将缓冲区数据写入内核;

而 fflush 的作用是 “手动触发这个过程”:不管缓冲区是否满,强制让 stdio 库调用 write,把当前缓冲区的数据推送到内核。

检查目标 FILE* 流的 stdio 缓冲区(用户态)是否有未写入的数据;

若有数据:调用 write(或 pwrite 等系统调用),将缓冲区数据推送到内核缓冲区;

若没有数据:直接返回成功,不调用任何系统调用(包括 write)。

 int fflush(FILE *stream);

适用性:fflush 对全缓冲和行缓冲都适用,无论当前流是全缓冲(如重定向到文件的stdout)还是行缓冲(如直接输出到终端的stdout),调用 fflush(stream) 都会强制将 stream 对应的缓冲区内容立即写入设备或文件。

1. 参数

FILE *stream: 指向 FILE 类型结构体的指针,代表要刷新的标准 IO 流。

常见取值包括:

stdout:标准输出流(如终端、重定向文件);

stderr:标准错误流;

自定义的文件流(如通过 fopen 打开的文件指针);

特殊值 NULL:此时 fflush 会刷新所有打开的输出流的缓冲区(仅对输出流有效,输入流如 stdin 无意义)。

2.返回值

成功:返回 0;

失败:返回 EOF,并设置全局变量 errno 以指示错误原因(例如流指针无效、IO 操作失败等)。

#include <stdio.h>  
#include <unistd.h>  
int main()
{printf("printf只输出一次 ");fflush(NULL);  fork();return 0;
}

总结一下:

操作系统的接口write是直接输出 没有缓冲

但是c语言的文件接口比如printf  fprintf

C语言会为其提供一个缓冲区

当缓冲区满足一定条件后(行缓冲 全缓冲)

再调用write一起输出

我们可以用代码验证这一点

int main()
{const char* str = "write ";const char* str1 = "fwrite ";write(1, str, strlen(str));close(1);printf("printf ");fwrite(str1, strlen(str1), 1, stdout);fork();return 0;
}

我们知道./test是行缓冲  ./test>log.txt是全缓冲

但是我的代码中没有\n 也就是

无论是./test   还是./test>log.txt

fwrite和fprintf发内容  在return 0之前都是在C语言的提供的缓冲区中

当遇到return 0的时候

父进程和子进程都要

调用write函数刷新缓冲区

但是由于stdout被close(1)关闭了

这个时候调用write函数失败

自然无法打印结果了

思考:这个代码有没有触发写实拷贝???

首先 我们要明确 return0的时候会刷新缓冲区

会调用write函数

但是由于stdout已经关闭了

所以write函数会调用失败

无法对享可写页面实现修改

所以就不会触发写实拷贝

但是要注意:

如果你先fork后

再执行 fwrite printf等等C语言的函数

就会触发写实拷贝 

因为C语言提供的用户缓冲区 本质上是共享可写的

当你子进程printf或者fwrite本质上是对这个缓冲区进行修改
所以就会触发写实拷贝

这个用户缓冲区位于FILE结构体中

我们以fopen函数为例

FILE 结构体的内存分配:“在语言层给我们 malloc (FILE)” ,当调用 fopen 时,libc.so 会在用户态的堆内存中动态分配 FILE 结构体(通过 malloc 实现)。

这个结构体包含了文件的缓冲、状态、关联的文件描述符等关键信息,是标准 I/O 库管理文件的核心载体。

fopen 借助 C 标准库(libc.so)分配 FILE 结构体的内存,并封装了底层系统调用,为开发者提供了 “用 FILE* 操作文件” 的便捷抽象。

fopen 本身会间接调用类似 open 的系统调用来打开文件,再通过 FILE 结构体封装这些底层细节,让开发者无需直接操作文件描述符和系统调用,只需通过 FILE* 就能完成文件的读写、缓冲管理等操作。

举个例子

当调用 fprintf(stdout, "hello world\n"); 时,stdout 是一个 FILE* 类型的指针(代表标准输出流)。fprintf 会先将数据写入 stdout 对应的用户态缓冲区(由 FILE 结构体管理)。

最后再由write写入内核缓冲区

write函数不会触发写实拷贝 但是会被打印两次

但是write函数没有缓冲区 本质上是直接

“将用户空间的数据,写入内核空间的文件描述符(fd)对应的内核缓冲区”(如文件的页缓存、终端的内核缓冲区),其操作不涉及 “修改用户空间内存”:

int main()
{const char* str = "write ";fork();write(1, str, strlen(str));return 0;
}

http://www.dtcms.com/a/609521.html

相关文章:

  • Node.js中常见的事件类型
  • Nacos的三层缓存是什么
  • 交通事故自动识别_YOLO11分割_DRB实现
  • 用flex做的网站空间注册网站
  • Vue + Axios + Node.js(Express)如何实现无感刷新Token?
  • 重大更新!Ubuntu Pro 现提供长达 15 年的安全支持
  • 重庆做学校网站公司农村服务建设有限公司网站
  • 尝试本地部署 Stable Diffusion
  • 网站前置审批专项好的用户体验网站
  • 【动规】背包问题
  • js:网页屏幕尺寸小于768时,切换到移动端页面
  • 《LLM零开销抽象与插件化扩展指南》
  • C++_面试题_21_字符串操作
  • 多重组合问题与矩阵配额问题
  • 什么情况下会把 SYN 包丢弃?
  • EG27324 带关断功能双路MOS驱动芯片技术解析
  • do_action wordpress 模板关键词优化排名的步骤
  • 海外网站入口通信管理局 网站备案
  • 在 Java 中实现 Excel 数字与文本转换
  • 如何保持不同平台的体验一致性
  • redis(五)——管道、主从复制
  • OBS直播教程:OBS实时字幕插件如何下载?OBS实时字幕插件如何安装?OBS实时字幕插件如何使用?OBS实时字幕插件官方下载地址
  • WPF中TemplatePart机制详解
  • 大学生毕业设计课题做网站网站开发研发设计
  • PPT制作正在发生一场静默革命
  • 无线通信信道的衰落特性
  • 大模型量化压缩实战:从FP16到INT4的生产级精度保持之路
  • ListDLLs Handle 学习笔记(8.11):谁注入了 DLL?谁占着文件不放?一篇教你全搞定
  • 电子电气架构 ---软件架构的准则与描述
  • linux下网站搭建wordpress文章页图片尺寸