
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- [1 ~> 预备阶段](#1 ~> 预备阶段)
-
- [1.1 文件操作归类认知相关重点:文件 = 内容 + 属性](#1.1 文件操作归类认知相关重点:文件 = 内容 + 属性)
- [1.2 对于文件的狭义上的理解:IO](#1.2 对于文件的狭义上的理解:IO)
- [1.3 文件操作学习的本质:学习进程和文件的关系](#1.3 文件操作学习的本质:学习进程和文件的关系)
- [1.4 访问文件就是在访问磁盘](#1.4 访问文件就是在访问磁盘)
- [1.5 思维导图](#1.5 思维导图)
- [2 ~> C文件操作:挑重点](#2 ~> C文件操作:挑重点)
-
- [2.1 句柄](#2.1 句柄)
- [3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术](#3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术)
-
- [3.1 重新定义"文件":从宏观哲学到系统接口](#3.1 重新定义“文件”:从宏观哲学到系统接口)
-
- [3.1.1 广义与狭义的文件理解](#3.1.1 广义与狭义的文件理解)
- [3.1.2 C 库函数 vs 系统调用接口](#3.1.2 C 库函数 vs 系统调用接口)
- [3.2 核心机制:文件描述符(fd)与重定向](#3.2 核心机制:文件描述符(fd)与重定向)
-
- [3.2.1 什么是文件描述符?](#3.2.1 什么是文件描述符?)
- [3.2.2 重定向的本质](#3.2.2 重定向的本质)
- [3.2.3 "一切皆文件"的底层实现](#3.2.3 “一切皆文件”的底层实现)
- [3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体](#3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体)
-
- [3.3.1 为什么会有缓冲区?](#3.3.1 为什么会有缓冲区?)
- [3.3.2 刷新策略的差异](#3.3.2 刷新策略的差异)
- [3.3.3 一个经典的"坑":fork 后的重复输出](#3.3.3 一个经典的“坑”:fork 后的重复输出)
- [3.4 动手实战:理解 FILE 结构体封装](#3.4 动手实战:理解 FILE 结构体封装)
- [4 ~> 总结与学习建议](#4 ~> 总结与学习建议)
-
- [4.1 逻辑架构和切入点](#4.1 逻辑架构和切入点)
- [4.2 总结](#4.2 总结)
- [4.3 学习建议](#4.3 学习建议)
- [5 ~> 思考](#5 ~> 思考)
- 结尾

1 ~> 预备阶段
1.1 文件操作归类认知相关重点:文件 = 内容 + 属性
文件时文件属性(元数据)和文件内容的集合,即文件 = 内容 + 属性(元数据) ,我们可以联想到一些文件操作,比如对内容做操作可以用fread/fwrite,对属性做操作可以用fstat。
一个常识:操作文件之前要先打开文件!
打开文件这里有三问,大家可以思考一下:
-
打开文件的本质是什么?
-
为什么要打开文件?
-
谁打开的文件?
举个例子,比如第一问:打开文件的本质是什么?把文件加载到内存!
1.2 对于文件的狭义上的理解:IO
磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出,简称"IO"。
IO ,也就是 Input 和 Output------我们需要站在内存的角度去理解:
在冯诺依曼体系结构里面,我们说IO的时候永远要站在内存的角度说IO!

- 上面的图中写错了,我们
要站在内存角度!
1.3 文件操作学习的本质:学习进程和文件的关系

1.4 访问文件就是在访问磁盘

1.5 思维导图
如下图所示:

2 ~> C文件操作:挑重点
2.1 句柄
来表征文件唯一性的标识符字段都可以把它叫做句柄。
- 这个柄可以理解为 "把柄"。

3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术
在 Linux 的世界里,"一切皆文件"不仅仅是一句口号,它是整个系统设计的灵魂。本文将带你深度剖析从 C 语言标准 I/O 到系统调用 I/O 的演进,揭秘文件描述符的底层逻辑,并拆解缓冲区背后的性能陷阱。
3.1 重新定义"文件":从宏观哲学到系统接口
3.1.1 广义与狭义的文件理解
在普通用户眼中,文件是磁盘上的 .txt 或 .mp4。但在 Linux 开发者看来:
-
狭义文件:存储在磁盘等永久性介质上的数据集合。
-
广义文件:一切皆文件。键盘是文件(只读)、显示器是文件(只写)、网卡、磁盘、甚至进程间通信的管道都是文件。
这种抽象极其强大:操作系统通过一套统一的接口(read/write)就能管理所有硬件,屏蔽了不同外设的物理差异。
3.1.2 C 库函数 vs 系统调用接口
我们熟悉的 fopen,fwrite,fread 是 C 标准库提供的函数。而 Linux 内核提供的是系统调用 open,write,read。
-
关系 :库函数是对系统调用的二次封装。
-
原因:系统调用接口直接面向内核,使用复杂且不具备跨平台性。标准库通过封装,既提高了易用性,又通过缓冲区提升了性能。
3.2 核心机制:文件描述符(fd)与重定向
3.2.1 什么是文件描述符?
当你调用 open 时,内核会为进程创建一个 struct file 结构体来描述该文件。进程通过一个名为 file_struct 的结构体管理所有打开的文件,其内部包含一个数组(文件描述符表)。
文件描述符 (fd):本质上就是这个数组的下标。
标准分配规则:
-
0:标准输入 (stdin) ------ 对应键盘 -
1:标准输出 (stdout) ------ 对应显示器 -
2:标准错误 (stderr) ------ 对应显示器
3.2.2 重定向的本质
所谓的"输出重定向"(如 > log.txt),其底层逻辑非常简单:改变文件描述符数组中下标的内容。
例如,原本下标 1 指向显示器,重定向操作让下标 1 指向 log.txt。程序依然向 1 号 fd 写入,数据自然就流向了文件。
- 关键系统调用:
cpp
int dup2(int oldfd, int newfd);
- 该系统调用的作用:将
oldfd的内容拷贝给newfd。如果你想把输出重定向到文件,只需 dup2(fd_of_file, 1)。
3.2.3 "一切皆文件"的底层实现
内核通过 struct file 中的 file_operations(函数指针结构体)来实现多态。
每个硬件驱动都有自己的 read / write 实现。
内核 struct file 指向这些驱动函数。
对于上层应用来说,只需调用同一个 write,底层会自动根据 fd 找到对应的硬件操作。
3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体
3.3.1 为什么会有缓冲区?
磁盘 I/O 的速度远慢于内存。如果每写一个字符都调用一次系统调用,CPU 大量时间会浪费在上下文切换上。
用户级缓冲区:由标准库(Glibc)维护。printf、fwrite 将数据先攒在内存里。
内核缓冲区:由 OS 维护。调用 write 后,数据进入内核缓冲区,由 OS 决定何时刷新到磁盘。
3.3.2 刷新策略的差异
行缓冲 (_IOLBF):遇到 \n 就刷新(如 stdout)。
全缓冲 (_IOFBF):缓冲区满了才刷新(如普通文件)。
无缓冲 (_IONBF):立即刷新(如 stderr)。
3.3.3 一个经典的"坑":fork 后的重复输出
现象 :程序中 printf("hello\n") 一次,如果直接运行显示一行;但如果重定向到文件,再 fork(),结果文件里出现了两行 hello。
原因:
-
1、重定向到文件后,刷新策略由"行缓冲"变为"全缓冲"。
-
2、
printf的数据留在用户缓冲区内没来得及刷新。 -
3、执行
fork()时,缓冲区作为父进程内存的一部分,被写时拷贝到了子进程。 -
4、程序结束时,父子进程各自刷新缓冲区,导致同一份数据被写了两遍。
注意:write 是系统调用,没有用户级缓冲区,因此不会出现这种现象。
3.4 动手实战:理解 FILE 结构体封装
在 C 语言中,FILE 结构体其实是一个"大管家",它内部封装了:
-
1、文件描述符 (fd)。
-
2、用户级缓冲区及其相关的读写指针。
-
3、刷新模式等信息。
我们可以尝试自己模拟实现一个 my_stdio,定义一个 mFILE 结构体,包含一个 int _fd 和一个 char _buffer[1024],手动管理数据拷贝和 write 调用。通过这个过程,你就能彻底理解 fflush 到底在做什么------它只是把用户缓冲区的数据 write 给了内核。
4 ~> 总结与学习建议
4.1 逻辑架构和切入点


4.2 总结
-
1、分层理解:应用层 (C 库) -> 系统调用层 (OS) -> 驱动层 (硬件)。
-
2、抓住 fd:fd 是连接用户态与内核态的纽带。
-
3、缓冲区是性能核心:理解什么时候数据在你的进程里,什么时候在内核里。
4.3 学习建议
下一步挑战: 尝试在你的 minishell (自主实现的命令行)中加入重定向功能,利用 strtok 解析 > 或 >>,并使用 dup2 改变进程的文件去向。
5 ~> 思考
如果我们在程序中先关闭了 1 号文件描述符(close(1)),然后紧接着打开一个新文件,那么这个新文件的 fd 会是多少?此时调用 printf 会发生什么?
希望这篇笔记能帮你打通 Linux I/O 的任督二脉!如果你对"写时拷贝"与缓冲区的结合点还有疑惑,欢迎随时讨论。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
