文章目录
- [从磁盘到inode:一口气搞懂Linux Ext系列文件系统(小白友好完整版)](#从磁盘到inode:一口气搞懂Linux Ext系列文件系统(小白友好完整版))
-
- 一、先搞懂硬盘本身:数据存在哪?
-
- [1.1 机械硬盘的物理结构](#1.1 机械硬盘的物理结构)
- [1.2 怎么找到数据?CHS与LBA寻址](#1.2 怎么找到数据?CHS与LBA寻址)
- 二、文件系统是干嘛的?给硬盘做「物业管理」
-
- [2.1 先搞懂两个基础概念:块 & 分区](#2.1 先搞懂两个基础概念:块 & 分区)
- 三、灵魂概念:inode是什么?
-
- [3.1 文件 = 内容 + 属性](#3.1 文件 = 内容 + 属性)
- [3.2 inode里有什么?](#3.2 inode里有什么?)
- [3.3 文件名存在哪?目录文件里!](#3.3 文件名存在哪?目录文件里!)
- 四、Ext2文件系统全景:块组是怎么回事?
-
- [1. 超级块(Super Block)](#1. 超级块(Super Block))
- [2. 块组描述符表(GDT)](#2. 块组描述符表(GDT))
- [3. 块位图(Block Bitmap)](#3. 块位图(Block Bitmap))
- [4. inode位图(Inode Bitmap)](#4. inode位图(Inode Bitmap))
- [5. inode表(Inode Table)](#5. inode表(Inode Table))
- [6. 数据块(Data Blocks)](#6. 数据块(Data Blocks))
- 补充:inode怎么找到数据块?
- 五、路径解析、内核缓存与打开文件完整流程
-
- [5.1 磁盘层面原始路径解析(无缓存)](#5.1 磁盘层面原始路径解析(无缓存))
- [5.2 dentry缓存(dcache目录项缓存)](#5.2 dentry缓存(dcache目录项缓存))
- [5.3 inode缓存](#5.3 inode缓存)
- [5.4 进程打开文件:fd、file结构体,两套引用计数](#5.4 进程打开文件:fd、file结构体,两套引用计数)
- [5.5 软链接打开的特殊逻辑](#5.5 软链接打开的特殊逻辑)
- 六、挂载:分区怎么和目录挂钩?
- 七、软硬链接:两个文件指向同一份数据?
-
- [7.1 硬链接(Hard Link)](#7.1 硬链接(Hard Link))
- [7.2 软链接(Symbolic Link)](#7.2 软链接(Symbolic Link))
- 软硬链接对比表
- 八、Ext家族进化:ext3、ext4强在哪?
- 九、完整逻辑总结

从磁盘到inode:一口气搞懂Linux Ext系列文件系统(小白友好完整版)
很多刚接触Linux的同学,一看到ext2/ext3/ext4、inode、块组、软硬链接这些名词就头皮发麻,总觉得文件系统是底层玄学,看不见摸不着。
其实这玩意儿一点都不玄,本质上就是给硬盘做「物业管理」------怎么存数据、怎么找数据、怎么管空间。今天咱们就从一块机械硬盘开始,一层一层往下扒,保证看完你能对着磁盘分区侃侃而谈,吃透底层缓存、删除机制、进程与文件交互逻辑。
一、先搞懂硬盘本身:数据存在哪?
1.1 机械硬盘的物理结构
机械硬盘是电脑里少有的纯机械设备,靠转圈干活。你可以把它想象成一叠黑胶唱片:
- 盘片(Platter):就是那张「唱片」,两面都能存数据,每个盘面对应一个磁头
- 磁道(Track):盘面上一圈一圈的同心圆纹路,数据就存在这些纹路里,从外圈往里圈编号
- 扇区(Sector) :把磁道切成一小块一小块的扇形,这是磁盘读写的最小物理单位,传统大小512字节
- 柱面(Cylinder):所有盘片上半径相同的磁道,叠起来就像一个空心圆柱。所有磁头是「共进退」的,同一时间都在同一个柱面上
一句话总结:硬盘读写数据,就是磁头定位到某个柱面,选中某个磁头,然后等扇区转过来,读写数据。
1.2 怎么找到数据?CHS与LBA寻址
早期的硬盘,找数据靠CHS寻址 :C=柱面号,H=磁头号,S=扇区号。就像你收快递写地址:几栋(柱面)几单元(磁头)几号房(扇区)。
但CHS有个致命问题:容量上限只有8GB左右(256磁头 × 1024柱面 × 63扇区 × 512字节),硬盘越做越大,这地址根本不够用。
于是就有了LBA(逻辑块地址) :
把整个硬盘所有扇区按顺序排成一个一维数组,每个扇区有一个编号(从0开始),这就是LBA地址。
操作系统只需要告诉硬盘「我要读LBA=10086的扇区」,硬盘内部的固件自己把LBA转换成CHS去定位。
简单说:CHS是物理地址,LBA是逻辑地址。现在我们基本只跟LBA打交道,CHS交给硬盘自己玩。
小计算:1个块4KB=8个扇区,所以块号 = LBA号 / 8,反过来LBA = 块号 × 8 + 块内偏移。
二、文件系统是干嘛的?给硬盘做「物业管理」
直接往LBA扇区里写数据不行吗?行,但你写了之后自己都找不到在哪。文件系统就是给硬盘做空间管理、目录索引的「物业系统」。
2.1 先搞懂两个基础概念:块 & 分区
(1)块(Block):文件读写的最小单位
扇区512字节太小了,操作系统一次只读一个扇区效率太低,就像你买米一粒一粒买。所以文件系统把连续的8个扇区(4KB)打包成一个块 ,文件读写的最小单位是块。
块大小在格式化的时候确定,最常见的就是4KB。
(2)分区(Partition):给硬盘「隔房间」
一块大硬盘,我们会把它分成几个区,就像一套大房子隔成卧室、客厅、书房。分区的本质就是划定起止LBA范围,每个分区独立管理,互不干扰。
Linux里一切皆文件,分区也是文件,比如/dev/sda1就是第一块硬盘的第一个分区。
三、灵魂概念:inode是什么?
这是Linux文件系统最核心的概念,很多人学了很久都没搞透。
3.1 文件 = 内容 + 属性
一个文件,比如test.txt,包含两部分:
- 内容数据:文件里写的文字、代码、视频数据
- 属性数据:文件大小、权限、所有者、创建/修改时间、占用多少块
Linux的设计哲学是:属性和内容分开存 。
内容存在数据块里,而属性,就存在一个叫inode(索引节点) 的东西里。
3.2 inode里有什么?
每个文件对应一个inode,inode里存了文件的所有元数据,我们用stat命令就能看到:
bash
$ stat test.c
File: test.c
Size: 654 Blocks: 8 IO Block: 4096 regular file
Device: 802h/2050d Inode: 263715 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-09-13 14:56:57.059012944 +0800
Modify: 2017-09-13 14:56:40.067012944 +0800
Change: 2017-09-13 14:56:40.069012948 +0800
内核里ext2_inode结构体的核心字段:
i_mode:文件类型 + 权限位i_uid/i_gid:所有者用户ID / 组IDi_size:文件字节大小i_atime/i_mtime/i_ctime:访问时间 / 内容修改时间 / 属性变更时间i_links_count:硬链接计数(磁盘上有多少文件名指向该inode)i_block[15]:数据块指针数组,指向文件内容所在的数据块
划重点:inode里不存文件名!
很多人第一次知道都惊了:合着文件名就是个马甲?没错!文件名根本不算文件的固有属性,它只是方便我们人类记忆的「别名」。
3.3 文件名存在哪?目录文件里!
目录也是文件!只不过目录文件的内容,是一张映射表 :里面存着该目录下所有文件名和对应inode号的对应关系。
目录文件在数据块里,下方有解释
你可以把目录理解成一个「通讯录」:
- 左边是人名(文件名)
- 右边是身份证号(inode号)
你想找文件,先查通讯录拿到身份证号(inode号),再凭号去inode表找文件信息,最后去数据块拿内容。
用ls -i就能看到文件名对应的inode号:
bash
$ ls -li
1052007 -rw-rw-r-- 1 whb whb 488 Oct 17 19:06 main.c
1052669 -rw-rw-r-- 1 whb whb 225 Oct 17 19:09 test.c
四、Ext2文件系统全景:块组是怎么回事?
ext2是Ext系列的经典款,ext3/ext4都是在它基础上升级的,核心结构没变。
ext2不会把整个分区一锅粥管理,而是把分区分成一个个块组(Block Group),每个块组独立管理自己的空间,就像小区分成好几栋楼,每栋楼自己管自己的住户。
每个块组从前往后依次是这几部分:
1. 超级块(Super Block)
整个文件系统的「总账本」,记录整个分区的全局信息:总块数、总inode数、空闲块/空闲inode数量、块大小、inode大小、挂载时间等等。
超级块非常重要,坏了整个文件系统就崩了,所以它会在多个块组里做冗余备份。
2. 块组描述符表(GDT)
每个块组的「档案袋」,记录这个块组的元信息:块位图在哪、inode位图在哪、inode表在哪、空闲块/空闲inode还有多少。
3. 块位图(Block Bitmap)
一张「入住登记表」,每个比特位对应一个数据块:0表示空闲,1表示已占用。
想分配新的数据块?查位图找0的位置,标记成1就行,效率极高。
4. inode位图(Inode Bitmap)
和块位图同理,每个比特位对应一个inode:0空闲,1已占用。
5. inode表(Inode Table)
存放这个块组里所有的inode,每个inode大小固定(一般128字节或256字节)。inode号是分区内唯一的,通过inode号就能算出它在哪个块组的inode表里。
6. 数据块(Data Blocks)
真正存文件内容的地方。普通文件存内容,目录文件存文件名‑inode映射表。
一个形象的类比:
整个分区 = 一个小区
块组 = 一栋楼
超级块 = 小区物业总台账
块位图 = 每户入住状态表
inode表 = 住户档案
数据块 = 住户家里的储物空间
目录 = 楼层通讯录
补充:inode怎么找到数据块?
inode里有一个i_block[15]数组,一共15个指针,采用「直接+多级间接」的设计:
- 12个直接指针:直接指向数据块。4KB块的话,48KB以内的小文件,直接通过这12个指针就能找到所有内容,速度极快。
- 1个一级间接指针:指向一个索引块,索引块里存的全是数据块指针。4KB块、每个指针4字节的话,一个索引块能存1024个指针,对应4MB文件。
- 1个二级间接指针:两层索引,能对应4GB左右的文件。
- 1个三级间接指针:三层索引,支持超大文件。
这就是为什么Linux既能高效处理小文件,也能支持大文件。
五、路径解析、内核缓存与打开文件完整流程
你以为cat /home/whb/test.txt很简单?内核在背后做了一整套流程。
5.1 磁盘层面原始路径解析(无缓存)
- 从根目录出发 :根目录
/的inode号是固定的,系统开机就知道。 - 找home目录 :读取根目录的数据块(目录内容),找到
home对应的inode号。 - 找whb目录 :读取home目录的数据块,找到
whb对应的inode号。 - 找test.txt :读取whb目录的数据块,找到
test.txt对应的inode号。 - 读文件内容:拿到test.txt的inode,根据里面的块指针,去数据块读取文件内容。
这个一层一层磁盘读取查找的过程,就叫路径解析。如果每次访问都读磁盘,性能极差,所以内核引入两套核心缓存。
5.2 dentry缓存(dcache目录项缓存)
⚠重点区分:磁盘上有「目录项」;内存里的结构体叫 dentry。磁盘没有dentry,dentry是内核内存对象。
每次磁盘解析目录得到的(父目录+文件名 → inode)结果,内核封装为dentry,全部放入全局内核缓存dcache。
- dentry核心成员:
d_name(单级文件名,不存完整路径),d_parent(父dentry指针),d_inode(指向内存inode缓存) - dentry在内存形成一颗完整目录树,全部进程共享。进程退出不会清理dentry;只有内存不足、文件删除重命名、umount卸载分区才会回收失效。
有缓存时打开文件:
直接沿着dentry父子指针在内存遍历匹配文件名,不需要读取磁盘目录块,速度提升几个数量级。缓存缺失才回落去读磁盘构造新dentry。
重要限制:dcache是正向映射:
父dentry +文件名 → inode。不提供反向索引,不能通过inode号反查文件名。一个inode对应多个硬链接时,会对应多条独立dentry。
5.3 inode缓存
从磁盘inode表读出的inode元数据,拷贝一份到内存,就是inode缓存。以inode号作为key,命中缓存就不用读取磁盘上的inode表。
dcache拿到dentry之后,通过
d_inode指针直接拿到内存inode缓存。
5.4 进程打开文件:fd、file结构体,两套引用计数
当进程open成功之后:
- 内核生成
struct file内存对象,存放文件偏移、读写标志;里面维护f_count,内存打开引用计数,统计多少处正在打开使用该文件。 - 在进程私有的文件描述符表分配fd(整数),fd指向这个
struct file。
两套计数一定要分清:
i_links_count(磁盘inode):统计磁盘上有多少文件名(硬链接)指向inode;为0代表已经从目录树上摘除。f_count(内存struct file):统计多少进程打开持有该文件。
磁盘空间释放的双重条件,两个计数全部归0:
i_links_count == 0:磁盘已经没有任何文件名指向这个inodef_count == 0:没有任何进程打开这个文件
现象:rm删除文件之后磁盘空间不释放。原因就是i_links_count已经为0,但还有进程打开文件,f_count不为0,磁盘数据块与inode暂时保留;所有进程close之后才真正回收磁盘空间。
rm本质就是unlink系统调用:删除目录里的文件名记录,
i_links_count--,不一定立刻删磁盘数据。
5.5 软链接打开的特殊逻辑
如果dentry拿到inode,检测到文件类型是符号链接l:
不会直接使用当前inode对应的内容;读取软链接inode内部存储的路径字符串,丢弃当前查找结果,重新完整执行一轮路径解析流程 。
软链接拥有自己独立inode,完全不修改原文件的i_links_count硬链接计数。
六、挂载:分区怎么和目录挂钩?
分区格式化好了,里面有完整的文件系统,但你直接是访问不到的。必须把它挂载(mount) 到一个目录上,这个目录就叫「挂载点」。
举个实操例子:
bash
# 制作一个5MB的镜像文件,模拟一块磁盘
$ dd if=/dev/zero of=./disk.img bs=1M count=5
# 格式化为ext4文件系统
$ mkfs.ext4 disk.img
# 创建空目录作为挂载点
$ mkdir /mnt/mydisk
# 将镜像挂载到目录上
$ sudo mount -t ext4 ./disk.img /mnt/mydisk/
# 查看挂载情况
$ df -h
# 用完卸载
$ sudo umount /mnt/mydisk
挂载的本质:给这个文件系统找一个目录入口 。以后你访问/mnt/mydisk,其实就是在访问这个分区里的内容。
Linux的目录树是统一的,所有分区都挂载到这棵树上的某个节点,这和Windows的C盘D盘完全不同。
小知识:上面例子里用文件当块设备挂载,用到了loop设备(回环设备),它可以把普通文件模拟成块设备,挂载ISO镜像就是这个原理。
umount卸载的时候,该文件系统全部dcache、inode缓存会被清空。
七、软硬链接:两个文件指向同一份数据?
7.1 硬链接(Hard Link)
硬链接就是同一个inode,多个文件名。相当于给同一个文件起了好几个别名。
bash
# 创建硬链接:ln 源文件 链接文件
$ ln abc def
$ ls -li abc def
263466 -rw-r--r-- 2 root root 0 9月 15 17:45 abc
263466 -rw-r--r-- 2 root root 0 9月 15 17:45 def
可以看到两个文件inode号完全一样,硬链接数变成了2。
删除硬链接文件的时候:
- 在目录里删掉这条文件名记录
- inode里的硬链接数
i_links_count减1 - 只有当硬链接数减到0,并且没有进程打开文件时,这个inode和对应的数据块才会被真正释放
硬链接的特点:
- 不占用新的inode,只是新增一条目录项
- 删除任意一个硬链接文件,只要链接计数大于0,文件本身还在
- 不能跨分区(inode号仅分区内唯一)
- 默认不能给目录创建硬链接(
.和..是系统自动创建的目录硬链接)
如果给父目录创建硬链接,系统无法自动返回上一级目录,会将父目录当做当前目录的子目录,造成一个循环卡死
7.2 软链接(Symbolic Link)
软链接就是Windows里的快捷方式。它是一个独立的文件,有自己的inode,文件内容里存的是目标文件的路径字符串。
bash
# 创建软链接:ln -s 源文件 链接文件
$ ln -s abc abc.s
$ ls -li
263563 -rw-r--r-- 2 root root 0 9月 15 17:45 abc
261678 lrwxrwxrwx 1 root root 3 9月 15 17:53 abc.s -> abc
可以看到软链接有自己独立的inode号,文件类型是l(链接文件)。
如果把原文件abc删了,软链接abc.s就会变成「悬空链接」,指向一个不存在的文件。
软链接的特点:
- 是独立文件,有自己的inode和数据块
- 可以跨分区、跨文件系统
- 可以给目录创建软链接
- 原文件删除后,软链接失效,不会影响原文件硬链接计数
软硬链接对比表
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| inode号 | 和原文件相同 | 独立inode |
| 文件类型 | 和原文件一致 | 链接文件(l) |
| 跨分区 | 不可以 | 可以 |
| 支持目录 | 不支持(除.和...) | 支持 |
| 删除原文件 | 不受影响,链接数‑1 | 失效,变成悬空链接 |
| 占用空间 | 几乎不占(仅目录项) | 占一个inode和数据块 |
八、Ext家族进化:ext3、ext4强在哪?
Ext3:加了日志的ext2
ext3和ext2结构几乎完全一样,最大的升级就是加了日志(Journal)功能 。
没有日志的ext2,突然断电可能导致文件系统不一致,开机要全盘扫描检查,大硬盘要扫几十分钟。
ext3在写数据之前,先把「我要做什么操作」写到日志里,写完数据再更新日志。万一断电了,开机只需要看日志,把没完成的操作回滚/重做就行,恢复速度极快。
ext3有三种日志模式:
- 写回模式(Writeback):只记录元数据日志,数据不记,性能最好,安全性一般
- 顺序模式(Ordered):默认模式,先写数据,再写元数据日志,平衡性能和安全
- 数据模式(Journal):数据和元数据都写日志,最安全,性能最差
Ext4:全面升级的下一代
ext4在ext3基础上做了大量优化,是现在大多数Linux发行版的默认文件系统:
- 更大的容量:支持最大1EB的分区,最大16TB的单个文件
- 延迟分配:先攒一攒再分配磁盘块,减少碎片,提升性能
- 多块分配:一次分配多个连续块,提升大文件写入速度
- 纳米级时间戳:时间精度从秒级提升到纳秒级
- 日志校验:日志增加校验和,更可靠
- 在线碎片整理:挂载状态下也能整理碎片
九、完整逻辑总结
- 物理硬盘由盘片、磁头、扇区组成,现代使用LBA一维逻辑地址寻址。
- 文件系统把扇区打包成块;磁盘划分分区,每个分区内部切割成多个块组进行管理。
- 文件元数据放在inode,实际内容放在数据块;文件名保存在目录文件的映射表,inode不存储文件名。
- 访问文件需要从根目录逐层做路径解析;dcache(dentry)、inode缓存是内核全局缓存,极大减少磁盘IO。dentry是内存对象,磁盘只有目录项。
- 文件删除有两套引用计数:磁盘
i_links_count硬链接计数,内存f_count打开计数;两套计数全部归零才真正释放磁盘空间。 - 硬链接:多文件名共享同一个inode;软链接是独立inode,内部存放目标路径字符串,打开时二次路径解析。
- Linux所有分区必须mount挂载到目录树的挂载点才可以访问,和Windows盘符模型完全不同。
搞懂了这些,你再看ls、stat、ln、mount、rm这些命令,就知道它们背后到底在操作什么了。文件系统不是玄学,就是一套精心设计的「数据管理方案」。