暗色模式

Linux 文件系统自检:从 e2fsck 只读体检到备份超级块抢救

技术教程
2026-09-17
6
0
本文要点
  • 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 e2fsck

fsck 本身只是个 43KB 的调度壳:它读 /etc/fstab、判断设备上是什么文件系统,再把活派给对应的 fsck.<类型>。而 fsck.ext4 根本不是独立程序,就是一个指向 e2fsck 的符号链接。所以对 ext4 来说,fsck /dev/vda1e2fsck /dev/vda1 最终是同一个程序在跑

命令职责
fsck调度壳,按文件系统类型分发;批量检查 fstab 里的设备
e2fsckext2/3/4 的真正检查与修复程序
dumpe2fs只读,打印超级块与每个块组的详细布局
tune2fs改超级块里的可调参数(挂载计数、自检周期)
debugfsext 系列的「调试器」,能直接翻 inode 和块
badblocks硬件层坏道扫描,与文件系统结构无关
mke2fs / mkfs.ext4格式化(mkfs.ext4mke2fs 的软链)

造一块可以随便糟蹋的实验盘

不要拿根分区练手。 已挂载的 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 可以直接看:

dumpe2fs 列出 4 个块组的边界与 inode 表位置,随后 e2fsck -fn 一次性通过五个检查阶段

cd /tmp/fsck-demo && dumpe2fs disk.img 2>/dev/null | grep -E "^Group [0-9]+:|Inode table at"; e2fsck -fn disk.img 2>&1
Group 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 1inode 与数据块的使用情况(最耗时的一步)
Pass 2目录结构(目录项是否合法、.. 指向对不对)
Pass 3目录连通性(目录是否可达、有没有孤立目录)
Pass 4引用计数(有多少目录项指向同一个 inode)
Pass 5块组摘要信息(超级块里的统计与位图是否吻合)

弄坏它:把主超级块抹掉

现在制造一次故障。主超级块位于文件系统内偏移 1024 字节处,dd 从这个偏移写 1KB 零进去就够了:

dd 抹掉主超级块后,e2fsck 报 Bad magic number,并给出 -b 8193 / -b 32768 的建议

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>&1
1+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>

几个要点:

  1. bs=1k seek=1 的意思是「跳过 1 个 1KB 的块,从第 1024 字节开始写」——正好覆盖超级块。conv=notrunc 保证不会把文件截断成 1KB。
  2. 报错是 Bad magic number in super-block:ext4 的超级块固定魔数是 0xEF53(小端存储就是 53 ef),被清零后自然对不上。
  3. -b 8193 这个提示不能照抄。 它是 e2fsprogs 按 1K 块大小的教科书场景给的默认值;我们这块盘是 4K 块,8193 这个块号压根不落在任何备份位置上。
  4. 顺便一提,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 显式告诉它块大小(超级块坏了就没人告诉它块多大了,必须手工指定):

e2fsck -b 8192 用备份超级块修复,复检通过,重新挂载后三个文件都还在

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 mnt
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
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.img
mke2fs 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.img
53ef
53ef

10801024 + 56(魔数在超级块结构里偏移 56 字节),335544888192 × 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.img
Checking 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/grubGRUB_CMDLINE_LINUX_DEFAULT 里加 fsck.mode=force fsck.repair=yesupdate-grub 后重启生效。

根分区真出事了怎么办

实验盘可以随便造,生产机的根分区不行。几条现实中的处理路径:

  1. 能挂载但报了错:把根分区重挂成只读(mount -o remount,ro /)后再跑 e2fsck -fn 评估严重程度,别在读写状态下直接 -y
  2. 挂不上、进不了系统:从云厂商的 VNC 控制台进救援模式,或把盘挂到另一台机器上离线修——这是最稳的一条路,也是本文章节顺序的现实版:先有布局认知,再有正确的 -b 参数
  3. RAID 与 LVM 之上的文件系统:先确保阵列/卷本身健康(见 Linux 软件 RAID:从 mdadm 创建阵列到磁盘故障自动重建LVM 逻辑卷管理:从物理卷到在线扩容与快照回滚),再对逻辑卷做 fsck。
  4. 分区表本身有问题:那是 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

三句话收尾:

  1. fsck 管结构,不管内容——别指望它发现文件被篡改,那是校验和的活。
  2. -n 先看,-y 后动——尤其在你还不知道损坏范围的时候。
  3. 备份超级块不是天然存在的——小分区上很容易一个都没有,格式化时留个心眼比事后抢救便宜得多。

实验做完了,把磁盘空间还回去:

rm -rf /tmp/fsck-demo

发表评论

暂无评论,快来抢沙发吧!