一句话总结:文件系统把分区“装修”成 superblock + inode + data block,文件名只是目录里的一条映射,链接就是围绕 inode 做文章。 适用:Rocky Linux / CentOS 9,ext4 / xfs。示例目录
/tmp/fs。
一、文件系统是什么
文件系统是操作系统里控制硬盘、组织数据的一套程序。
- 分区:把一块硬盘“打隔断”,分成一个个小空间;
- 文件系统:对每个小空间“做装修”,负责把里面的数据组织好。
所以:每个分区都要有一个文件系统,格式化(mkfs)就是“装修”的过程。
使用者 ──> block 块(文件系统管理单位) ──> n 个扇区(硬盘最小读写单位)
扇区通常 512B,文件系统以 block(默认 4K)为单位分配空间,一个 block 包含 8 个扇区。
二、文件系统的三大类块
一个文件由元数据 + 内容数据两部分组成,分别落在不同的块里:
| 块类型 | 存什么 | 数量 |
|---|---|---|
| superblock 超级块 | 整个文件系统的整体信息:inode/block 的总量、已用、剩余、文件系统格式 | 一个文件系统只有一个 |
| inode block 索引节点 | 一个文件的元数据:权限、属主、属组、大小、时间戳、数据块指针、链接数 | 一个文件一个 inode |
| data block 数据块 | 文件真正的内容(cat 看到的东西);目录的 data block 存“子文件名 → inode 号” |
一个文件可以有多个 |
/etc/passwd ──> [ inode #12345 ] ──> [ data block 1 ][ data block 2 ] ...
元数据 真正的文本内容
重要结论:文件名不存 inode 里。文件名和 inode 号的对应关系存在目录的 data block 里。这也是硬链接、软链接原理的根源。
三、查看文件系统
| 命令 | 作用 | 例子 |
|---|---|---|
df -T |
看挂载点和文件系统类型 | df -T |
df -h |
看空间使用量(带单位) | df -h |
df -i |
看 inode 使用量 | df -i |
lsblk -f |
看分区、文件系统、UUID、挂载点 | lsblk -f |
mount |
看当前挂载 | mount |
stat 文件 |
看 inode 号、块数、三个时间 | stat /etc/passwd |
ls -i |
只看 inode 号 | ls -i /etc/passwd |
findmnt |
树形查看挂载关系 | findmnt / |
df -T
df -h
df -i
lsblk -f
四、磁盘“满”的两种情况
这是运维面试和故障排查的高频点:df -h 显示还有空间,但就是写不进去。
| 情况 | 原因 | 怎么查 | 怎么造 |
|---|---|---|---|
| inode 耗尽 | 小文件太多,inode 号用光了(空间可能还有富余) | df -i,IUse% 到 100% |
循环 touch 几百万个空文件 |
| 磁盘空间耗尽 | 单个大文件把 block 占满 | df -h,Use% 到 100% |
dd 生成超大文件 |
# 造 inode 耗尽(300 万个空文件,别在生产上试)
for ((i=1;i<=3000000;i++)); do touch $i.txt; done
# 造空间耗尽(30G 零字节文件)
dd if=/dev/zero of=/tmp/1.txt bs=10G count=3
# 动态观察 inode 使用
watch -n 1 "df -i | grep sda1"
快速释放一个已被进程打开的超大文件:直接
rm可能不释放空间(进程还拿着 fd)。正确做法是先清空内容:> /path/bigfile(或: > bigfile),再删。
清理 inode:先定位哪个目录小文件最多。
for d in /var/*; do echo -n "$d "; find "$d" -xdev -type f 2>/dev/null | wc -l; done
find /var/spool -type f -name "*.tmp" -delete
五、打开一个文件的底层流程
以 cat /etc/passwd 为例:
1. 从根目录 / 的 inode 开始(根 inode 号固定,ext4 通常是 2)
2. 读 / 的 data block,找到 "etc" 对应的 inode 号
3. 读 etc 的 data block,找到 "passwd" 对应的 inode 号
4. 读 passwd 的 inode,拿到权限、属主、数据块指针
5. 按指针把 data block 里的内容读进内存,交给 cat 输出
关键点:
- 目录也是文件:它的 inode 存元数据,data block 存“子文件名 → inode 号”的列表;
- 路径里每一级都要有 x 执行权限才能进去;
- 进程打开文件后,内核在进程的文件描述符表里记一项,指向系统的“打开文件表”,再指向 inode。所以 fd 是“进程视角的编号”,inode 是“磁盘视角的身份”。
六、硬链接与软链接
6.1 硬链接 hard link
ln /1.txt /2.txt
- 本质:两个文件名指向同一个 inode,inode 的链接数 +1。
- 特点:
| 特点 | 说明 |
|---|---|
| 内容同步 | 改一个文件内容,另一个跟着变(本来就是同一份数据) |
| 元数据同步 | 改权限、属主,另一个也变(同一个 inode) |
| 删源文件 | 只是解除“文件名 → inode”的关联,链接数 -1,数据还在,另一个名字照常用 |
| 不能跨分区 | ln: 无法创建硬链接 ... 无效的跨设备连接 |
| 不能给目录做 | 内核禁止(防止目录环) |
ln /1.txt /2.txt
ls -li /1.txt /2.txt # inode 号相同,链接数变成 2
rm /1.txt
cat /2.txt # 依然能读
6.2 软链接 symbolic link
ln -s /1.txt /4.txt
- 本质:新建一个文件(有自己的 inode),它的 data block 里存的是目标文件的路径字符串。
- 特点:
| 特点 | 说明 |
|---|---|
| 内容同步 | 通过软链接读写,实际操作的还是源文件 |
| 元数据不同步 | 改源文件权限,软链接自己的权限不变(不同 inode) |
| 删源文件 | 软链接变成“断链”,访问报 No such file or directory |
| 可以跨分区 | 因为它只存路径字符串 |
| 可以给目录做 | ln -s /data/app /opt/app |
ln -s /1.txt /4.txt
ls -li /1.txt /4.txt # inode 号不同,4.txt 类型是 l
readlink /4.txt # 看它指向谁
6.3 一表看懂
| 对比 | 硬链接 | 软链接 |
|---|---|---|
| 创建命令 | ln 源 目标 |
ln -s 源 目标 |
| inode | 与源文件相同 | 自己独立一个有 inode |
| 文件类型 | 普通文件 - |
链接 l |
| 链接数 | +1 | 源文件链接数不变 |
| 跨分区 | 不行 | 可以 |
| 链接目录 | 不行 | 可以 |
| 删源文件 | 数据保留,另一个名字可用 | 断链,失效 |
| 改元数据 | 互相影响 | 互不影响 |
| 典型用途 | 同一份数据多个入口、防误删 | 版本切换、路径简化(/usr/local/nginx -> /opt/nginx-1.24) |
发布场景:程序解压到
/opt/app-1.24,再建软链接/opt/app -> /opt/app-1.24。升级只改软链接,回滚也只改软链接,服务配置里始终写/opt/app。
速查表
df -T; df -h; df -i
lsblk -f; findmnt; mount | column -t
stat /etc/passwd; ls -i /etc/passwd
ln a b; ln -s a b; ls -li; readlink b
dd if=/dev/zero of=/tmp/big bs=10G count=1
> /path/bigfile # 清空已被进程占用的文件
watch -n 1 "df -i | grep sda1"
常见坑
df -h有空间但写不进去 → 先看df -i,多半是 inode 耗尽。- 删了文件空间没释放 → 进程还开着这个文件,
lsof | grep deleted查,重启进程或清空文件。 - 硬链接跨分区必失败,这不是权限问题。
- 软链接用相对路径创建,链接被移动后会断,建议用绝对路径。
rm删除的是“文件名”,不是 inode;只有链接数归零且没有进程打开,空间才真正释放。- 目录的
x权限决定能否进入,r决定能否列出文件名,w决定能否在里面增删文件。
练习(14 题)
- 文件系统、分区、扇区、block 之间是什么关系?
- superblock、inode、data block 各存什么,数量上有什么区别?
- 一个 10G 的分区,
df -h显示还有 2G,但创建文件报No space left on device,最可能是什么原因?用什么命令确认? - 写出制造 inode 耗尽的测试思路(不要在生产环境执行)。
- 如何动态观察某个分区的 inode 使用率?
- 一个被进程打开着的大日志文件,直接
rm后空间没释放,怎么处理? - 简述
cat /etc/passwd时内核的查找流程。 - 目录的 data block 里存的是什么?
- 创建硬链接和软链接的命令分别是什么?
- 硬链接为什么不能跨分区?软链接为什么可以?
- 删除源文件后,硬链接和软链接分别是什么结果?
- 修改源文件的权限,硬链接和软链接哪个会跟着变?为什么?
- 用
ls -li判断两个文件是否是硬链接,依据是什么? - 发布目录
/opt/app-1.24,如何做到“改软链接就完成升级和回滚”?
答案与解析
1.
硬盘被分成扇区(最小读写单位,通常 512B),分区是把硬盘划分成独立空间,文件系统负责组织分区里的数据,以 block(默认 4K,8 个扇区)为单位分配。
2.
superblock 存整个文件系统的总量/使用/格式,一个文件系统只有一个;inode 存单个文件的元数据和数据块指针,一个文件一个;data block 存内容,一个文件可以有多个。
3.
inode 耗尽。用
df -i看IUse%是否 100%。
4.
在测试分区循环创建大量空文件,例如
for ((i=1;i<=3000000;i++)); do touch $i.txt; done,同时用watch -n 1 "df -i"观察。
5.
watch -n 1 "df -i | grep sda1"
6.
先清空内容释放空间:
> /path/bigfile(或: > bigfile),再删文件;或者重启占用它的进程。用lsof | grep deleted找到进程。
7.
根目录 inode → 根 data block 找
etc的 inode 号 → etc 的 data block 找passwd的 inode 号 → 读 passwd inode 拿到数据块指针 → 读 data block 输出。
8.
存该目录下“子文件名(子目录名)→ inode 号”的映射列表。
9.
ln /1.txt /2.txt # 硬链接
ln -s /1.txt /4.txt # 软链接
10.
硬链接要求同一个 inode,而 inode 号只在单个文件系统内唯一;软链接只是一个存路径的普通文件,路径可以指向任何分区。
11.
硬链接:链接数减 1,数据仍在,另一个名字还能用;软链接:变成断链,访问报 No such file or directory。
12.
硬链接会变,因为两者是同一个 inode;软链接不变,因为它有自己的 inode,只存路径。
13.
ls -li第一列 inode 号相同即为硬链接(且链接数 ≥2)。
14.
ln -s /opt/app-1.24 /opt/app # 首次
ln -sfn /opt/app-1.25 /opt/app # 升级
ln -sfn /opt/app-1.24 /opt/app # 回滚
-f覆盖已有链接,-n防止把链接建到目录里面;服务配置始终指向/opt/app。