Linux 挂载机制 — mount、VFS 与 fstab / Linux Mounting with Mount, VFS, and Fstab
📅 创建时间:2026-06-17 🏷️ 标签:#Linux #mount #VFS #fstab #挂载 📚 前置知识:[[04-filesystem-concepts]], [[08-linux-fhs]]
📋 本章目标
- 理解
mount系统调用的本质——把块设备"插"到目录树上 - 掌握 VFS(Virtual File System)——Linux 如何用统一的接口支持几十种文件系统
- 理解
/etc/fstab每一列的含义和常见错误 - 理解 UUID 为什么替代
/dev/sda1——设备名不是稳定的 - 掌握 bind mount、tmpfs、挂载选项的实战用法
- 理解
mountumountfindmntlsblk之间的关系
第1部分:mount 本质——把设备插到树上
1.1 mount 做什么
┌─────────────────────────────────────────────────────────────┐
│ mount 操作的直观理解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 初始的根文件系统 (initramfs 或 光盘启动后): │
│ │
│ / │
│ ├── bin/ │
│ ├── etc/ │
│ └── mnt/ ← 空目录, 等待用作"挂钩点" │
│ │
│ 执行 mount /dev/nvme0n1p3 /mnt: │
│ │
│ / │
│ ├── bin/ │
│ ├── etc/ │
│ └── mnt/ ← 挂载点 (mount point) │
│ ├── boot/ ← 来自 nvme0n1p3 的 boot 目录 │
│ ├── home/ ← 来自 nvme0n1p3 的 home 目录 │
│ ├── etc/ ← 来自 nvme0n1p3 的 etc 目录 │
│ └── ... │
│ │
│ 关键: nvme0n1p3 上的文件系统被"嫁接"到了 /mnt 位置 │
│ 原有的 /mnt 下如果有什么内容, 会被暂时"遮盖" │
│ umount 后又能看到原来的内容 │
│ │
│ 这就是 Arch 安装时的操作: │
│ mount /dev/nvme0n1p3 /mnt ← 挂载根分区 │
│ mount /dev/nvme0n1p1 /mnt/boot ← 挂载 ESP 到 /boot │
│ │
│ 第二个 mount 在已挂载的树里再加一层: │
│ /mnt/ │
│ └── boot/ ← 现在指向 nvme0n1p1 (ESP, FAT32) │
│ ├── vmlinuz-linux │
│ ├── initramfs-linux.img │
│ └── EFI/ │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1.2 挂载前必须做的事
┌─────────────────────────────────────────────────────────────┐
│ Arch 安装时的 mount 序列 │
├─────────────────────────────────────────────────────────────┤
│ │
│ # 1. 挂载根分区 │
│ mount /dev/nvme0n1p3 /mnt │
│ │
│ # 2. 创建挂载点 (必须在已有树内) │
│ mkdir -p /mnt/boot │
│ mkdir -p /mnt/home │
│ │
│ # 3. 挂载其他分区 │
│ mount /dev/nvme0n1p1 /mnt/boot │
│ │
│ # 4. (如果是 btrfs) 挂载 subvolume │
│ mount -o subvol=@home /dev/nvme0n1p3 /mnt/home │
│ │
│ 顺序很重要: 必须先挂 /, 才能在 / 的子目录上挂别的东西 │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
第2部分:VFS — Linux 的文件系统抽象层
2.1 VFS 是什么
┌─────────────────────────────────────────────────────────────┐
│ VFS (Virtual File System) — 统一的翻译层 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题: Linux 要支持 ext4, btrfs, FAT32, NTFS, XFS, NFS... │
│ 几十种文件系统, 每种内部实现完全不同 │
│ 但应用程序不应该关心底层是什么文件系统 │
│ │
│ 解法: VFS 层 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 应用程序 (cat, ls, cp, mv...) │ │
│ │ ↓ 系统调用 (open, read, write, stat...) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ VFS 层 │ │ │
│ │ │ │ │ │
│ │ │ 统一的: inode, dentry, file, super_block │ │ │
│ │ │ (在内存中的通用数据结构) │ │ │
│ │ │ │ │ │
│ │ │ 每个文件系统注册自己的函数指针表: │ │ │
│ │ │ → .read_inode, .write_inode, .create... │ │ │
│ │ └──────────┬──────────────────────────────────┘ │ │
│ │ │ 根据 super_block → 分发到具体驱动 │ │
│ │ ┌───────┼───────┬──────────┬──────────┐ │ │
│ │ ↓ ↓ ↓ ↓ ↓ │ │
│ │ ext4 btrfs FAT32 NTFS NFS │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 关键概念: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ super_block → 对应一个"挂载了的文件系统" │ │
│ │ 从磁盘超级块读取, 存块大小、inode 总数│ │
│ │ │ │
│ │ inode → 对应一个"文件"或"目录" │ │
│ │ VFS 维护的是 VFS inode (内存) │ │
│ │ 文件系统驱动负责: 磁盘 inode ↔ VFS inode│ │
│ │ │ │
│ │ dentry → 对应一个"目录项" │ │
│ │ 存文件名 → VFS inode 的映射 │ │
│ │ VFS 维护 dentry cache (快速路径查找) │ │
│ │ │ │
│ │ file → 对应一个"打开的文件" │ │
│ │ 存偏移量、访问模式 │ │
│ │ 多个 file 可以指向同一个 inode │ │
│ │ (多个进程打开同一个文件) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
第3部分:/etc/fstab — 开机自动挂载
3.1 fstab 的六列
┌─────────────────────────────────────────────────────────────┐
│ /etc/fstab 六列详解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ # <文件系统> <挂载点> <类型> <选项> <dump> <pass> │
│ │
│ 例: │
│ UUID=xxx-xxx / btrfs defaults,compress=zstd 0 0 │
│ UUID=yyy-yyy /boot vfat defaults,noatime 0 2 │
│ UUID=zzz-zzz /home btrfs defaults,subvol=@home 0 0 │
│ │
│ 第1列: 文件系统 (Filesystem) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 三种写法: │ │
│ │ │ │
│ │ UUID=xxx-xxx-xxx ← 推荐! 稳定, 不随插槽改变 │ │
│ │ PARTUUID=xxx-xxx ← GPT 分区自身的 UUID │ │
│ │ /dev/nvme0n1p3 ← 不推荐! 内核设备名可能变化 │ │
│ │ LABEL=mylabel ← 可读, 但不如 UUID 唯一 │ │
│ │ │ │
│ │ 查看 UUID: │ │
│ │ blkid /dev/nvme0n1p3 │ │
│ │ ls -la /dev/disk/by-uuid/ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第2列: 挂载点 (Mount Point) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 必须是已存在的目录 │ │
│ │ swap 分区用 "none" 或 "swap" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第3列: 文件系统类型 (Type) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ext4, btrfs, vfat, ntfs3, xfs, swap, tmpfs, ... │ │
│ │ auto: 让内核自动检测 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第4列: 挂载选项 (Options) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ defaults = rw,suid,dev,exec,auto,nouser,async │ │
│ │ │ │
│ │ 常用选项: │ │
│ │ noatime 不更新访问时间 → 减少 SSD 写入 │ │
│ │ compress=zstd btrfs 透明压缩 │ │
│ │ subvol=@ btrfs 子卷选择 │ │
│ │ discard=async SSD TRIM (4.19+ 建议用 async) │ │
│ │ nofail 设备不存在也不阻止启动 │ │
│ │ _netdev 网络设备, 等网络就绪后再挂载 │ │
│ │ ro 只读挂载 │ │
│ │ noexec 禁止执行此文件系统上的程序 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第5列: dump (备份) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 0 = 不备份 (常用) │ │
│ │ 1 = 备份 (dump 命令使用, 基本没人用了) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第6列: fsck 检查顺序 (pass) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 0 = 不检查 │ │
│ │ 1 = 最先检查 (只能给 / 根文件系统) │ │
│ │ 2 = 其余依次检查 (可并行) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
3.2 为什么用 UUID 而不是 /dev/sda1
┌─────────────────────────────────────────────────────────────┐
│ 设备名不稳定的经典案例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景: │
│ 你的 fstab 里写着: │
│ /dev/sda1 /boot vfat defaults 0 2 │
│ │
│ 某天你插了一块 U 盘 → 内核把它分配为 /dev/sda │
│ → 原 /dev/sda 变成了 /dev/sdb │
│ → fstab 里的 /dev/sda1 现在指向了 U 盘! │
│ → 启动失败 (找不到 /boot) │
│ │
│ 用 UUID: │
│ UUID=ABCD-1234 /boot vfat defaults 0 2 │
│ → 不管设备名怎么变, UUID 不变 │
│ → 总是能正确挂载 │
│ │
│ 查看 UUID: │
│ $ blkid │
│ /dev/nvme0n1p1: UUID="ABCD-1234" TYPE="vfat" │
│ │
│ 内核在 /dev/disk/by-uuid/ 下创建了快捷方式: │
│ $ ls -la /dev/disk/by-uuid/ │
│ ABCD-1234 -> ../../nvme0n1p1 │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第4部分:特殊挂载类型
4.1 bind mount — 把目录挂到另一个位置
┌─────────────────────────────────────────────────────────────┐
│ bind mount 的应用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ bind mount = 把目录树的某个节点"镜像"到另一个位置 │
│ │
│ # 让 /mnt 也能访问 /home │
│ mount --bind /home /mnt/home-ext │
│ │
│ # 实际上: /home 和 /mnt/home-ext 是同一个 inode 树 │
│ # 修改任何一个都影响同一个数据 │
│ │
│ 关键区别: bind mount vs 符号链接 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 符号链接: ls -la /link → /target → 是一个路径快捷方式 │ │
│ │ bind mount: 文件系统级别的镜像, 和原始位置完全等价 │ │
│ │ │ │
│ │ 符号链接的问题: │ │
│ │ chroot 环境里 /link 指到 /target → 但 /target 不存在!│ │
│ │ bind mount 不受 chroot 影响 (因为是独立挂载) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Arch 安装时的重要用途: │
│ # mount --bind /dev /mnt/dev ← 设备节点 │
│ # mount --bind /proc /mnt/proc ← 进程信息 │
│ # mount --bind /sys /mnt/sys ← 内核接口 │
│ │
│ 然后 arch-chroot /mnt → chroot 后的环境有完整的 /dev/proc/sys│
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
4.2 tmpfs — 内存里的文件系统
┌─────────────────────────────────────────────────────────────┐
│ tmpfs — 重启就消失的文件系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ tmpfs 把数据存在 RAM + swap 里, 而不是磁盘上. │
│ │
│ mount -t tmpfs tmpfs /tmp -o size=4G │
│ │
│ 系统默认的 tmpfs 挂载 (/proc/mounts 可见): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ tmpfs on /dev/shm 共享内存 │ │
│ │ tmpfs on /run 运行时数据 (PID/锁/套接字) │ │
│ │ tmpfs on /tmp 临时文件 (如果配置了的话) │ │
│ │ tmpfs on /sys/fs/cgroup cgroup 控制组接口 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ tmpfs 的关键属性: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 大小可伸缩 — 实际使用多少才占多少内存 │ │
│ │ • 重启消失 — 适合不需要持久化的临时数据 │ │
│ │ • 极快 — 没有磁盘 IO │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 与之对比: ramfs — 只能用物理 RAM, 不能 swap │
│ tmpfs → 可以用 swap → 更灵活 │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第5部分:挂载选项实战
5.1 btrfs 特有的挂载选项
# 你 Arch 上实际使用的挂载选项:
# /etc/fstab 里的典型 btrfs 根分区:
UUID=xxx / btrfs rw,noatime,compress=zstd,ssd,discard=async,subvol=@ 0 0
# 各选项含义:
# rw → 读写模式
# noatime → 不更新访问时间 (减少 SSD 写) → 省 IO
# compress=zstd → zstd 压缩 (btrfs 特有) → 省空间
# ssd → SSD 优化 (btrfs 自动检测, 但可手动指定)
# discard=async → 异步 TRIM (btrfs 特有) → 省 IO
# subvol=@ → 挂载名为 @ 的子卷 → btrfs 特有2
3
4
5
6
7
8
9
10
11
5.2 常用通用挂载选项
┌─────────────────────────────────────────────────────────────┐
│ 常用挂载选项速查 │
├─────────────────────────────────────────────────────────────┤
│ │
│ async/sync 异步/同步写入 (sync 极慢, 一般不用) │
│ atime/noatime 更新/不更新访问时间 (SSD 建议 noatime) │
│ relatime 只在 mtime/ctime 更新时更新 atime (折中) │
│ exec/noexec 允许/禁止执行可执行文件 │
│ suid/nosuid 允许/禁止 SUID 位生效 │
│ dev/nodev 允许/禁止设备节点 │
│ rw/ro 读写/只读 │
│ user/nouser 允许/禁止普通用户挂载 │
│ auto/noauto 启动时自动挂载/不自动挂载 │
│ nofail 设备不存在不报错 (关键选项, 见下文) │
│ │
│ nofail 的使用场景: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 你有一块外部硬盘, 有时插着有时不插 │ │
│ │ 如果 fstab 里有它, 启动时会因为找不到设备而进 emergency│ │
│ │ 加了 nofail → 找不到就跳过 → 正常启动 │ │
│ │ 这对可移动设备和网络挂载 (NFS) 尤其重要 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
核心总结
总结1:mount 的本质
mount <设备> <挂载点> = 把<设备>上的文件系统"嫁接"到<挂载点>
umount <挂载点> = 切断这个嫁接, 恢复挂载点原来的内容2
总结2:VFS 的价值
应用不需要知道文件系统类型 → VFS 提供统一接口
所有文件系统驱动都实现同一套函数指针 → 一个 open() 系统调用就够了2
总结3:fstab 核心要素
6 列: 设备(UUID) → 挂载点 → 类型 → 选项 → dump → fsck序
UUID 替代 /dev/sda1 → 设备名变也不怕
nofail → 可选设备不要阻止启动2
3
章节测试
测试1:mount 遮盖
如果你 /mnt 下有一个文件 hello.txt,然后 mount /dev/sdb1 /mnt。hello.txt 还在吗?umount /mnt 之后呢?
测试2:VFS
VFS 层解决的核心问题是什么?
测试3:UUID 必要性
为什么 fstab 用 UUID 而不用 /dev/nvme0n1p3?
测试4:fstab 格式
UUID=xxx /home btrfs defaults,subvol=@home,nofail 0 0 中 nofail 的作用是什么?
参考答案
测试1答案
mount /dev/sdb1 /mnt 后,原来的 hello.txt 被"遮盖"了——它物理上还在磁盘上,但因为挂载点现在指向了 sdb1,访问 /mnt/hello.txt 会去 sdb1 上找。umount /mnt 后,原来的内容重见天日——hello.txt 完好无损。这就是 mount 的"遮盖"特性。
测试2答案
VFS 解决了文件系统类型多样性和应用程序通用性之间的矛盾。Linux 支持几十种文件系统,但每个应用程序不可能针对每种文件系统写不同的代码。VFS 层定义了一套统一的抽象(super_block, inode, dentry, file)和操作接口(函数指针表),每个文件系统驱动只需实现这些接口,上层所有应用自动支持该文件系统。
测试3答案
因为内核设备名(如 /dev/nvme0n1p3)不是稳定的。添加/移除磁盘、改变 SATA 端口、甚至只是内核探测顺序的变化,都可能导致设备名改变。UUID 存储在文件系统的超级块里,除非重新格式化,否则永远不会变。所以用 UUID 保证 fstab 在所有条件下都能正确挂载。
测试4答案
nofail 告诉 systemd:如果这个设备不存在(比如外部硬盘没插),不要报错,不要进入 emergency 模式,直接跳过,继续正常启动。没有 nofail 的设备缺失会导致启动失败。
相关笔记
- [[08-linux-fhs]] - fstab 挂载的目标——目录树
- [[10-linux-disk-practice]] - 从分区到 fstab 编辑的完整流程
- [[04-filesystem-concepts]] - VFS 抽象了什么——inode/超级块/dentry
下一步学习
- [ ] 阅读 10 - Linux 磁盘管理实战
学习状态:🟡 开始学习