文章目录
前言
在 Linux 系统中尽管我们对文件系统有所了解,我们仍然对一个文件的打开过程存在一些疑问。这篇文章,就是用来帮助小编自己大致来梳理一下文件系统的一些流程。如何打开一个文件,底层操作系统到底干了什么。
1. 物理内存和文件系统的交互
磁盘的扇区 大小一般都是4KB,这是硬件支持的。同时我们一般的内存空间也是以 4KB 为一个页框被作为一个数据交互的最小单位 。这里有非常多的考量:IO效率的考虑 、局部性原理......考虑。我们这里不多探讨。

页框和页帧的概念比较接近,多用于描述物理内存;页面多用于描述虚拟内存。
操作系统必然需要对内存进行管理 。操作系统管理内存的方式必然是:"先描述,再组织"。
简单来看,系统描述内存空间就可以这样
c
//每一个页框的描述
struct page
{
long long start;
long long end;
//...
};
//组织
struct page m[1048576];
简单来看:我们访问内存就是访问描述内存空间的数组对应的内容。
交互
在操纵系统启动的时候就需要把文件系统中的诸多信息加载到内存中。例如,一个非常关键的字段:super block。这个是文件系统的管理机制,所以操作系统需要将这样加载起来的超级块管理起来。
同时操作系统还会缓存一些 inode 。将一些文件的 inode 信息在内存中缓存起来,这样可以避免后期在进行 IO 请求的时候还需要进行多余的于磁盘进行IO交互获取文件的 inode。
同时操作系统还会缓存一些 dentry 。这个就是目录项的内容 ,里面存放的内容就是一些 inode 编号和文件名的映射关系!
2. 打开一个文件的过程
简单来说,当你在 Linux 中执行 open("/home/user/test.txt", O_RDWR)
这样的系统调用时,内核会:
-
解析路径:从根目录 / 开始,逐级查找到 test.txt 对应的 inode。
-
权限检查:检查当前进程是否有权限打开这个文件。
-
创建内核数据结构:在内存中为这次"打开"操作创建相关的内核数据结构和文件描述符。
-
返回文件描述符:将一个整数返回给应用程序,后续的读、写等操作都通过这个描述符进行。
整个过程可以分为以下几个关键阶段:
-
阶段一:用户空间发起系统调用。
-
系统调用 :上层打开文件最后都会使用系统调用
open
。 -
触发软中断 :
open
函数会将系统调用号、文件路径、打开标志等参数放入特定的寄存器。(准备从用户态转换到内核态) -
执行
syscall
指令:执行syscall
指令,导致 CPU 从用户态切换到内核态。控制权交给内核中一个预定义的入口点。
-
-
阶段二:内核虚拟文件系统处理
- 路径查找 :这是最复杂的步骤之一。内核从当前进程的根目录 (通常是
/
)或当前工作目录开始,逐分量解析路径/home/user/test.txt
。(如果是相对路径,仍然是同样的道理)-
首先查找根目录
/
的inode
。 -
在
/
的目录项(dentry)缓存中查找名为home
的条目,找到其对应的 inode 编号,找 inode缓存,如果不存在就进行磁盘IO读取信息,然后对权限进行检查。如果权限合理,我们就可以继续拿着home
的 inode 找到其对应的 dentry缓存(缓存没有命中就 IO 加载)...... 直到找到对应文件的 inode 编号注意:为了提高效率,内核维护了一个目录项缓存(dentry cache),如果路径的某一部分已经在缓存中,就可以跳过磁盘 I/O,极大地加速查找过程。
-
- 路径查找 :这是最复杂的步骤之一。内核从当前进程的根目录 (通常是
-
阶段三:内核数据结构准备
-
构建目标文件的 inode对象 。内核获取目标文件的 inode编号之后,还是会先检查 inode缓存中是否有对应的内核数据结构。如果没有就从磁盘进行加载,在内核中创建一个 inode对象,并放入 inode缓存中(未来极有可能会再次访问到这个文件)。同时内核还会为用户维护一个文件页缓冲区,这个文件页缓冲区可以理解为存储的是一个个 struct page 结构体。上层写入的内容可以写到文件的页缓冲区中。后面再写入文件的时候,可以统一写入到文件中。
-
构建 struct file。同时分配文件描述符,在当前进程的文件描述符表中,找到一个空闲的最小编号的槽位,将新创建的 struct file 对象的指针填入该槽位。这个槽位的索引号就是返回给用户空间的文件描述符。
-
-
阶段四:返回用户空间
-
系统调用返回:内核完成所有工作后,将文件描述符放入结果寄存器)。
-
切换回用户态:内核执行特殊的指令(如
sysret
),将 CPU 从内核态切换回用户态。 -
系统调用返回:从寄存器中取出结果,将其作为 open() 函数的返回值传递给应用程序。
-
路径解析循环 通过 通过 拒绝 拒绝 从缓存或磁盘
加载该目录的node对象 对路径中的每个目录
获取其node编号 检查对目录的
执行权限 读取目录内容
查找下一分量 获得下一项的node编号 系统调用开始
open('/home/user/test.txt', flags) 路径解析
逐分量查找 循环直至获得
目标文件的node编号 从缓存或磁盘
加载目标文件的node对象 检查对目标文件的
请求权限(r/w) 创建file对象
分配文件描述符 返回文件描述符(fd) 返回错误 EACCES
-
总结:
打开一个文件的过程,本质上是内核为应用程序访问文件建立一条"连接通道"的过程。
最后如下图: