文件存在磁盘上到底长什么样?一文吃透 Linux 磁盘文件系统核心原理
一、文件系统的本质:给磁盘穿上 “结构化外衣”
磁盘本身只是一块可以按扇区(通常 512 字节)读写的裸存储设备,没有 “文件”“文件夹” 的概念,只能按物理地址读写原始二进制数据。
文件系统就是操作系统在磁盘上建立的一套数据组织结构和管理规则,它把磁盘空间划分成统一管理的单元,实现:
- 按文件名存取数据,不用记忆物理扇区地址;
- 支持目录层级、文件权限、时间戳等属性;
- 自动管理空闲空间,分配、回收、碎片化处理;
- 保障数据可靠性,支持异常恢复。
简单类比:
- 裸磁盘 = 空白的白纸,只有页码,没有内容分类;
- 文件系统 = 在白纸上画好表格、做好目录索引,让你可以按标题快速找到内容。
二、Linux 文件系统双层架构:VFS + 具体文件系统
Linux 支持几十种不同的文件系统(ext4、xfs、btrfs、ntfs 等),为了给上层提供统一接口,内核设计了虚拟文件系统(Virtual File System,VFS)抽象层。
1. VFS 虚拟文件系统
VFS 是内核的抽象框架,定义了一套所有文件系统都必须遵守的通用接口和数据模型,向上给用户态提供统一的系统调用(open/read/write)。 无论底层是 ext4 还是 xfs,是本地磁盘还是网络文件系统,上层应用的调用方式完全一致,实现了 “一套接口,多种实现”。
VFS 的四大核心对象:
- 超级块(super_block):描述整个文件系统的全局信息,比如块大小、inode 总数、空闲块数量;
- 索引节点(inode):描述一个文件的元数据(大小、权限、时间、数据块指针),每个文件对应唯一 inode;
- 目录项(dentry):描述文件名和 inode 的对应关系,维护目录层级结构;
- 文件对象(file):描述一个被进程打开的文件,保存读写偏移等运行时状态。
2. 具体文件系统
负责落地 VFS 定义的接口,真正管理磁盘上的数据布局,常见实现:
- ext4:Linux 主流日志文件系统,稳定通用;
- xfs:高性能日志文件系统,适合大文件、大存储场景;
- btrfs:支持快照、校验、子卷的新一代文件系统。
三、磁盘物理布局:以 ext4 为例,数据到底存在哪
一个 ext4 文件系统会把磁盘分区划分为一个个块组(Block Group),每个块组结构完全相同,分散管理,减少磁道寻道开销。
单个块组的结构从前往后依次是:
- 引导块:存放系统引导信息,只有第一个块组有;
- 超级块副本:整个文件系统的元数据备份,损坏时可用于恢复;
- 块组描述符表:记录当前块组的空间使用状态;
- 数据块位图:用二进制位标记每个数据块是空闲还是已占用;
- inode 位图:用二进制位标记每个 inode 是空闲还是已占用;
- inode 表:连续存放该块组所有的 inode 结构体;
- 数据块区域:真正存放文件内容的地方,是磁盘空间的主体。
关键单位说明
- 扇区:磁盘硬件最小读写单位,通常 512 字节,是磁盘的物理属性;
- 块(Block):文件系统最小读写单位,是逻辑概念,通常 4KB(8 个扇区),是内核和文件系统交互的最小单元。
四、文件的唯一身份:inode 与目录项
1. inode:文件的 “身份证”
每个文件对应唯一的 inode 编号,inode 里存储了文件的所有元数据,唯独不存储文件名:
- 文件大小、权限位(rwx)、所有者、所属组;
- 三个时间戳:创建时间、修改时间、访问时间;
- 数据块指针:指向文件内容所在的数据块地址;
- 硬链接计数。
经典结论:Linux 系统中,文件名不是文件的固有属性,只是用来查找 inode 的别名。
2. 目录项 dentry:文件名的索引
目录本身也是一种特殊文件,它的内容就是一张目录项表,每条记录包含「文件名 + 对应的 inode 编号」。 我们根据文件名找文件的过程,本质就是:
- 在目录文件里查找文件名,得到对应的 inode 编号;
- 根据 inode 编号找到 inode 结构体;
- 根据 inode 里的数据块指针,读取文件内容。
这也解释了为什么硬链接可以跨目录访问同一个文件、删除文件名不代表删除数据 —— 只要 inode 的硬链接计数不为 0,文件数据就还在。
五、读取一个文件的完整内核流程
以read系统调用读取普通文件为例,完整执行链路:
- 用户调用
read系统调用,陷入内核态; - VFS 层接收请求,根据文件路径逐层解析目录项,找到目标文件的 inode;
- 内核先检查页缓存中是否有对应的数据页:
- 缓存命中:直接把数据从内核页缓存拷贝到用户空间,函数返回,全程不碰磁盘;
- 缓存未命中:内核根据 inode 的数据块指针,计算出磁盘上的物理块地址;
- 内核向块设备层发起读请求,通过磁盘控制器读取对应扇区的数据;
- 数据先载入内核页缓存,再拷贝到用户空间;
- 系统调用返回,切回用户态,程序拿到数据。
写文件的流程
write系统调用陷入内核;- 找到对应 inode,分配数据块(如果需要);
- 数据写入内核页缓存,对应页面标记为脏页;
write直接返回,用户认为写入完成;- 内核后台回写线程定期把脏页批量写入磁盘,完成真正的持久化。
六、文件系统与内核页缓存的协同
文件系统和内核页缓存是深度绑定的:
- 所有文件读写默认都经过页缓存,只有设置
O_DIRECT标志才会绕过缓存直接 IO; - 页缓存以文件的页为单位缓存,和文件系统的块对齐,提升命中率;
- 预读机制:读取文件时,内核会提前把后面的几个页面也加载进缓存,利用空间局部性提升顺序读性能。
七、生活化举例理解
举例 1:图书馆类比文件系统架构
可以把整个文件系统比作一座图书馆,各个组件一一对应:
- 裸磁盘 = 图书馆所有书架的物理空间,只有位置编号,没有分类;
- VFS 虚拟文件系统 = 图书馆统一的借阅规则,无论什么类别的书,借还流程完全一致;
- ext4/xfs 等具体文件系统 = 不同阅览室的书架摆放、分类规则;
- inode(索引节点) = 每本书的索引卡片,记录了书的作者、页数、存放架位,唯独不写书名;
- 目录项 = 前台的书名检索目录,记录「书名 → 索引卡编号」的对应关系;
- 数据块 = 书架上真正放书的位置;
- 查找文件的过程 = 先查书名目录拿到索引卡编号,再凭索引卡找到书的具体位置。
举例 2:查找/home/user/test.txt的完整过程
我们日常按文件名打开文件,内核底层会走完整的目录解析流程:
- 系统先获取根目录
/的 inode(固定编号),读取根目录的目录项表,找到home目录对应的 inode 编号; - 根据
home的 inode 编号,读取home目录文件的内容(目录项表),找到user目录对应的 inode 编号; - 根据
user的 inode,读取user目录的目录项,找到test.txt对应的 inode 编号; - 根据 inode 编号,在 inode 表中定位到该文件的 inode 结构体,获取文件权限、大小、数据块指针等信息;
- 最后根据数据块指针,到磁盘对应位置读取文件的实际内容。
八、核心考点与易混区分
- 易混点:文件名存在哪里? 正解:存在目录文件的目录项里,不在 inode 里。
- 易混点:文件大小受什么限制? 正解:受 inode 里数据块指针的寻址能力和磁盘空间共同限制。
- 考点:VFS 的作用与四大核心对象。
- 考点:ext4 块组的结构,inode 包含的内容。
- 结论:
write成功不代表数据落盘,只是写入了页缓存;强持久化必须调用fsync。
九、思维导图
Linux磁盘文件系统原理 ├─ 本质:在裸磁盘上建立的结构化数据管理规则 ├─ 双层架构 │ ├─ VFS虚拟文件系统 │ │ ├─ 作用:统一上层接口,屏蔽底层差异 │ │ └─ 四大对象:超级块、inode、目录项、文件对象 │ └─ 具体文件系统:ext4、xfs、btrfs等,落地磁盘管理 ├─ ext4磁盘布局(块组) │ ├─ 引导块、超级块副本 │ ├─ 块组描述符、数据块位图、inode位图 │ ├─ inode表:存放所有inode结构体 │ └─ 数据块区域:存储文件实际内容 ├─ 核心概念 │ ├─ inode:文件元数据(大小、权限、指针),不含文件名 │ ├─ 目录项:文件名→inode编号的映射表 │ ├─ 扇区:磁盘物理最小单位(512B) │ └─ 块:文件系统逻辑最小单位(通常4KB) ├─ 文件读写全流程 │ ├─ 读:解析路径找inode → 查页缓存 → 命中直接返回,未命中读磁盘 │ └─ 写:写入页缓存标记脏页 → 后台线程异步回写磁盘 ├─ 与页缓存协同 │ ├─ 默认所有IO经过页缓存,提升性能 │ ├─ O_DIRECT可绕过缓存,实现直接IO │ └─ 预读机制提升顺序读性能 └─ 核心考点 ├─ inode不存储文件名,文件名在目录项中 ├─ VFS的作用与四大对象 ├─ write成功≠数据落盘,fsync才保证持久化 └─ ext4块组的组成结构