Linux 操作系统之文件系统
1 概述
1.1 文件系统在内核中的位置
操作系统内核可分为若干功能子系统:进程调度、进程间通信、内存管理、网络接口、文件系统。文件系统是其中负责管理持久化数据的子系统。
进程间通信的常见方式包括:
- 管道:匿名管道与命名管道(FIFO);
- 消息队列;
- 信号(signal);
- 信号量;
- 共享内存;
- 套接字(socket):包括 Unix domain socket 与网络 socket。
其中:
- 共享内存:内核在物理内存中预留一块区域,需要访问的进程将其映射到自己的地址空间。多个进程直接读写同一块内存,无须复制数据,效率较高。
- 信号量:本质上是一个计数器,配合共享内存使用,用于控制多进程对共享数据的访问顺序,实现进程间同步。
文件系统知识框架如下图所示:

1.2 文件系统的定义与作用
文件系统是操作系统中负责管理持久化数据的子系统。它将用户文件存放在磁盘等持久化存储介质中,即使计算机关机断电,数据也不会丢失。
文件系统是管理文件与目录的一套机制。不同的组织方式形成不同的文件系统类型。文件系统的基本数据单位是文件,主要完成对磁盘上文件的创建、删除、修改、移动、重命名等组织与管理工作。
文件系统类型对单个文件与文件系统本身的大小存在上限约束,例如:
ext3:单个文件不超过 2 TiB;ext4:单个文件最大 16 TiB(4 KiB 块时),文件系统最大 1 EiB;FAT32:单个文件不超过 4 GiB。
1.3 一切皆文件
在 Linux 中,一切皆文件。普通文件、目录、块设备、管道、socket 等,都由文件系统统一管理。用户可以使用统一的系统调用接口(open、read、write、close 等)操作它们。
这一设计继承自 UNIX 哲学:把一切资源抽象为文件,用户以读写文件的方式访问硬件设备,这类特殊文件称为设备文件。
2 存储硬件基础
2.1 磁盘、硬盘与其他存储设备
磁盘(disk)是利用磁记录技术存储数据的存储器;硬盘(hard disk drive, HDD)是计算机最主要的存储设备,属于磁盘的一种,采用温彻斯特结构。软盘也属于磁盘,但已被淘汰;U 盘、固态硬盘(SSD)与磁盘的存储原理不同。
磁盘通过磁性进行读写,断电后数据不丢失;内存的存储载体与磁盘不同,断电后数据即丢失。
2.2 硬盘的物理结构
硬盘由涂覆磁性介质的金属盘片构成,每个盘片有两个盘面,每个面对应一个磁头(head)。盘片绕主轴高速旋转,磁头沿半径方向移动。关键物理概念:
| 概念 | 英文 | 说明 |
|---|---|---|
| 磁头 | head | 每个盘片一般有两个磁头,分别对应上下两个盘面 |
| 磁道 | track | 盘片表面以盘片中心为圆心的同心圆;从外圈向内圈编号为 0 磁道、1 磁道......靠近主轴的同心圆用于停靠磁头,不存储数据 |
| 柱面 | cylinder | 不同盘面上半径相同的所有磁道逻辑上构成一个柱面;柱面数等于磁道数 |
| 扇区 | sector | 每个磁道被切分为若干扇形区域,是磁盘读写的最小物理单位,传统大小为 512 字节 |
| 盘片 | platter | 圆盘数量即盘片数量 |



扇区是磁盘存储数据的基本单位,传统硬盘每个扇区 512 字节。磁头、柱面、扇区三者可唯一定位一个扇区,即 CHS(Cylinder-Head-Sector)寻址方式。磁盘容量计算公式为:
存储容量 = 磁头数 × 柱面数 × 每道扇区数 × 每扇区字节数 \text{存储容量} = \text{磁头数} \times \text{柱面数} \times \text{每道扇区数} \times \text{每扇区字节数} 存储容量=磁头数×柱面数×每道扇区数×每扇区字节数
信息记录可表示为:某柱面(磁道)、某磁头、某扇区。
传统 CHS 寻址用 8 bit 存储磁头地址、10 bit 存储柱面地址、6 bit 存储扇区地址,可寻址的最大容量为:
256 × 1024 × 64 × 512 B = 8 G i B 256 \times 1024 \times 64 \times 512\ \mathrm{B} = 8\ \mathrm{GiB} 256×1024×64×512 B=8 GiB
CHS 模式支持的硬盘容量有限,无法满足大容量硬盘的需求,因此现代硬盘普遍采用逻辑块寻址方式。
2.3 从 CHS 到 LBA:磁盘的逻辑结构
可以把磁盘类比为卷在一起的磁带:盘面上的同心圆磁道展开后是一条一维带,所有盘面的同一磁道(柱面)再依次展开,整个磁盘就等价于一个一维扇区数组。每个扇区获得一个线性下标,称为 LBA(Logical Block Address,逻辑块地址)。







LBA 与 CHS 可以相互转换。设每磁道扇区数为 S P T SPT SPT,磁头数为 H max H_{\max} Hmax,则单个柱面的扇区总数为 H max × S P T H_{\max} \times SPT Hmax×SPT。CHS 转换为 LBA(扇区号 S S S 从 1 开始,柱面号 C C C、磁头号 H H H 从 0 开始):
L B A = C × ( H max × S P T ) + H × S P T + ( S − 1 ) C = ⌊ L B A H max × S P T ⌋ H = ⌊ L B A m o d ( H max × S P T ) S P T ⌋ S = ( L B A m o d S P T ) + 1 \begin{aligned} LBA &= C \times \left( H_{\max} \times SPT \right) + H \times SPT + \left( S - 1 \right) \\ C &= \left\lfloor \frac{LBA}{H_{\max} \times SPT} \right\rfloor \\ H &= \left\lfloor \frac{LBA \bmod \left( H_{\max} \times SPT \right)}{SPT} \right\rfloor \\ S &= \left( LBA \bmod SPT \right) + 1 \end{aligned} LBACHS=C×(Hmax×SPT)+H×SPT+(S−1)=⌊Hmax×SPTLBA⌋=⌊SPTLBAmod(Hmax×SPT)⌋=(LBAmodSPT)+1
磁盘内部自行维护上述参数与转换关系。对操作系统而言,磁盘就是一个以扇区为元素的一维数组,数组下标即 LBA,用一个整数即可访问任意扇区,无须关心物理位置。
2.4 块:文件系统的读写单位
磁盘是典型的块设备。操作系统读写硬盘时不会逐个扇区读取,而是一次性连续读取多个扇区,即读取一个"块"(block)。块大小在格式化时确定且不可更改,最常见为 4 KiB,即连续 8 个扇区组成一个块;块是文件存取的最小单位。
扇区与块的地址换算关系为:
块号 = ⌊ L B A 8 ⌋ L B A = 块号 × 8 + n ( n = 0 , 1 , ... , 7 ) \begin{aligned} \text{块号} &= \left\lfloor \frac{LBA}{8} \right\rfloor \\ LBA &= \text{块号} \times 8 + n \qquad ( n = 0, 1, \ldots, 7 ) \end{aligned} 块号LBA=⌊8LBA⌋=块号×8+n(n=0,1,...,7)
其中 n n n 为块内 n n n 个扇区。

2.5 硬盘的类型
按存储介质与原理,硬盘分为两类:
- 固态硬盘(SSD):以闪存芯片为存储介质,属于半导体存储,不存在机械动作;由控制芯片、缓存芯片和闪存芯片组成 PCB。特点是读写速度快、抗震、功耗低,单位容量价格较高。
- 机械硬盘(HDD):通过磁性介质读写,属于电磁存储,存在机械运转部件;特点是容量大、单位价格低,但读写速度低于 SSD,且怕震动。
磁盘是计算机中主要的存储介质,容量大、价格便宜但速度慢;机械磁盘是计算机中唯一的机械设备。
2.6 硬盘接口
硬盘接口是硬盘与主机系统之间的连接部件,负责在硬盘缓存与主机内存之间传输数据,直接影响系统的整体性能。常见接口:
| 接口 | 说明 |
|---|---|
| IDE(PATA) | 早期并行接口,已淘汰 |
| SCSI | 小型计算机系统接口,常用于服务器 |
| SATA | 串行 ATA,SATA 3.0 速率约 600 MB/s |
| SAS | 串行 SCSI,用于企业级存储,向下兼容 SATA |
| NVMe | 基于 PCIe 总线,面向 SSD,远高于 SATA 的带宽与时延性能 |
| 光纤通道 | 用于存储区域网络(SAN) |
2.7 分区与分区表
一块磁盘可以划分为多个分区(partition),分区实质上是对硬盘的一种格式化。柱面(在 LBA 设备上等价于连续块区间)是分区的最小单位,分区本质上是设置每个分区的起始与结束位置。柱面大小一致、每柱面扇区数一致,因此只要记录起始与结束位置即可确定分区大小及其块范围。


分区表主要有两种格式:
2.7.1 MBR
MBR(Master Boot Record)分区表中,第一个扇区最为重要,包含:
- 主引导记录(boot code),占 446 字节;
- 分区表(partition table),占 64 字节,分为 4 个 16 字节的分区项;
- 结束标志
0x55AA,占 2 字节。
分区表仅有 64 字节,最多记录 4 个分区,分为主分区(primary)与扩展分区(extended)。扩展分区只有一个,需再划分为若干逻辑分区(logical partition)才能使用;逻辑分区编号从 5 开始。MBR 最大支持 2 TiB 磁盘( 2 32 × 512 B 2^{32} \times 512\ \mathrm{B} 232×512 B)。
2.7.2 GPT
GPT(GUID Partition Table)为兼容不同扇区大小的磁盘,采用逻辑块地址(LBA)统一编址:
- LBA 0:保护性 MBR,用于兼容旧工具;
- LBA 1:GPT 头,记录分区表的位置、大小、备份位置及校验码(CRC32);
- LBA 2 至 LBA 33:共 32 个块记录分区项,默认每块 4 个分区项,共 128 个分区;
- 磁盘末尾 33 个块:分区表与 GPT 头的备份。
GPT 没有扩展分区的概念,全部为主分区;若分区表损坏,可用末尾的备份恢复。GPT 最大支持约 8 ZiB( 2 64 × 512 B 2^{64} \times 512\ \mathrm{B} 264×512 B)的磁盘。

2.8 Linux 中的设备命名
Linux 把硬盘、分区等设备都表示为文件,位于 /dev 目录下。分区文件的命名方式为磁盘设备名加编号:
- IDE 设备以
hd开头,如hda1表示第一块 IDE 硬盘的第一个分区; - SCSI、SATA、SAS 及 USB 设备以
sd开头,如sda1、sdb2; - 虚拟化环境中的虚拟磁盘以
vd开头,如vda1; - 回环设备(loop device)以
loop开头,如loop0,用于将镜像文件挂载为块设备; - 设备映射(LVM、dm-crypt 等)以
dm-开头,如dm-1。
编号 1 至 4 保留给主分区与扩展分区,逻辑分区从 5 开始。同一块硬盘上,主分区与扩展分区合计最多 4 个。
3 文件系统的逻辑结构
3.1 文件 = 属性 + 内容
文件由属性(元数据)与内容(数据)两部分组成,二者在磁盘上分离存储。ls -l 输出的每一行包含 7 列:模式(文件类型与权限)、硬链接数、文件所有者、所属组、大小、最后修改时间、文件名。
bash
$ ls -l
total 12
-rw-rw-r-- 1 sjx sjx 827 Jul 20 18:04 install.sh # 普通文件
drwxrwxr-x 12 sjx sjx 4096 Sep 17 22:00 linux # 目录
-rw-rw-r-- 1 sjx sjx 625 Sep 10 20:18 Makefile # 普通文件
stat 命令可以查看更完整的元数据:
bash
$ stat Makefile
File: 'Makefile'
Size: 625 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 658165 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/ sjx) Gid: ( 1001/ sjx)
Access: 2026-09-13 21:08:03.034928673 +0800 # 最后访问时间
Modify: 2026-09-10 20:18:12.216482861 +0800 # 内容最后修改时间
Change: 2026-09-10 20:18:12.216482861 +0800 # 属性最后修改时间
Birth: -

存储文件元信息(创建者、日期、大小等)的区域称为 inode(索引节点)。每个文件对应一个 inode,inode 内包含唯一的 inode 编号。
3.2 格式化与磁盘布局
磁盘格式化后,存储空间被划分为两个区域:一个区域存放文件的元数据(inode),一个区域存放文件的实际内容(block)。初始化时还会创建超级块(super block),其中保存整个文件系统的信息(如 dumpe2fs 命令查看到的信息即来自超级块),并在多个位置备份。

Linux 正统文件系统(如 ext2、ext3、ext4)将硬盘分区划分为三个区域:
- 超级块(super block):记录块与 inode 的总量、已用量与剩余量、块大小、最近一次挂载/写入/校验时间等文件系统自身结构信息;
- inode 表:每个 inode 对应一个文件或目录,记录文件长度、创建与修改时间、权限、所属关系、数据块位置等;
- 数据块区:存放文件内容。

3.3 ext2 文件系统:块组(Block Group)布局
要在硬盘上存储文件,必须先把硬盘格式化为某种文件系统。ext2 系列文件系统将整个分区划分为若干大小相同的块组(Block Group),每个块组具有相同的内部结构;只要能管理一个块组,就能管理整个分区乃至整个磁盘。

启动块(Boot Block)大小固定为 1 KiB,由 PC 标准规定,存储磁盘分区与启动信息,任何文件系统不得修改。启动块之后才是文件系统的数据。每个块组的构成:
- 超级块(Super Block):每个块组的开头都保存一份超级块拷贝(第一个块组必须有,其余块组可没有),彼此保持一致。这样即使部分扇区出现物理损坏,文件系统仍能正常工作;
- 块组描述符表(GDT,Group Descriptor Table):描述每个块组的属性,如块位图、inode 位图、inode 表的位置,空闲块与空闲 inode 数量等,每个块组开头同样保留一份拷贝;
- 块位图(Block Bitmap):记录数据块区中哪些块已被占用、哪些空闲;
- inode 位图(Inode Bitmap):每个比特表示一个 inode 是否空闲;
- inode 表(Inode Table):存放本组全部文件的属性。inode 编号以分区为单位整体划分,不可跨分区;
- 数据块区(Data Blocks):存放文件内容。block 按分区划分,不可跨分区。目录的数据块中存放该目录下的文件名与 inode 编号映射。
3.4 inode 数据结构
ext2 的磁盘级 inode 结构定义(节选自内核源码):
c
struct ext2_inode {
__le16 i_mode; /* 文件模式(类型与权限) */
__le16 i_uid; /* 所有者 UID 低 16 位 */
__le32 i_size; /* 文件大小(字节) */
__le32 i_atime; /* 访问时间 */
__le32 i_ctime; /* 创建时间 */
__le32 i_mtime; /* 修改时间 */
__le32 i_dtime; /* 删除时间 */
__le16 i_gid; /* 组 GID 低 16 位 */
__le16 i_links_count; /* 硬链接计数 */
__le32 i_blocks; /* 占用块数 */
__le32 i_flags; /* 文件标志 */
union { ... } osd1; /* 操作系统相关字段 */
__le32 i_block[EXT2_N_BLOCKS]; /* 指向数据块的指针数组 */
__le32 i_generation; /* 文件版本号(用于 NFS) */
__le32 i_file_acl; /* 文件 ACL */
__le32 i_dir_acl; /* 目录 ACL */
__le32 i_faddr; /* 碎片地址 */
union { ... } osd2; /* 操作系统相关字段 */
};
#define EXT2_NDIR_BLOCKS 12 /* 12 个直接块指针 */
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) /* 共 15 个指针 */
要点:
- 文件名不存放在 inode 中,而存放在目录的数据块中;
- inode 的大小一般为 128 字节或 256 字节;
- 文件内容大小可以不同,但属性大小固定;
i_block[15]中,下标 0 至 11 为直接块指针,12 为一级间接指针,13 为二级间接指针,14 为三级间接指针。
3.5 inode 与数据块的映射
inode 内部通过 i_block[EXT2_N_BLOCKS] 完成 inode 与数据块的映射。以 4 KiB 块为例,各寻址级别可达容量为:
| 寻址方式 | 指针数量 | 可寻址空间 |
|---|---|---|
| 12 个直接块 | 12 | 12 × 4 K i B = 48 K i B 12 \times 4\ \mathrm{KiB} = 48\ \mathrm{KiB} 12×4 KiB=48 KiB |
| 一级间接块 | 1024 1024 1024 | 4 M i B 4\ \mathrm{MiB} 4 MiB |
| 二级间接块 | 1024 2 1024^2 10242 | 4 G i B 4\ \mathrm{GiB} 4 GiB |
| 三级间接块 | 1024 3 1024^3 10243 | 4 T i B 4\ \mathrm{TiB} 4 TiB |

由此,文件 = 属性 + 内容 都可以定位:通过 inode 找到属性与数据块位置,通过数据块读取内容。
3.6 创建文件的过程
以 touch abc 为例,创建一个新文件依次执行以下 4 个操作:
- 存储属性:内核在 inode 位图中找到一个空闲 inode(假设编号为 263466),将文件大小、所有者、创建时间等属性写入其中;
- 存储数据:文件内容需要若干个数据块,内核在块位图中找到空闲块(如 300、500、800),将内核缓冲区中的数据依次复制到这些块;
- 记录分配情况 :内核在 inode 的磁盘分布区(
i_block数组)中按顺序记录上述块号; - 添加文件名到目录 :内核将入口
(inode 编号, 文件名)添加到所在目录的数据块中,建立文件名与 inode 的映射关系。

3.7 目录的本质与文件名映射
目录也是文件,磁盘上不存在独立的"目录"概念,只有文件属性与文件内容。目录的数据块中保存的是 文件名与 inode 编号的映射列表,其中至少包含两个特殊条目:
.:指向目录自身;..:指向父目录。
新建目录的 . 与 .. 通过 inode 编号关联;文件系统创建时,根目录的父目录被设置为指向自身,因此根目录下 . 与 .. 指向同一个 inode。
可以通过程序读取目录内容验证文件名与 inode 的映射关系:
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR *dir = opendir(argv[1]); /* 打开目录流 */
if (!dir) { perror("opendir"); exit(EXIT_FAILURE); }
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) { /* 逐个读取目录项 */
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0) /* 跳过 . 与 .. */
continue;
printf("Filename: %s, Inode: %lu\n",
entry->d_name, (unsigned long)entry->d_ino);
}
closedir(dir);
return 0;
}
用户访问文件时使用文件名而非 inode 编号。查找文件时,先通过路径找到对应目录项,获得 inode 编号;再在 inode 表中找到 inode;最后通过 inode 中的块指针找到数据块。
3.8 路径解析
目录本身也是文件,访问目录同样需要其 inode 编号。访问路径 /home/sjx/code/test.c 时,必须从根目录开始,依次打开路径上的每个目录,逐层查找下一级目录项,直到定位目标文件,这一过程称为路径解析。
- 访问文件必须提供"目录 + 文件名"即完整路径;
- 根目录的文件名与 inode 编号固定,系统启动后必须已知,无须查找;
- 路径由进程提供:进程拥有当前工作目录(CWD),用户执行指令或调用
open时提供路径; - 系统中的目录结构由系统与用户共同构建:新建的任何文件都位于某个已存在的目录之下,从而形成天然的树状路径结构。
3.9 目录项与路径缓存
每次访问文件都从根目录逐级解析效率太低,因此内核会将已解析的目录缓存在内存中,这一机制的数据结构是目录项(dentry)。
- 磁盘上没有 dentry;dentry 是内核维护的数据结构,缓存在内存中,包括文件名、inode 指针及与其他目录项的层级关系;
- 磁盘上的目录文件是持久化的,目录项则由内核在内存中构建;
- dentry 结构使所有已打开文件在内存中形成树状结构,同时隶属于 LRU(最近最少使用)链表进行节点淘汰,并加入哈希表以便快速查找;
- 打开任何文件时,内核先在该树中按路径查找:命中则直接返回 inode 与内容;未命中则从磁盘加载路径并缓存新的 dentry,构成 Linux 的路径缓存。
c
struct dentry {
atomic_t d_count; /* 引用计数 */
unsigned int d_flags;
spinlock_t d_lock;
struct inode *d_inode; /* 关联的 inode,负目录项为 NULL */
struct hlist_node d_hash; /* 哈希链表节点 */
struct dentry *d_parent; /* 父目录项 */
struct qstr d_name; /* 文件名 */
struct list_head d_lru; /* LRU 链表节点 */
union {
struct list_head d_child; /* 父目录的子项链表 */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* 子目录项链表 */
struct list_head d_alias; /* inode 别名链表 */
unsigned long d_time;
struct dentry_operations *d_op;
struct super_block *d_sb; /* dentry 树的根(超级块) */
void *d_fsdata;
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* 内联短文件名 */
};

总结三者关系:文件 = 目录项(dentry)+ inode + 数据块(block)。目录项提供文件名到 inode 的映射,inode 提供属性与数据块位置,数据块存放内容。
3.10 挂载
inode 编号不可跨分区,而 Linux 系统中可以同时存在多个分区。分区写入文件系统后并不能直接使用,必须与某个目录关联起来,即挂载(mount)之后才能使用。挂载点就是访问该分区的入口。
bash
$ dd if=/dev/zero of=./disk.img bs=1M count=5 # 制作 5 MiB 镜像文件,模拟一个分区
$ mkfs.ext4 disk.img # 格式化并写入 ext4 文件系统
$ mkdir mydisk # 建立空目录作为挂载点
$ sudo mount -t ext4 ./disk.img ./mydisk # 将镜像挂载到挂载点
$ df -h # 确认挂载成功
/dev/loop0 3.9M 53K 3.5M 2% /home/sjx/mydisk
$ sudo umount mydisk # 卸载分区
其中 /dev/loop0 表示第一个回环(loop)设备,是一种伪设备,允许将普通文件当作块设备使用。
挂载后,根据访问路径的前缀即可判断文件位于哪个分区。手动挂载的信息必须写入 /etc/fstab,否则下次开机需要重新挂载。




4 内核分层架构与虚拟文件系统
4.1 文件系统的分层结构
从物理磁盘到用户进程,文件访问经过如下层次:
- 硬盘驱动(Device Driver) :不同硬盘(PATA、SATA、AHCI、NVMe 等)提供各自的 I/O 接口,驱动模块一般位于内核源码
drivers/ata目录,通用驱动可直接编译进内核,可通过/boot/config-xxx确认,如CONFIG_SATA_AHCI=y; - 通用块设备层(General Block Device Layer):将杂乱的驱动接口抽象为统一的块设备接口,对上层屏蔽硬盘与驱动的差异;该层的修改会影响所有文件系统;
- 具体文件系统 :由
mkfs.xxx命令创建,如mkfs.ext4、mkfs.xfs;内核支持的文件系统类型见内核源码fs目录; - 虚拟文件系统(VFS):将不同文件系统的 API 抽象为统一接口;
- 系统调用层(System Call Interface):用户空间经系统调用操作文件系统。

4.2 虚拟文件系统(VFS)
文件系统种类繁多,操作系统为向用户提供统一接口,在用户层与具体文件系统层之间引入中间层,即虚拟文件系统。VFS 定义了一组所有文件系统都支持的数据结构与标准接口,用户与应用程序只需面对统一接口,无须关心底层文件系统的差异。文件系统挂载后,其元数据被加载到内存,VFS 仅存在于内存中。
VFS 主要定义四类对象:
- 超级块对象(super block) :记录文件系统的总体信息,如 inode 总量、block 总量;VFS 的超级块最终落地到具体文件系统(
ext4、xfs等)的超级块; - 索引节点对象(inode):包含文件的元信息与数据块位置;
- 目录项对象(dentry):记录文件名与层级关系;
- 文件对象(file):记录进程与打开文件的交互状态。
用户空间的访问路径为:用户程序 → 系统调用(open、read、write、close、mount、umount、mkdir 等)→ VFS → 具体文件系统 → 通用块设备层 → 设备驱动 → 磁盘。


4.3 文件描述符
进程通过系统调用使用文件:open 打开文件并返回文件描述符,read/write 通过文件描述符读写,close 关闭文件。
c
int fd = open(name, flag); /* 打开文件,参数为路径名与标志 */
write(fd, ...); /* 按文件描述符写入 */
close(fd); /* 关闭文件,避免资源泄漏 */
open 的参数包含文件的路径名与文件名;后续 read、write 均使用 open 返回的文件描述符,不再使用文件名。
文件描述符(file descriptor, fd) 是进程打开文件表中的表项编号,是打开文件的标识。传统默认配置下每个进程最多打开 1024 个文件,文件描述符取值范围为 0 至 1023;该限制可通过 ulimit -n 或 /etc/security/limits.conf 调整,现代内核的全局上限由 fs.nr_open 控制。
4.4 进程
进程(process) 是程序关于某数据集合的一次运行活动,是系统进行资源分配与调度的基本单位。进程的组成:
- PCB(Process Control Block,进程控制块):包含 UID、PID、状态、地址空间等信息,是进程存在的唯一标识;
- 代码段(code):程序的机器指令;
- 数据段(data):程序运行过程中产生的数据。
操作系统为每个进程维护一张打开文件表,表中的每一项对应一个文件描述符。进程通过文件描述符直接访问文件时,文件描述符中已记录了文件的访问上下文,无须再经路径逐级查找。
对文件执行移动操作时,只改变目录项(文件名与 inode 的映射)所属的目录,inode 与数据块在磁盘上的位置不变,因此同分区移动文件速度很快。
5 常见文件系统类型
Linux 文件系统可以粗略分为:根文件系统(rootfs)、虚拟文件系统(VFS)、真实文件系统(存在于持久化存储设备)、伪文件系统(存在于内存)、网络文件系统(NFS)。
5.1 本地磁盘文件系统
ext4:第四代扩展文件系统,带日志功能,ext3/ext4在ext2基础上增强但布局设计保持一致;是许多发行版的默认文件系统。最大文件 16 TiB(4 KiB 块),最大文件系统 1 EiB;xfs:由 SGI 开发的 64 位日志文件系统,RHEL 7 / CentOS 7 及之后版本的默认文件系统。特点如下:- 全 64 位体系,支持上百万太字节级存储空间;
- 对特大文件与海量小文件、超大目录的支持表现出色;
- 最大单文件 2 63 − 1 2^{63} - 1 263−1 字节(约 8 EiB),最大文件系统 16 EiB;
- 支持大文件与大分区,读写吞吐高,可扩展性优于
ext4。
ext4 与 xfs 的对比:
| 对比项 | ext4 | xfs |
|---|---|---|
| 单个文件大小 | 16 GiB 至 16 TiB | 16 TiB 至约 8 EiB |
| 最大文件系统 | 1 EiB | 16 EiB |
| 日志 | 支持 | 支持,且元数据与数据日志可分离 |
| 适用场景 | 中小规模、通用场景 | 大文件、大分区、海量小文件场景 |
/boot 分区容量小、文件数量少且以读取为主,使用 ext4 可获得更快的启动阶段驱动加载速度。
5.2 交换分区(swap)
交换分区是磁盘上划出的、用作内存扩展的区域。当物理内存不足时,内核将部分内存页换出到交换分区,以临时存放数据。由于磁盘访问速度远低于内存,频繁换页会导致程序运行变慢,因此生产环境中常按业务需要谨慎配置。现代内存容量较大的场景下,交换分区一般不再按物理内存 2 倍配置,而是取较小值(如 2 至 4 GiB)或按需设置。
常用操作:
bash
$ cat /proc/swaps # 查看交换分区状态
Filename Type Size Used Priority
/dev/dm-1 partition 2097148 0 -2
$ swapon -a # 开启所有交换分区
$ swapoff -a # 关闭所有交换分区
$ swapon /dev/sdd2 # 开启指定交换分区
$ swapoff /dev/sdd2 # 关闭指定交换分区
$ free -m # 查看内存与交换空间使用情况
5.2.1 内核参数 swappiness
内核参数 vm.swappiness 控制换页倾向,取值范围 0 至 100:数值越大,内核越倾向于回收匿名页(换出到交换分区);数值越小,越倾向于回收文件页(页缓存)。默认值因发行版而异(常见为 30 或 60)。
bash
$ cat /proc/sys/vm/swappiness # 查看当前值(运行时生效,重启失效)
30
$ vim /etc/sysctl.conf # 永久修改:写入内核参数配置文件
vm.swappiness = 10
$ sysctl -p # 使配置文件中的参数立即生效
5.2.2 开机自动挂载与交换分区的启停
磁盘手动挂载后,必须将挂载信息写入 /etc/fstab,否则下次开机需要重新挂载。示例配置:
fstab
/dev/mapper/centos-root / xfs defaults 0 0
UUID=0615b8e8-3e85-45eb-9b7d-4f7e08a00eac /boot xfs defaults 0 0
/dev/mapper/centos-swap swap swap defaults 0 0
临时关闭交换分区:swapoff /dev/mapper/centos-swap;再次开启:swapon -a。永久关闭:注释掉 /etc/fstab 中的 swap 行并重启,或执行 swapoff -a 后同步修改配置文件。
/etc/sysctl.conf 存放内核参数;/proc/sys 目录下的子系统(abi、crypto、debug、dev、fs、kernel、net、user、vm)与运行中的内核参数一一对应,修改对应文件即修改内核运行参数。
5.3 伪文件系统
伪文件系统不将数据存储在硬盘上,而是占用内存空间,以文件形式向用户提供系统信息与设备管理手段,常用于 /proc、/sys、/dev。
5.3.1 procfs
procfs(进程文件系统)挂载于 /proc,由内核在启动时动态生成,用于通过文件接口访问进程与系统信息。它不占磁盘空间,仅占用少量内存。/proc 下以数字命名的目录对应各进程,目录名即进程 PID。
bash
$ cat /proc/cpuinfo # CPU 信息
$ cat /proc/meminfo # 内存信息
$ cat /proc/swaps # 交换分区信息
$ ls /proc/self # 当前进程的目录
查看内核支持的文件系统类型:cat /proc/filesystems。
/proc 节点的创建以内核源码 fs/proc/cpuinfo.c 中的实现为例:文件操作结构体注册读写方法,proc_create 函数在 /proc 下创建节点,fs_initcall 宏使初始化函数在内核启动阶段执行。
c
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpufreq.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
__weak void arch_freq_prepare_all(void) { } /* 架构相关准备函数 */
extern const struct seq_operations cpuinfo_op;
static int cpuinfo_open(struct inode *inode, struct file *file)
{
arch_freq_prepare_all();
return seq_open(file, &cpuinfo_op); /* 以 seq_file 方式输出 */
}
static const struct file_operations proc_cpuinfo_operations = {
.open = cpuinfo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init proc_cpuinfo_init(void)
{
proc_create("cpuinfo", 0, NULL, &proc_cpuinfo_operations); /* 创建节点 */
return 0;
}
fs_initcall(proc_cpuinfo_init); /* 内核启动时自动初始化 */
用户态亦可编写内核模块创建自定义 proc 节点。驱动源码(基于 Ubuntu 本地内核头文件编译):
c
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#include <linux/uaccess.h>
static char kernel_buf[1024];
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static int myproc_open(struct inode *node, struct file *file)
{
printk("[%s %d][%s]\n", __FILE__, __LINE__, __func__);
return 0;
}
static ssize_t myproc_read(struct file *file, char __user *buf,
size_t size, loff_t *offset)
{
printk("[%s %d][%s]\n", __FILE__, __LINE__, __func__);
copy_to_user(buf, kernel_buf, MIN(1024, size)); /* 向用户空间复制数据 */
return MIN(1024, size);
}
static ssize_t myproc_write(struct file *file, const char __user *buf,
size_t size, loff_t *offset)
{
printk("[%s %d][%s]\n", __FILE__, __LINE__, __func__);
copy_from_user(kernel_buf, buf, MIN(1024, size)); /* 从用户空间复制数据 */
return MIN(1024, size);
}
static int myproc_close(struct inode *node, struct file *file)
{
printk("[%s %d][%s]\n", __FILE__, __LINE__, __func__);
return 0;
}
static const struct file_operations myproc_operations = {
.owner = THIS_MODULE,
.open = myproc_open,
.read = myproc_read,
.write = myproc_write,
.release = myproc_close,
};
static int __init myproc_init(void)
{
proc_create("myproc", 0, NULL, &myproc_operations); /* 创建 /proc/myproc */
return 0;
}
static void __exit myproc_exit(void)
{
remove_proc_entry("myproc", NULL); /* 删除节点 */
}
module_init(myproc_init);
module_exit(myproc_exit);
MODULE_DESCRIPTION("proc test");
MODULE_LICENSE("GPL");
配套 Makefile:
makefile
KERNELDIR := /usr/src/linux-headers-5.4.0-149-generic # 内核头文件路径
CURRENT_PATH := $(shell pwd)
CONFIG_MODULE_SIG = n # 禁用模块签名
obj-m := proc.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules # 调用内核构建系统
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译、加载与测试流程:
bash
$ make # 编译模块,生成 proc.ko
$ sudo insmod proc.ko # 加载模块
$ lsmod | grep proc # 查看已加载模块
$ sudo chmod 777 /proc/myproc # 赋予节点访问权限
$ echo "hello" > /proc/myproc # 向模块写入数据
$ dmesg # 查看内核打印
$ cat /proc/myproc # 从模块读取数据
$ sudo rmmod proc # 卸载模块
5.3.2 devtmpfs
devtmpfs 在 Linux 内核启动早期自动建立初步的 /dev 目录,使一般启动程序无须等待 udev(设备管理器,负责维护 /dev 下的设备节点)即可使用设备文件,从而缩短开机时间。设备节点是外观与普通文件相同的特殊文件,通过设备文件与设备驱动程序交互,可使用 ls、cat 等常规命令操作。
5.3.3 sysfs
sysfs 是基于内存的虚拟文件系统,挂载于 /sys,用于以文件形式向用户程序提供内核信息,便于管理系统中的设备。与 /dev 提供设备本身的访问接口不同,/sys 用于查看和管理设备:包含系统中几乎所有设备的详细信息,如制造商与型号、设备的总线位置、设备状态、设备层级关系等。/sys 下的文件不是设备节点,不与设备直接交互。
5.4 临时文件系统 tmpfs
tmpfs 完全驻留于内存(必要时可使用交换分区),数据在重启或卸载后丢失,典型挂载点为 /dev/shm、/run。适合存放临时数据、共享内存段等需要高速访问的场景。
5.5 内存文件系统的用途小结
/proc、/sys 类文件系统的共同特点是读写这些文件实际上是读写内核中的数据:读写 /proc/sys 下的文件等价于读写对应的内核参数;/proc/cpuinfo、/proc/meminfo 反映硬件与运行状态。
5.6 网络文件系统(NFS)
NFS(Network File System)采用客户端/服务器架构,使客户端访问远程文件如同访问本地文件。服务器将本地文件系统导出(export),客户端挂载(mount)后使用;文件通常以块为单位传输。典型应用:在嵌入式 Linux 开发中,将主机上的目标文件系统或可执行文件共享给目标板,便于目标板直接运行程序。
5.7 光盘文件系统 iso9660
iso9660 是光盘标准文件系统,使用前需将光盘放入光驱(或将 ISO 镜像通过回环设备挂载):
bash
$ mount -t iso9660 /dev/sr0 /mnt # 挂载光盘
$ mount -o loop,ro -t iso9660 image.iso /mnt # 挂载 ISO 镜像
5.8 Windows 文件系统
FAT32:已淘汰,单文件上限 4 GiB;NTFS(New Technology File System):微软专利技术,当前主流。
5.9 嵌入式与其他文件系统
- 基于闪存的文件系统:
jffs2、yaffs、ubifs; - 只读压缩文件系统:
cramfs、squashfs、romfs; - 内存文件系统:
ramdisk、ramfs/tmpfs; - 网络文件系统:NFS;
- 分布式与专用文件系统:GFS(Google File System)、HDFS(Hadoop Distributed File System)、TFS(淘宝文件系统,面向海量小图片与文件)。
6 文件操作语义
6.1 读取文件的数据通路
文件系统读取文件的完整流程:
- 根据文件名,在目录的数据块中查到对应的 inode 编号;
- 在 inode 表中定位该 inode;
- 通过 inode 中的块指针找到数据块,读取内容。
inode 表与块位图用于空间管理:inode 位图用比特标注哪些 inode 已用(1)、哪些空闲(0),块位图同理。df -i 可查看各文件系统的 inode 使用情况:
bash
$ df -i /boot
文件系统 Inode 已用(I) 可用(I) 已用(I)% 挂载点




进程通过文件描述符持续读写文件时,描述符中已记录文件的访问上下文,无须每次经路径解析与目录项查找。
6.2 删除文件与数据恢复
删除文件时,文件系统执行以下操作:
- 在目录的数据块中删除该文件名对应的目录项;
- 在 inode 位图中将该 inode 标记为未使用;
- 在块位图中将该文件占用的数据块标记为未使用。
inode 与数据块本身的内容并未立即清除。inode 数量与块数量按比例分配,inode 通常少于块;若系统中大量小文件耗尽 inode,即使块仍有剩余空间,也会无法创建新文件。
因此:
- 删除小文件比删除大文件快得多:两者都要删除目录项并更新位图,但大文件占用的数据块数量多;
- 删除后的数据可以恢复:误删文件后应立即停止写入新数据(最好关机),使用恢复软件扫描残留的 inode 与数据块进行恢复;
- 现代文件系统带有日志(journal),记录磁盘操作元数据,可用于恢复目录项等信息。
6.3 硬链接与软链接
真正定位磁盘文件的是 inode 而非文件名。Linux 允许多个文件名对应同一个 inode,即硬链接(hard link):
bash
$ touch test
$ ln test hard # 创建硬链接
$ ls -li
656163 -rw-rw-r-- 2 sjx sjx 0 Sep 29 17:19 hard
656163 -rw-rw-r-- 2 sjx sjx 0 Sep 29 17:19 test
test 与 hard 的 inode 相同,内核记录的硬链接数为 2。删除文件时:删除目录中对应记录,并将硬链接计数减 1,计数归零时才释放 inode 与数据块。
软链接(符号链接,symbolic link)通过文件名引用另一个文件,自身是一个独立文件,拥有独立的 inode,其数据块中存放目标路径字符串,类似于快捷方式:
bash
$ ln -s a b # 创建软链接
$ ls -li
656163 -rw-rw-r-- 1 sjx sjx 0 Sep 29 17:31 a
667405 lrwxrwxrwx 1 sjx sjx 1 Sep 29 17:31 b -> a

对比与用途:
| 对比项 | 硬链接 | 软链接 |
|---|---|---|
| inode | 与目标文件相同 | 独立 inode |
| 本质 | 文件名与 inode 的映射关系 | 独立文件,内容为目标路径 |
| 跨分区 | 不支持 | 支持 |
| 链接目录 | 不支持(防止环) | 支持 |
| 典型用途 | 文件备份、. 与 .. 即目录的硬链接 |
版本管理、路径重定向、快捷方式 |
6.4 文件的时间属性
每个文件维护三个时间:
Access:最后访问时间;Modify:文件内容最后修改时间;Change:文件属性(inode 元数据)最后修改时间。
修改文件内容通常同时更新 Modify 与 Change;仅修改权限等属性只更新 Change。
7 分区规划与挂载配置
7.1 经典分区方案
标准方案(以 CentOS 为例):
| 挂载点 | 建议大小 | 说明 |
|---|---|---|
/ |
剩余全部空间 | 根分区 |
/boot |
约 1 GiB,ext4 |
存放内核、启动配置与 GRUB 等启动必需文件 |
swap |
视内存与业务而定 | 交换分区 |
/home |
按需单独划分 | 用户数据 |
/var |
按需单独划分 | 日志与频繁变动数据 |
/mysql_data 等 |
按业务划分,xfs |
数据库等专用数据分区,便于管理与扩容 |
自定义方案示例:/ 使用 xfs 占剩余空间;/boot 使用 ext4 约 1 GiB;swap 按实际需求设置;数据库数据使用独立分区存放。/lib 存放共享库,/proc 存放运行中的进程与内核信息(进程以 PID 为目录名)。
7.2 开机自动挂载
手动挂载的分区必须在 /etc/fstab 中登记才能开机自动挂载。/etc/fstab 每行格式为:
fstab
<设备或 UUID> <挂载点> <文件系统类型> <挂载选项> <dump> <fsck>
示例:
fstab
UUID=0615b8e8-3e85-45eb-9b7d-4f7e08a00eac /boot xfs defaults 0 0
UUID=e0b23637-b723-4680-8d8c-a6f4238095e8 /mount xfs defaults 0 0
UUID=7d44e927-00d2-43b6-823a-89d9e6e30478 /mount1/hyrz ext4 defaults 0 0
使用 UUID 标识设备可避免设备名变化导致的挂载错误。
7.3 开机自启服务管理(systemd)
bash
$ systemctl enable firewalld # 设置开机自启(建立指向 /etc/systemd/system/ 的符号链接)
$ systemctl disable firewalld # 关闭开机自启(删除对应的符号链接)
enable 操作会在 /etc/systemd/system/multi-user.target.wants 等目录下创建指向 /usr/lib/systemd/system/ 中服务单元的符号链接。
8 常用命令速查
8.1 文件系统信息查看
bash
$ dumpe2fs /dev/sdb2 | less # 查看 ext2/3/4 文件系统信息(超级块来自此)
$ xfs_info /data # 查看 xfs 文件系统信息
$ stat a.txt # 查看文件元数据
$ df -h # 查看磁盘空间使用情况
$ df -i # 查看 inode 使用情况
$ lsblk # 查看块设备与分区拓扑
$ fdisk -l # 查看分区表(MBR)
$ parted -l # 查看分区表(GPT/MBR)
$ cat /proc/filesystems # 查看内核支持的文件系统类型
$ cat /proc/cpuinfo # 查看 CPU 参数
8.2 文件与进程
bash
$ lsof -i :80 # 查看占用 80 端口的进程
$ lsof -p 1055 # 查看指定进程打开了哪些文件
$ lsof # 列举系统中所有打开的文件
8.3 分区与文件系统创建
bash
$ fdisk /dev/sdb # MBR 分区交互工具
$ parted /dev/sdb # GPT/MBR 分区工具
$ mkfs.ext4 /dev/sdb1 # 创建 ext4 文件系统
$ mkfs.xfs /dev/sdb2 # 创建 xfs 文件系统
$ mkswap /dev/sdb3 # 创建交换分区
$ mount /dev/sdb1 /data # 挂载分区
$ umount /data # 卸载分区
$ blkid # 查看设备 UUID 与文件系统类型
8.4 链接操作
bash
$ ln target linkname # 创建硬链接
$ ln -s target linkname # 创建软链接
注:本文为"Linux 系统 | 文件系统"相关合辑。
略作重排,图片清晰度受引文原图所限。
如有内容异常,请看原文。
Reference
- linux磁盘分区详解 - 6点32分 - 博客园
https://www.cnblogs.com/sangmu/p/6629594.html - https://cyc2018.github.io/CS-Notes/#/notes/Linux
- https://www.cnblogs.com/alantu2018/p/8461749.html
- https://www.cnblogs.com/brianzhu/p/6398923.html
- Linux系统------文件(上)-CSDN博客
https://blog.csdn.net/2501_93697341/article/details/165756735 - Linux系统------文件(下)-CSDN博客
https://blog.csdn.net/2501_93697341/article/details/166740297 - Linux文件系统-CSDN博客
https://blog.csdn.net/luyajun97/article/details/88801659 - Linux操作系统之文件系统详解_linux文件系统-CSDN博客
https://blog.csdn.net/m0_52165864/article/details/126436213 - linux文件系统-CSDN博客
https://blog.csdn.net/xxxx123041/article/details/131541092