news 2026/7/28 23:27:22

Linux之文件--缓冲区和c封装

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux之文件--缓冲区和c封装

缓冲区在哪里

  • read:操作系统把内核缓冲区的数据,拷贝到你传入的用户内存buf; 如果内核缓冲区没有需要的数据 → OS 发起硬件请求,从磁盘加载数据进入内核缓冲区,再拷贝给应用。
  • write:把应用buf的数据,拷贝进内核缓冲区,函数返回;后续内核再异步写到磁盘。

操作表 = 一组函数指针数组(跳转表)存储这个文件 / 硬件对应的:readwriteopenclosellseek等底层实现地址。

//内核伪代码 struct file_operations { //读写偏移 loff_t (*llseek) (struct file *, loff_t, int); //对应系统调用 read() ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //对应系统调用 write() ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //打开文件 int (*open) (struct inode *, struct file *); //关闭文件 int (*release) (struct inode *, struct file *); //还有 ioctl、poll、mmap…… };

用户层永远只调用统一 API:read(fd)/write(fd)不需要区分底层是磁盘文件、键盘、显示器、网卡、串口。 内核收到系统调用后:

  1. 根据 fd 找到struct file
  2. 取出里面的f_op(操作表)
  3. 调用操作表里对应的函数指针

缓冲区在内核空间

内核缓冲区

图中间区域:内核缓冲区、磁盘、read()/write()系统调用、struct file

  • 归属:内核空间,操作系统维护,所有进程共享内核缓存资源
  • 接口:系统调用read(int fd, buf, size) / write()
  • 数据流向:
  • write 流程:应用 → 用户缓冲区 → write 系统调用 → 内核缓冲区 →(延迟刷新)磁盘
  • read 流程:磁盘 → 内核缓冲区 → read 系统调用拷贝 → 用户缓冲区 → 应用程序变量

重点:调用write()绝不直接写磁盘!write 只是把数据拷贝到内核缓冲区,操作系统会在合适时机(内核线程 pdflush)把缓冲区数据刷入磁盘;这种机制叫延迟写

为什么要有缓冲区

系统调用是有成本的,我们要减少系统调用的次数

new malloc底层都是会调用系统调用,但是你在申请的时候给你的是虚拟地址空间,当你真正开始用的时候在区物理内存里面给你申请

比如在stl里面vector需要扩容的时候,一般采用2倍,这样也有利于减少系统调用的次数

为了减少系统调用,c,c++就登场了

用户级缓冲区

  • 归属:用户空间,由 C 标准库(libc)维护
  • 接口:fopen/fgets/fputs/fprintf/fread/fwrite这些标准 IO 函数
  • 意义:减少系统调用次数系统调用(read/write)会发生用户态 ↔ 内核态切换,开销大。 程序不会调用一次write写 1 个字节;而是先把数据攒在用户缓冲区,缓冲区满 / 主动刷新时,才调用一次write系统调用进入内核。

两条数据流路线

路线 A:标准库 IO(带用户缓冲区 fputs/fprintf)

应用内存变量 →FILE 用户缓冲区→ 缓冲区刷新 →write()系统调用 →内核缓冲区→ 操作系统异步刷入磁盘

路线 B:原生系统调用(无用户缓冲区 read/write)

应用内存 buffer → 直接通过read/write在内核缓冲区、程序内存之间拷贝

接下来我们看下面三个现象

eg1

  1. Linux 规则:最小可用文件描述符分配进程启动默认:fd0 (标准输入)、fd1 (标准输出 stdout)、fd2 (标准错误 stderr)
  • close(1):释放 fd=1
  • 随后open()打开文件,优先拿到最小空闲 fd,也就是 1最终:fd 1 指向 log.txtprintf底层往stdout(fd=1)输出,所以内容本该写入 log.txt。

说明

  • printf:数据放入 stdout 缓冲区,尚未 write
  • close(fd)关闭文件描述符 fd=1
  • 缓冲区数据还躺在内存里,但是通往文件的 fd 已经被关掉!
  • 等到程序退出,库函数尝试刷新缓冲区、调用 write (fd=1) → fd=1 已经关闭,系统调用失败,缓冲区数据直接丢失
  • 文件没有写入任何内容,log.txt为空文件

当我们为他添加fflush,时又重新把hello world写入log.txt文件

eg2

运行效果:等待 1 秒后输出hello

运行效果:等待 1 秒,无任何输出

printf("hello")不带换行符\n;程序输出到终端,stdout 是行缓冲模式。 行缓冲规则:只有遇到\n、主动fflush、进程正常库层退出,才刷新缓冲区;单纯字符串无换行,数据停留在C 库用户缓冲区,不会调用 write 系统调用。

exit会刷新语言级缓冲,二_exit则不会,导致缓冲区内容丢失

eg3

  1. 标准 IO 库函数(C 库,带用户缓冲区)printf / fprintf(stdout) / fputs数据先存入进程用户态 stdio 缓冲区,不一定立刻调用write系统调用。
  2. 系统调用(无用户缓冲区)write(fd,buf,len)直接发起内核调用,数据直达内核缓冲区,不存在用户缓冲区滞留

前置缓冲规则

  • 输出到终端(交互式 shell):stdout =行缓冲,遇见\n自动刷新缓冲区;
  • 输出到普通文件(重定向 > aaaa):stdout =全缓冲\n不会触发刷新,数据停留在用户缓冲区

解释现象

1../buffer.exe直接终端运行(行缓冲) 每条标准 IO 带\n,触发缓冲区刷新,依次打印 4 行,无重复。

2../buffer.exe > aaaa重定向到文件(全缓冲)\n不再刷新;程序退出时exit()统一刷新缓冲区。 顺序:write 直接输出;随后程序退出一次性输出缓冲区内 3 条标准 IO 内容。

解释现象

fork

  1. fork()创建子进程时,采用写时复制(COW),父进程整个用户地址空间完整复制一份给子进程。stdio 缓冲区位于用户地址空间,会被一并拷贝!

  2. 分支 1:终端运行(行缓冲) 标准 IO 函数内部\n触发刷新,fork 执行前缓冲区已经清空。 fork 时父子进程缓冲区都是空的,后续没有重复输出。

  3. 分支 2:重定向文件(全缓冲\n不会刷新缓冲区!执行fork()那一刻: 父进程的 stdout 缓冲区还存放着:hello printf\nhello printf\nhello fprintf\nhello fputs\nfork 复制地址空间 →子进程拿到一份一模一样的缓冲区副本

父子进程是独立进程:

  • 父进程退出:刷新自身缓冲区,输出一组标准 IO 内容;
  • 子进程退出:刷新自己复制得来的缓冲区,再次输出同一组内容;

write()是系统调用,没有用户缓冲区,在 fork 之前就已经完成内核输出,只会打印一次。

内核缓冲区两种刷新方式

  1. 主动强制落盘:fsync () /fdatasync ()系统调用,强制把内核缓冲区数据立即写入磁盘硬件,等待磁盘 IO 完成才返回。
  • fsync(fd):刷新文件数据 + 文件元信息(大小、修改时间等)
  • fdatasync(fd):只刷新文件内容,不强制同步元信息,性能略高
  1. 操作系统异步自动刷新内核后台守护进程(pdflush/flusher)定时扫描,在系统空闲、内存不足时,自动把 Page Cache 数据刷到磁盘;无需程序干预。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/28 23:25:53

Unity材质引用丢失的自动化修复方案与最佳实践

1. 问题现象与根源剖析如果你在Unity开发中经常从Asset Store下载资源,那么大概率遇到过这个让人头疼的问题:兴致勃勃地导入一个精美的模型包、一套炫酷的粒子特效,或者一个完整的场景示例,结果在项目视图中一看,所有材…

作者头像 李华
网站建设 2026/7/28 23:21:19

本地AI编程助手搭建指南:基于DeepSeek API的轻量化开发环境部署

这次我们来看一个本地 AI 开发环境搭建项目:Codex。如果你正在寻找一个能替代 ChatGPT 订阅、又能方便接入国产大模型(比如 DeepSeek)的本地化方案,这篇文章就是为你准备的。Codex 的核心价值在于,它提供了一个集成的开…

作者头像 李华
网站建设 2026/7/28 23:18:46

C/C++二叉树遍历全解析:递归与迭代实现及工程实践指南

1. 项目概述:为什么二叉树遍历是C/C程序员的必修课? 如果你正在学习C或C,并且已经接触到了“数据结构与算法”这个领域,那么“二叉树的遍历”绝对是一个绕不开的核心关卡。这不仅仅是教科书上的一个章节,更是你理解递归…

作者头像 李华
网站建设 2026/7/28 23:18:39

终极指南:5分钟学会使用XCOM 2替代模组启动器AML

终极指南:5分钟学会使用XCOM 2替代模组启动器AML 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xco…

作者头像 李华
网站建设 2026/7/28 23:15:51

高性能硬件与大模型本地化部署实战:E5-2680v4+V100运行Qwen3-Next-80B

1. 项目概述:高性能硬件与大模型本地化部署实战在深度学习领域,如何利用现有硬件资源高效运行百亿参数级别的大语言模型一直是开发者面临的挑战。这次我将分享基于Intel Xeon E5-2680v4处理器和NVIDIA V100 32GB显卡的硬件平台,通过llama.cpp…

作者头像 李华