目录
- 理解"⽂件"
- linux内核中传递标记位的方法
- 系统文件IO
- 内核中打开文件的组织形式
-
- 进程操作文件的途径
- [不同进程打开同一个文件的struct file问题](#不同进程打开同一个文件的struct file问题)
- 文件描述符的分配规则
- 重定向
- 一切皆文件
- 缓冲区
理解"⽂件"
狭义理解
• ⽂件在磁盘⾥
• 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
• 磁盘是外设(既是输出设备也是输⼊设备)
• 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出简称IO
⼴义理解
• Linux下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘......这些都是抽象化的过程)
⽂件操作的归类认知
文件=属性(元数据)+内容
• 对于0KB的空⽂件是占⽤磁盘空间的(因为还有属性)
• ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件=属性(元数据)+内容)
• 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作
系统⻆度
• 对⽂件的操作本质是进程对⽂件的操作
• 磁盘的管理者是操作系统
• ⽂件的读写本质不是通过C语⾔/C++的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的
linux内核中传递标记位的方法

系统文件IO
c/c++操作文件的相关接口,本质上底层都是封装了文件操作相关的系统调用。
编程语言通过抽象层屏蔽不同操作系统的底层差异,通过语言接口在不同系统映射对应的系统调用,实现跨平台性,使开发者无需关注系统调用接口。
所以c++/c中描述打开文件的类/结构体中一定封装了底层文件描述符(fd),因为系统调用的接口只匹配fd
系统调用open
系统接口中使用open函数打开文件,open函数的函数原型如下:
int open(const char *pathname, int flags, mode_t mode);
- 第一个参数:
路径+文件名,不加路径直接在当前路径查找文件 - 第二个参数:标记位
open第二个参数是指定打开方式,通过位图的原理,将多种打开方式用一个32位的整数表示。
选项如下:
可以通过按位或(|)传入多个选项,如下
O_WRONLY | O_CREAT - 第三个参数:权限
传入八进制数表示如果创建新文件的初始权限。结合umask权限掩码可得到最终权限
返回值
open的返回值是文件描述符(文件描述符表的下标),失败返回-1
close
系统接口中使用close函数关闭文件,close函数的函数原型如下:
int close(int fd);
使用close函数时传入需要关闭文件的文件描述符即可,若关闭文件成功则返回0,若关闭文件失败则返回-1。
read
系统接口中使用read函数从文件读取信息,read函数的函数原型如下:
ssize_t read(int fd, void *buf, size_t count);
我们可以使用read函数,从文件描述符为fd的文件读取count字节的数据到buf位置当中。
如果数据读取成功,实际读取数据的字节个数被返回。
如果数据读取失败,-1被返回。
内核中打开文件的组织形式
与进程描述符(struct task_struct)相似已打开文件都会有一个对应的文件描述(struct file)结构体记录文件的动态信息。
进程操作文件的途径
操作文件的本质是进程对文件的操作,一个进程可能操作多个文件
进程操作文件的途径:
- 进程描述符(task_struct)中存在一个文件描述符表(struct files_struct),
- 文件描述符表中记录进程打开的文件的信息,包括打开文件的进程描述结构体指针数组(struct file *fd),指向文件的进程描述,由此管理进程打开的文件。
- 文件描述(struct file)结构体记录文件的动态信息。
- 文件描述符(fd)就是该数组的下标,是最典型的 "句柄" ------ 它本质上是操作系统分配给进程的、用于标识打开文件 / IO 资源的整数型句柄

类比结合内存描述符理解
不同进程打开同一个文件的struct file问题
struct file中包含打开进程的一些信息(文件位置指针等),所以不同进程打开同一个文件,有各自独立的struct file,而struct file中的引用计数是对于单个进程或父子进程内文件描述符表中指向该struct file的个数。(父子进程子进程通过继承父进程的文件描述符,可以共享指向同一个struct file,引用计数++)
本质:struct file 包含了大量进程特定的状态信息,所以每个进程打开文件时需要独立的实例,而 struct inode 才是真正共享的文件元数据。
文件描述符的分配规则
- 进程打开时会默认打开0、1、2,对应标准输入流、标准输出流、标准错误流,0对应键盘,12对应显示器。
而键盘和显示器都属于硬件,属于硬件就意味着操作系统能够识别到,当某一进程创建时,操作系统就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file连入文件双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。
由此可以理解linux下一切(资源)皆(抽象为)文件 - 自己打开的文件的描述符一般从下标3开始,如果关闭012从关闭位置分配(实现重定向)
- 本质是从最小但是没有被使用的fd_array数组下标开始进行分配的。

重定向
重定向的原理
重定向的本质就是修改文件描述符下标对应的struct file*的内容。
-
输出重定向:将本应该输出到一个文件的数据重定向输出到另一个文件中。

-
追加重定向:和输出重定向相同但唯一区别是,输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据。虽然都对应显示器,但其中一个重定向不会影响另一个。
分别对应传递open标志位参数的两个宏,追加、覆盖

-
输入重定向:将本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。

dup
完成重定向我们只需进行fd_array数组当中元素的拷贝即可,系统调用dup可以实现重定向
原型:int dup2(int oldfd, int newfd);
参数:将第一个参数下标的指针拷贝到第二个参数下标的指针
返回值:成功返回newfd,失败返回-1
注意这里关闭一个fd不会关闭文件,因为文件描述采用类似智能指针的引用计数
stdout和stderr为什么分开
主要原因是为了区分程序的正常输出和错误信息,确保错误信息优先输出。
一切皆文件
"一切皆文件" 的本质是通过统一的抽象接口,将复杂的系统资源转化为可通过文件操作逻辑管理的实体。
统一抽象的文件接口,与底层实现分开,
不同外设都有自己的功能实现,但file提供统一的接口
由此将所有资源用抽象的文件接口进行管理,
这便是一切皆文件
之所以文件系统可以封装设备管理也就是抽象操作方法,本质是因为外设无非也是资源,无非也是I/O数据流动所以可以当作文件

通过在file结构体中存储对应实现方法的指针(与类似c++虚函数表指针)实现多态思想,将接口抽象,与实现解耦,实现一切皆文件

缓冲区
缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备,分为输⼊缓冲区和输出缓冲区。
常见的缓冲区类型:
- 标准 I/O 缓冲区(C 语言库层面)
由 stdio.h 提供(如 printf、fwrite 使用的缓冲区),在FILE中设置指针进行管理,分为三种模式:
- 全缓冲:缓冲区满时刷新(如普通文件)。
- 行缓冲:遇到 \n 或缓冲区满时刷新(如终端 stdout)。
- 无缓冲:数据立即输出(如 stderr 错误流)。
FILE中的关键部分:fd(系统文件描述符)、缓冲区相关指针

- 内核缓冲区(操作系统层面)
即使应用程序的缓冲区已刷新,数据也不会直接写入物理设备,而是先进入内核缓冲区,由操作系统统一调度写入(如 Linux 的页缓存)。 - 硬件缓冲区
外设自带的缓冲区(如磁盘的缓存、网卡的接收缓冲区),进一步减少与内存的交互次数。
缓冲区的刷新时机:
- 主动触发:调用 fflush()(标准 I/O)、sync()(内核缓冲)等函数。
- 条件满足:缓冲区写满、行缓冲遇到 \n(仅终端)。
- 被动触发:进程正常退出、关闭文件(fclose() 会自动刷新)。
缓冲区的核心作用:
- 平衡速度:让快速设备(如 CPU)不用等待慢速设备(如磁盘),提高整体效率。
- 减少开销:批量处理 I/O 操作,降低设备访问频率。
- 数据暂存:应对突发的大量数据(如网络峰值流量),避免数据丢失。
标准I/O缓冲区与操作系统内核缓冲区的关系
标准I/O库缓冲区将用户程序中的输入数据暂存,避免频繁调用系统调用(调用成本),当缓冲区刷新时才会将数据发送到下一层(内核缓冲区)。
内核中有内核缓冲区,操作系统有刷新方案进行缓冲区管理,来提升磁盘、网络等设备的 I/O 性能
实例综合理解