本文要点
- fsck 检查的是「账本」不是「货」:它核对的是超级块、位图、inode 表这些元数据之间是否自洽,跟你文件内容有没有被改坏毫无关系——那是
sha256sum的活 - Ubuntu 上
fsck只是个「调度壳」(43KB 的 util-linux 二进制),/sbin/fsck.ext4其实是指向e2fsck的符号链接,真正干活的是 e2fsck dumpe2fs会把每个块组的家底摊开:主/备份超级块、块位图、inode 位图、inode 表各自的块号一目了然;Group 1 那行会直接标出Backup superblock at 8192- ⚠️ 主超级块在文件系统内的偏移 1024 字节处,而备份超级块位于所在块的块首偏移 0——所以把备份超级块
dd出来喂给file,它会认成一个不相干的东西,这不代表备份坏了 - 实测踩坑:默认参数格式化的 100MB 镜像只有 1 个块组,按
sparse_super约定根本没有备份超级块,主超级块一坏就是彻底救不回来;加-g 8192切成 4 个块组后才有8192这个救命备份 - e2fsck 报错时提示的
-b 8193不能照抄——那是按 1K 块大小算的默认值,4K 块的文件系统要按自己的块号换算 e2fsck -fn只读体检、-fy才动手;tune2fs -c 30控制开机自动 fsck 的频率,badblocks -sv则负责硬件层面的坏道扫描
Linux 文件系统自检:从 e2fsck 只读体检到备份超级块抢救
服务器掉电重启后卡在 unexpected inconsistency; RUN fsck MANUALLY,或者挂载时直接甩一句 wrong fs type, bad option, bad superblock——这类故障的排查起点,几乎都是 fsck。
但 fsck 能干什么、不能干什么,很多人是模糊的。它不检查你的文件内容有没有被篡改(那是 Linux 文件完整性校验:从 sha256sum 到 dpkg -V 篡改排查 的领域),它检查的是文件系统自己的账本有没有对不上:超级块说的空闲块数和位图记的对不对得上、inode 表里有没有孤儿、目录项指向的 inode 还在不在。
这篇文章在一台 Ubuntu 22.04 的云服务器(KVM 虚拟机,1 核、内存 1.9G、内核 5.15.0-30-generic、e2fsprogs 1.46.5)上,用一块 100MB 的实验盘把整条链路走一遍:看布局 → 只读体检 → 亲手把主超级块抹掉 → 用备份超级块救回来。中途会撞上一个真实的坑:默认参数下这块盘其实没有备份超级块可用。
工具分工:谁是谁的壳
动手前先把这一家子认清楚,fsck 家族的成员经常被混为一谈:
ls -l /sbin/fsck /sbin/fsck.ext4; file /sbin/fsck.ext4-rwxr-xr-x 1 root root 43440 Aug 19 17:43 /sbin/fsck
lrwxrwxrwx 1 root root 6 Jun 1 2022 /sbin/fsck.ext4 -> e2fsck
/sbin/fsck.ext4: symbolic link to e2fsckfsck 本身只是个 43KB 的调度壳:它读 /etc/fstab、判断设备上是什么文件系统,再把活派给对应的 fsck.<类型>。而 fsck.ext4 根本不是独立程序,就是一个指向 e2fsck 的符号链接。所以对 ext4 来说,fsck /dev/vda1 和 e2fsck /dev/vda1 最终是同一个程序在跑。
| 命令 | 职责 |
|---|---|
fsck | 调度壳,按文件系统类型分发;批量检查 fstab 里的设备 |
e2fsck | ext2/3/4 的真正检查与修复程序 |
dumpe2fs | 只读,打印超级块与每个块组的详细布局 |
tune2fs | 改超级块里的可调参数(挂载计数、自检周期) |
debugfs | ext 系列的「调试器」,能直接翻 inode 和块 |
badblocks | 硬件层坏道扫描,与文件系统结构无关 |
mke2fs / mkfs.ext4 | 格式化(mkfs.ext4 是 mke2fs 的软链) |
造一块可以随便糟蹋的实验盘
不要拿根分区练手。 已挂载的 ext4 文件系统做写修复,轻则 e2fsck 拒绝执行、重则直接把运行中的系统写崩。正确做法是造一个镜像文件,用 loop 设备挂载——坏了大不了 rm:
mkdir -p /tmp/fsck-demo && cd /tmp/fsck-demo
dd if=/dev/zero of=disk.img bs=1M count=100 status=none
mkfs.ext4 -F -g 8192 disk.img
mkdir -p mnt && mount -o loop disk.img mnt
for i in 1 2 3; do echo "demo $i" > mnt/file$i.txt; done
sync && umount mnt这里 dd 只是造一个 100MB 的全零文件(和 dd 命令:从磁盘克隆到数据备份的完整指南 里造文件的用法一致),mkfs.ext4 -F 的 -F 表示「这是普通文件不是块设备,别拦我」。
关键参数是 -g 8192——它把每个块组的容量钉死在 8192 个块。默认情况下 4K 块大小的 ext4 一个块组能装 32768 个块,100MB 镜像总共才 25600 个块,会被塞进唯一的一个块组里。这个差别在第六节会变成一个生死攸关的问题,先记住这个参数。
看懂布局:超级块、位图、inode 表都在哪
ext4 把整个分区切成若干个块组(block group),每个块组自带一套元数据。用 dumpe2fs 可以直接看:

cd /tmp/fsck-demo && dumpe2fs disk.img 2>/dev/null | grep -E "^Group [0-9]+:|Inode table at"; e2fsck -fn disk.img 2>&1Group 0: (Blocks 0-8191) csum 0xa38d [ITABLE_ZEROED]
Inode table at 59-458 (+59)
Group 1: (Blocks 8192-16383) csum 0x0699 [INODE_UNINIT, ITABLE_ZEROED]
Inode table at 459-858 (bg #0 + 459)
Group 2: (Blocks 16384-24575) csum 0x0c3e [INODE_UNINIT, ITABLE_ZEROED]
Inode table at 859-1258 (bg #0 + 859)
Group 3: (Blocks 24576-25599) csum 0xf407 [INODE_UNINIT, ITABLE_ZEROED]
Inode table at 1259-1658 (bg #0 + 1259)
e2fsck 1.46.5 (30-Dec-2021)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
disk.img: 14/25600 files (0.0% non-contiguous), 2794/25600 blocks-g 8192 生效了,现在是 0、1、2、3 四个块组。输出末尾那句 14/25600 files … 2794/25600 blocks 是健康文件的标志:14 个 inode 被占用(3 个文件 + 一堆系统保留 inode),没有非连续碎片。
把 Group 0 单独摊开看,块组内部的排布就清楚了:
dumpe2fs disk.img 2>/dev/null | sed -n '/^Group 0:/,/^Group 1:/p'Group 0: (Blocks 0-8191) csum 0xa38d [ITABLE_ZEROED]
Primary superblock at 0, Group descriptors at 1-1
Reserved GDT blocks at 2-50
Block bitmap at 51 (+51), csum 0xfbc6cca7
Inode bitmap at 55 (+55), csum 0xd0a81914
Inode table at 59-458 (+59)
6527 free blocks, 6386 free inodes, 2 directories, 6386 unused inodes
Free blocks: 1665-8191
Free inodes: 15-6400七个部分各司其职:
- Primary superblock(块 0):文件系统的总账——块大小、总块数、inode 总数、UUID、特性标志全在这。它是唯一一份,坏了整个文件系统就「不认识自己」了。
- Group descriptors(块 1):每个块组一行摘要,记录该组的位图在哪、inode 表在哪、还剩多少空闲。
- Reserved GDT blocks(块 2-50):给「将来扩容导致块组变多」预留的位置。
- Block bitmap(块 51):一位一个数据块,标记这块被占了还是空着。
- Inode bitmap(块 55):同样是一位一个 inode。
- Inode table(块 59-458):真正的 inode 数组——文件权限、大小、时间戳、数据块指针都在这。
- 最后两行是该组的空闲统计与空闲清单。
注意 Group 1 的起始块号是 8192。先记住这个数字。
只读体检:-n 和 -y 差的是「动不动手」
e2fsck 最要紧的两个开关是 -n 和 -y:
-n:只读模式。发现问题只报告,一个字都不改,退出码非 0。-y:对所有提问自动回答 yes,真的动手改。- 还有个
-p(preen):只自动修「明显安全」的问题,稍微复杂点就退出让你人工介入——开机时内核调用的就是这个模式。
上手先用 -n。上面那张截图的后半段就是干净盘的体检结果,五个 Pass 一次通过:
| Pass | 检查内容 |
|---|---|
| Pass 1 | inode 与数据块的使用情况(最耗时的一步) |
| Pass 2 | 目录结构(目录项是否合法、.. 指向对不对) |
| Pass 3 | 目录连通性(目录是否可达、有没有孤立目录) |
| Pass 4 | 引用计数(有多少目录项指向同一个 inode) |
| Pass 5 | 块组摘要信息(超级块里的统计与位图是否吻合) |
弄坏它:把主超级块抹掉
现在制造一次故障。主超级块位于文件系统内偏移 1024 字节处,dd 从这个偏移写 1KB 零进去就够了:

cd /tmp/fsck-demo && dd if=/dev/zero of=disk.img bs=1k seek=1 count=1 conv=notrunc 2>&1; e2fsck -fn disk.img 2>&11+0 records in
1+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.000172109 s, 5.9 MB/s
e2fsck 1.46.5 (30-Dec-2021)
ext2fs_open2: Bad magic number in super-block
e2fsck: Superblock invalid, trying backup blocks...
e2fsck: Bad magic number in super-block while trying to open disk.img
The superblock could not be read or does not describe a valid ext2/ext3/ext4
filesystem. If the device is valid and it really contains an ext2/ext3/ext4
filesystem (and not swap or ufs or something else), then the superblock
is corrupt, and you might try running e2fsck with an alternate superblock:
e2fsck -b 8193 <device>
or
e2fsck -b 32768 <device>几个要点:
bs=1k seek=1的意思是「跳过 1 个 1KB 的块,从第 1024 字节开始写」——正好覆盖超级块。conv=notrunc保证不会把文件截断成 1KB。- 报错是
Bad magic number in super-block:ext4 的超级块固定魔数是0xEF53(小端存储就是53 ef),被清零后自然对不上。 -b 8193这个提示不能照抄。 它是 e2fsprogs 按 1K 块大小的教科书场景给的默认值;我们这块盘是 4K 块,8193 这个块号压根不落在任何备份位置上。- 顺便一提,e2fsck 还试过自动找备份(
trying backup blocks...),但也失败了——原因见下一节。
抢救:找到真正的备份超级块
备份超级块的位置不是猜的,dumpe2fs 早就写在那了——回到上一节 Group 1 的输出:
dumpe2fs disk.img 2>/dev/null | sed -n '/^Group 1:/,/^Group 2:/p'Group 1: (Blocks 8192-16383) csum 0x0699 [INODE_UNINIT, ITABLE_ZEROED]
Backup superblock at 8192, Group descriptors at 8193-8193
Reserved GDT blocks at 8194-8242
...Backup superblock at 8192——就是 Group 1 的起始块号。ext4 靠 sparse_super 特性决定哪些块组留备份,规则是第 0 个块组放主超级块,第 1、3、5、7、9、25、27、49… 个块组放备份(3 的幂、5 的幂、7 的幂)。四个块组里,只有 Group 1 和 Group 3 有备份。
拿这个块号去恢复,-B 4096 显式告诉它块大小(超级块坏了就没人告诉它块多大了,必须手工指定):

cd /tmp/fsck-demo && e2fsck -b 8192 -B 4096 -fy disk.img 2>&1; e2fsck -fn disk.img 2>&1; mount -o loop disk.img mnt && ls mnt && umount mnte2fsck 1.46.5 (30-Dec-2021)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
Free blocks count wrong for group #1 (8141, counted=8138).
Fix? yes
Free blocks count wrong (22809, counted=22806).
Fix? yes
Free inodes count wrong for group #0 (6389, counted=6386).
Fix? yes
Free inodes count wrong (25589, counted=25586).
Fix? yes
Padding at end of inode bitmap is not set. Fix? yes
Block bitmap differences: Group 1 block bitmap does not match checksum.
FIXED.
disk.img: ***** FILE SYSTEM WAS MODIFIED *****
disk.img: 14/25600 files (0.0% non-contiguous), 2794/25600 blocks
e2fsck 1.46.5 (30-Dec-2021)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
disk.img: 14/25600 files (0.0% non-contiguous), 2794/25600 blocks
file1.txt
file2.txt
file3.txt
lost+found修复过程做了两件事:一是把备份超级块的内容写回主超级块位置(所以后面的 e2fsck -fn 不再报 Bad magic number),二是顺手修正了备份超级块记的账和当前位图之间的偏差(那几个 Free blocks count wrong 就是备份超级块停留在旧快照上的证据——备份超级块不是实时同步的,它只在特定时机被更新)。
最后 mount 成功、三个文件原样躺在里面,说明只要超级块能救回来,数据一个都没丢。
那个没救回来的版本:为什么 -g 8192 是必须的
在加上 -g 8192 之前,我用的就是最朴素的 mkfs.ext4 -F disk.img。结果那块盘被我同样抹掉主超级块后,是彻底救不回来的:
e2fsck: Bad magic number in super-block while trying to open disk.img
The superblock could not be read or does not describe a valid ext2/ext3/ext4
filesystem.
...
e2fsck -b 8193 <device>按提示试 -b 8193 是同样的报错,mount 则直接 wrong fs type, bad option, bad superblock。原因就是第一节埋的伏笔:默认参数下这块 100MB 的盘只有一个块组(0-25599),而备份超级块按约定只存在于第 1、3、5、7… 个块组里——没有第 1 个块组,就等于一份备份都没有。e2fsck 说 trying backup blocks... 之后放弃,是真的找不到。
这也解释了一个常见困惑:mke2fs -n 本该列出备份超级块位置,在这块盘上却一声不吭——
mke2fs -n -F disk.imgmke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 25600 4k blocks and 25600 inodes就两行,没有任何 Superblock backups stored on blocks:。不是命令坏了,是确实没有备份可列。
⚠️ 所以这里有个容易踩的认知陷阱:备份超级块不是「一定有」的东西,它取决于块组数量和 sparse_super 约定。 大分区天然有多个块组,这个问题不明显;但小分区(尤其是容器里那种几百 MB 的数据盘、树莓派上的小 boot 分区)很容易只有一个块组。真在意可恢复性,格式化时用 -g 把块组切小,或者干脆多留一份超级块备份。
一个反直觉的细节:备份超级块的位置偏移
顺手验证一下"备份超级块到底长什么样"。按 ext4 规范,主超级块位于偏移 1024 字节,而备份超级块位于所在块的块首(偏移 0)。用 xxd 找魔数 53ef 可以直接验证:
xxd -s 1080 -l 2 -p disk.img; xxd -s 33554488 -l 2 -p disk.img53ef
53ef1080 是 1024 + 56(魔数在超级块结构里偏移 56 字节),33554488 是 8192 × 4096 + 56。两处都有 53ef。
正因为偏移规则不同,如果你把备份超级块所在的那个块 dd 出来直接喂给 file,它会认成一个完全不相干的东西:
dd if=disk.img bs=4096 skip=0 count=1 status=none | file -
dd if=disk.img bs=4096 skip=8192 count=1 status=none | file -/dev/stdin: Linux rev 1.0 ext4 filesystem data, UUID=b556dfb9-6da0-414a-8f12-9139923e7290 (extents) (64bit) (large files) (huge files)
/dev/stdin: Atari 68xxx CPX file (version 0005)第一行(块 0,主超级块在块内 +1024)被正确识别;第二行(块 8192,备份超级块在块内 +0)就被误判了。这不代表备份坏了,只是 file 的魔数探测偏移对不上而已。
顺带两件事:坏道扫描与开机自检策略
fsck 修的是「账本」,硬件层面有没有坏道是另一条线,用 badblocks 扫:
badblocks -sv disk.imgChecking blocks 0 to 102399
Checking for bad blocks (read-only test): done
Pass completed, 0 bad blocks found. (0/0/0 errors)-s 显示进度、-v 输出详情。注意这里扫的是 102400 个块(按 1KB 计),和文件系统层的块大小无关——badblocks 面对的是裸存储。生产环境上更稳的做法是 badblocks -nsv(非破坏性读写测试,能逼出读没问题、写下去才暴露的坏块),代价是要慢上几十倍。
另一件是控制开机自动 fsck 的频率。内核在挂载前会根据超级块里的两个计数器决定要不要强制体检,tune2fs 能改:
tune2fs -c 30 disk.img; tune2fs -l disk.img 2>/dev/null | grep -E "Mount count|Maximum mount count|Check interval"tune2fs 1.46.5 (30-Dec-2021)
Setting maximal mount count to 30
Mount count: 0
Maximum mount count: 30
Check interval: 0 (<none>)Mount count/Maximum mount count:挂载次数达到上限就强制 fsck 一次。改成-1关闭(tune2fs -c -1)。Check interval:按时间强制自检,tune2fs -i 30d设为 30 天,0表示不启用。
云服务器上这两个值默认往往是「关闭」状态,因为强制 fsck 会让重启变慢、甚至卡在控制台上。反过来,如果你需要主动触发一次根分区体检,可以在 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT 里加 fsck.mode=force fsck.repair=yes,update-grub 后重启生效。
根分区真出事了怎么办
实验盘可以随便造,生产机的根分区不行。几条现实中的处理路径:
- 能挂载但报了错:把根分区重挂成只读(
mount -o remount,ro /)后再跑e2fsck -fn评估严重程度,别在读写状态下直接-y。 - 挂不上、进不了系统:从云厂商的 VNC 控制台进救援模式,或把盘挂到另一台机器上离线修——这是最稳的一条路,也是本文章节顺序的现实版:先有布局认知,再有正确的
-b参数。 - RAID 与 LVM 之上的文件系统:先确保阵列/卷本身健康(见 Linux 软件 RAID:从 mdadm 创建阵列到磁盘故障自动重建 和 LVM 逻辑卷管理:从物理卷到在线扩容与快照回滚),再对逻辑卷做 fsck。
- 分区表本身有问题:那是
fdisk/parted层面的事,先确认分区起点对不对(Linux 磁盘挂载:从 fdisk 分区到 fstab 开机自动挂载),再谈文件系统。
另外,如果是空间被吃满而不是结构损坏,那是另一套流程——参见 Linux 磁盘空间排查与清理:从 df 到 journald 的完整救火流程。
小结
| 场景 | 命令 |
|---|---|
| 看块组与元数据布局 | dumpe2fs disk.img |
| 只看超级块摘要 | dumpe2fs -h disk.img |
| 只读体检,不改动 | e2fsck -fn disk.img |
| 自动修复 | e2fsck -fy disk.img |
| 从备份超级块恢复 | e2fsck -b <块号> -B <块大小> -fy disk.img |
| 硬件坏道扫描 | badblocks -nsv disk.img |
| 挂载次数 / 自检周期 | tune2fs -c 30 / tune2fs -i 30d |
三句话收尾:
- fsck 管结构,不管内容——别指望它发现文件被篡改,那是校验和的活。
-n先看,-y后动——尤其在你还不知道损坏范围的时候。- 备份超级块不是天然存在的——小分区上很容易一个都没有,格式化时留个心眼比事后抢救便宜得多。
实验做完了,把磁盘空间还回去:
rm -rf /tmp/fsck-demo
评论 (0)
暂无评论,快来抢沙发吧!