Linux 文件系统
一切皆文件
Linux 最核心的设计哲学之一是 "一切皆文件"(Everything is a file)。不仅普通文档是文件,目录、设备、管道、Socket 都被抽象为文件,统一通过 open() / read() / write() / close() 系统调用操作。
| 文件类型 | 符号 | 示例 |
|---|---|---|
| 普通文件 | - |
/etc/passwd |
| 目录 | d |
/home/user/ |
| 符号链接 | l |
/usr/bin/python -> python3 |
| 块设备 | b |
/dev/sda(磁盘) |
| 字符设备 | c |
/dev/tty(终端) |
| 管道 | p |
命名管道 |
| Socket | s |
/var/run/mysql.sock |
这种抽象的好处是 统一接口:程序不需要知道自己在操作的是磁盘文件还是网络 Socket,只需要用同一套系统调用即可。
inode:文件的身份证
在 Linux 文件系统中,文件名只是一个标签,文件的真正身份是 inode(索引节点)。
目录项 (dentry) inode 数据块 (data blocks)
┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 文件名: foo.txt│──────▶│ inode 编号: 42│──────▶│ 实际文件内容 │
│ inode: 42 │ │ 文件大小: 1024│ │ Hello World... │
└──────────────┘ │ 权限: 644 │ └─────────────────┘
│ 所有者: root │ ┌─────────────────┐
│ 时间戳 │──────▶│ 更多数据块... │
│ 数据块指针 │ └─────────────────┘
│ 链接计数: 1 │
└──────────────┘
inode 存储的信息
| 信息 | 说明 |
|---|---|
| 文件类型 | 普通文件、目录、链接等 |
| 权限 | 读/写/执行权限(9 位) |
| 所有者 / 所属组 | UID 和 GID |
| 文件大小 | 字节数 |
| 时间戳 | atime(访问)、mtime(修改)、ctime(元数据变更) |
| 硬链接计数 | 指向此 inode 的目录项数量 |
| 数据块指针 | 指向实际存储数据的磁盘块 |
关键理解:inode 不存储文件名。文件名存储在目录项(dentry)中,目录项是"文件名 → inode 编号"的映射关系。
数据块指针结构
对于大文件,inode 使用多级间接指针来寻址数据块:
inode
├── 直接指针 ×12 → 直接指向数据块(最多 48KB)
├── 一级间接指针 ×1 → 指向一个指针块 → 数据块
├── 二级间接指针 ×1 → 指针块 → 指针块 → 数据块
└── 三级间接指针 ×1 → 指针块 → 指针块 → 指针块 → 数据块
假设块大小 4KB,指针 4B:
- 直接: 12 × 4KB = 48KB
- 一级: 1024 × 4KB = 4MB
- 二级: 1024² × 4KB = 4GB
- 三级: 1024³ × 4KB = 4TB
硬链接与软链接
硬链接(Hard Link)
硬链接是多个目录项指向同一个 inode。就像一个人有多个名字(身份证号只有一个)。
目录项 A inode 42
┌──────────┐ ┌──────────────┐
│ foo.txt │──────▶│ 链接计数: 2 │──────▶ 数据块
└──────────┘ └──────────────┘
▲
目录项 B │
┌──────────┐ │
│ bar.txt │───────┘
└──────────┘
删除 foo.txt → 链接计数变为 1 → 数据不删除
删除 bar.txt → 链接计数变为 0 → 数据被释放
# 创建硬链接
ln foo.txt bar.txt
# 验证:两者的 inode 编号相同
ls -li foo.txt bar.txt
# 42 -rw-r--r-- 2 user group 1024 foo.txt
# 42 -rw-r--r-- 2 user group 1024 bar.txt
软链接(符号链接 / Symbolic Link)
软链接是一个独立的文件,其内容是目标文件的路径字符串。类似 Windows 的快捷方式。
目录项 inode 100 数据块
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ link.txt │──────▶│ 类型: 符号链接│───▶│ "/path/foo" │
└──────────┘ └──────────────┘ └──────────────┘
│
▼ 访问时解析路径
inode 42
┌──────────────┐
│ 实际文件数据 │
└──────────────┘
# 创建软链接
ln -s foo.txt link.txt
# 验证:两者的 inode 编号不同
ls -li foo.txt link.txt
# 42 -rw-r--r-- 1 user group 1024 foo.txt
# 100 lrwxrwxrwx 1 user group 7 link.txt -> foo.txt
硬链接 vs 软链接
| 对比维度 | 硬链接 | 软链接 |
|---|---|---|
| 本质 | 同一 inode 的多个目录项 | 独立文件,存储目标路径 |
| inode | 相同 | 不同 |
| 删除原文件 | 数据仍在(链接计数 > 0) | 链接失效(悬挂链接) |
| 跨文件系统 | ❌ 不可以 | ✅ 可以 |
| 链接目录 | ❌ 不可以(防止循环) | ✅ 可以 |
| 文件大小 | 与原文件相同 | 等于路径字符串长度 |
文件权限模型
Linux 文件权限分为三组,每组三个位:
所有者(u) 所属组(g) 其他人(o)
rwx rwx rwx
421 421 421
示例: -rwxr-xr-- = 754
│ │││ │││ │││
│ │││ │││ └── 其他人: r-- = 4 (只读)
│ │││ └── 所属组: r-x = 5 (读+执行)
│ └── 所有者: rwx = 7 (读+写+执行)
└── 文件类型: - (普通文件)
权限含义
| 权限 | 对文件 | 对目录 |
|---|---|---|
r (读) |
可读取文件内容 | 可列出目录内容 (ls) |
w (写) |
可修改文件内容 | 可在目录中创建/删除文件 |
x (执行) |
可作为程序执行 | 可进入目录 (cd) |
特殊权限位
| 权限 | 符号 | 数字 | 作用 |
|---|---|---|---|
| SetUID | s(所有者 x 位) |
4000 | 执行时临时获得文件所有者的权限 |
| SetGID | s(组 x 位) |
2000 | 执行时临时获得文件所属组的权限 |
| Sticky | t(其他人 x 位) |
1000 | 目录中的文件只能被所有者删除 |
# SetUID 示例: passwd 命令需要修改 /etc/shadow(root 才能写)
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 passwd
# ^-- SetUID: 普通用户执行时临时获得 root 权限
# Sticky Bit 示例: /tmp 目录
ls -ld /tmp
# drwxrwxrwt 10 root root 4096 /tmp
# ^-- Sticky: 所有人都能创建文件,但只能删除自己的
常用权限命令
# 修改权限(数字方式)
chmod 755 script.sh # rwxr-xr-x
# 修改权限(符号方式)
chmod u+x script.sh # 给所有者加执行权限
chmod go-w file.txt # 去掉组和其他人的写权限
chmod a+r file.txt # 给所有人加读权限
# 修改所有者
chown user:group file.txt
# 查看默认权限掩码
umask # 通常是 022
# 新文件权限 = 666 - 022 = 644 (rw-r--r--)
# 新目录权限 = 777 - 022 = 755 (rwxr-xr-x)
ext4 文件系统
ext4(Fourth Extended Filesystem)是 Linux 最常用的文件系统。它在 ext3 基础上做了大量改进:
| 特性 | ext3 | ext4 |
|---|---|---|
| 最大文件 | 2TB | 16TB |
| 最大分区 | 16TB | 1EB |
| 子目录数 | 32,000 | 无限制 |
| 块分配 | 直接/间接块 | Extent(连续区间) |
| 日志校验 | 无 | 有 |
| 延迟分配 | 不支持 | 支持 |
Extent 机制
ext4 引入了 Extent 取代传统的间接块映射。Extent 记录的是连续的磁盘块区间:
传统方式(ext3): 逐个记录每个块号
inode → [块1] [块2] [块3] [块4] [块5] ...
一个 1GB 文件需要约 26 万个块指针
Extent 方式(ext4): 记录起始块 + 长度
inode → [起始块=100, 长度=256000]
一个 1GB 的连续文件只需要 1 个 Extent
这大大减少了元数据开销,也提高了大文件的读写性能。
生产高频题
inode 是什么?一个文件可以有多个文件名吗?
inode 是文件系统中存储文件元数据的数据结构,包含权限、大小、时间戳、数据块指针等,但不包含文件名。一个文件可以有多个文件名——通过硬链接实现,多个目录项指向同一个 inode。
软链接和硬链接的区别?
硬链接是同一个 inode 的多个目录项,删除原文件不影响硬链接,但不能跨文件系统、不能链接目录。软链接是独立的文件(有自己的 inode),存储的是目标路径,删除原文件后软链接悬挂,但可以跨文件系统和链接目录。
为什么删除文件后磁盘空间没有释放?
可能还有进程持有该文件的文件描述符(fd)。Linux 中文件的实际删除发生在 inode 的链接计数降为 0 且没有进程打开该文件 时。使用 lsof | grep deleted 可以查找这类文件。