0x00 一个看似无害的操作,引爆了一场灾难

事情是这样的。我的笔记本是 Windows + Ubuntu (Kubuntu) 双系统,配置如下:

  • 磁盘0(NVMe 953GB):

    • p1: 100MB EFI System Partition (Windows ESP)
    • p2: 16MB Microsoft Reserved
    • p3: 127.3GB C盘 (NTFS, BitLocker加密)
    • p4: 704MB Windows恢复分区
    • p5: 320GB D盘预留空间 (NTFS, BitLocker加密但从未格式化使用)
    • p6: 384MB EFI System Partition (Ubuntu ESP)
    • p7: 480GB Linux filesystem (Ubuntu根分区,Btrfs文件系统)
    • p8: 25.4GB Linux swap
  • 磁盘1(476GB):exFAT外置U盘,与系统无关

那个320GB的p5分区是我当初装双系统时特意留给D盘的,但因为我一直主要在Linux环境下开发学习,就忘了格式化它。某天心血来潮想用Windows磁盘管理把它格式化成NTFS当真正的D盘用——结果重启后,Ubuntu再也进不去了。

开机直接掉进 grub> 命令行,无论怎么 insmod part_gpt、insmod ext2、insmod btrfs、ls 各个分区,统统报 unknown filesystem。

我当时的心态:完了,是不是硬盘坏了?是不是要重装系统了?我那480GB的开发环境、代码、课程作业……


0x01 诊断误区——为什么在 grub> 里救不了

最初的尝试和失败

我天真地以为可以在GRUB命令行里手动指定内核路径启动,就像网上教程说的那样。于是我照着做:

grub> insmod part_gpt
grub> insmod ext2
grub> ls
(memdisk) (hd0) (hd0,gpt1) (hd1)
grub> ls (hd0,gpt1)/
error: unknown filesystem.
grub> insmod btrfs
grub> ls (hd0,gpt1)/
error: unknown filesystem.

诡异现象1: ls 只列出了一个 (hd0,gpt1),而且这个分区还是 unknown filesystem。可我明明有8个分区啊!

诡异现象2: 出现了 (memdisk) 设备——这玩意儿正常情况下不该出现在硬盘启动的环境里。

诡异现象3: 即使我加载了所有文件系统模块(ext2、btrfs、fat),依然读不出任何分区内容。

真相浮出水面

后来我才明白,这三个"诡异"其实指向同一个根源:

  1. (memdisk) 的来源:我的LiveUSB是用Ventoy制作的,Ventoy的引导机制会往内存注入虚拟磁盘来加载ISO镜像。所以当时那个 grub> 很可能根本不是硬盘Ubuntu的GRUB,而是被Ventoy环境污染过的混乱状态。
  2. 只显示一个分区的真正原因:GPT主头损坏了。GRUB的 part_gpt 驱动解析器非常脆弱,一旦主头CRC校验失败,它就直接放弃,不会像Linux内核那样智能回退去读备份头。于是在GRUB眼里,这块盘"没有可用分区"。
  3. 为什么加载再多模块也没用:因为问题不在文件系统识别能力,而在于压根就没找到分区在哪。

关键教训

不要在 grub> / grub rescue> 里浪费时间试图手动引导Btrfs分区。

GRUB命令行对Btrfs的支持本来就残缺(无法优雅切换subvolume),再加上GPT主头损坏导致分区枚举失败,这条路从一开始就是死的。正确做法是直接上LiveUSB,用功能完整的Linux环境修复。


0x02 用Linux内核视角穿透"空白分区"的伪装

Windows磁盘管理的误导性显示

插上Ubuntu安装U盘,进入Try Ubuntu模式,打开终端第一件事就是看分区情况。但我手头只有Windows那边的信息作为参照——之前在Windows磁盘管理里看到的样子是这样的:

卷标/名称容量文件系统状态
(C:)127.31 GBNTFS (BitLocker)状态良好
(E:)476.92 GBexFAT状态良好
磁盘0分区1100 MB—状态良好(EFI系统)
磁盘0分区4704 MB—状态良好(恢复分区)
磁盘0分区6384 MB—状态良好(EFI系统)
磁盘0分区7480 GB—状态良好(主分区)
磁盘0分区825.38 GB—状态良好(主分区)
新加卷(D:)320 GBNTFS (BitLocker)状态良好

注意那几个"状态良好(主分区)"且文件系统一栏空白的家伙——p6/p7/p8。

这就是最大的迷惑点:Windows不认识Linux的文件系统(ext4/btrfs/swap),遇到就显示成"未知类型的主分区",既不报错也不标注具体格式。很多人一看"未知"+"主分区"就慌了:"是不是坏了?要不要初始化?"

千万别! 这正是你的Ubuntu藏身之处。

用 lsblk -f 还原真实面貌

回到LiveUSB终端,执行:

lsblk -f

输出(节选):

NAME        FSTYPE    LABEL   UUID                                    MOUNTPOINT
nvme0n1                                                                    
├─nvme0n1p1 vfat      FAT32   40E0-01F9                               ← Windows ESP
├─nvme0n1p2                                                               ← MS保留
├─nvme0n1p3 BitLocker         7147d05e-c146-430c-8bb2-bd5afb4ee5fc       ← C盘
├─nvme0n1p4 ntfs              7830C36030C323CA                            ← 恢复分区
├─nvme0n1p5 BitLocker         11d33bad-b1ab-4bb9-bbcf-f6de461f4bf3        ← D盘预留
├─nvme0n1p6 vfat      FAT32   40F8-3C82                               ← Ubuntu ESP ✨
├─nvme0n1p7 btrfs             b5ef84f3-cce2-4cc8-8022-622e4a6927c0       ← Ubuntu根(Btrfs)✨
└─nvme0n1p8 swap      1       0696a29e-62a7-46f8-ac12-f43212e6b14e       ← Swap ✨

一目了然:p7是Btrfs格式的480GB根分区,p8是25.4GB交换分区,p6是384MB的Ubuntu专用ESP。它们在Windows眼里是"未知主分区",但在Linux内核面前无所遁形。

另一个坑:blkid 居然看不到分区节点

正当我以为大局已定时,又发现一个新问题:

sudo blkid

输出里 /dev/nvme0n1 那一行只有 PTTYPE="gpt",后面一个分区都没列出来!这意味着虽然fdisk/parted这类用户态工具能读到扇区上的GPT字节并解析出8个分区,但Linux内核此刻拒绝为它们创建 /dev/nvme0n1pX 设备节点。

这就解释了为什么之前 mount /dev/nvme0n1p7 会失败——节点根本不存在嘛!

为什么会这样? GPT在设计上有两份目录:主头在盘首(前几个扇区),备份头在盘尾(最后几个扇区)。正常工作时两者互为冗余。但如果主头的CRC校验位损坏,不同工具的容忍度就不一样了:

  • fdisk/gdisk等用户态工具:检测到主头坏 → 自动回退读备份头 → 照样能列出分区;
  • Linux内核:检测到主头坏 → 保守起见直接罢工,不建节点(除非你强制让它重扫);
  • GRUB的part_gpt驱动:检测到主头坏 → 彻底懵逼,认为"这块盘没分区"。

所以当时的局面是:数据完好、分区边界清晰、Windows还能正常启动,唯独GRUB和内核都不认账。


0x03 确诊病因——GPT主头损坏而非数据丢失

决定性证据:fdisk 的一句英文提示

为了搞清楚到底是哪种损坏,我在LiveUSB终端跑了:

sudo fdisk -l /dev/nvme0n1

第一行赫然写着:

The primary GPT table is corrupt, but the backup appears OK, so that will be used.

翻译成人话:主GPT表已损坏,但备份表看起来完好,因此将改用备份表读取分区信息。

这句话瞬间把所有疑团串联起来了:

  1. ✅ 为什么Windows磁盘管理和KDE Partition Manager都能看到8个分区?因为它们都是用户态直读扇区,fdisk告诉它们的逻辑一样——主头坏就用备份头。
  2. ❌ 为什么 blkid 和 mount 一开始失败了?因为内核没这么聪明,它看见主头坏就直接放弃了。
  3. 💀 为什么GRUB完全瞎了?因为它的 part_gpt 驱动比内核还固执,连回退都不会。

结论:这不是硬件故障,不是数据丢失,只是分区表的"封面"破了,而"封底"还好好的。 修复方法就是用gdisk把完好的备份头复制回主头位置,让三者(内核、GRUB、Windows)都满意。

Btrfs子卷结构的额外确认

既然根分区是Btrfs而不是常见的ext4,还有个特殊考量:Btrfs的文件系统UUID存储在superblock里,移动/压缩分区不会改变它。这是个好消息——意味着我的 /etc/fstab 里的UUID大概率还是对的,不用大改。

但Btrfs也有个麻烦:挂载时必须显式指定subvolume。Ubuntu默认的Btrfs布局通常是:

  • @ → 根目录 /
  • @home → 家目录 /home
  • (可能还有 @snapshot 等其他子卷)

如果裸挂或者依赖default subvolume,很容易挂到顶层空壳,看不到真正的 /etc、/boot。

为了摸清结构,我先做了只读探测:

sudo mkdir -p /mnt/probe
sudo mount -o subvolid=5 /dev/nvme0n1p7 /mnt/probe  # 挂到顶层看全貌
ls -la /mnt/probe
sudo btrfs subvolume list /mnt/probe
sudo btrfs subvolume get-default /mnt/probe
sudo umount /mnt/probe

输出证实了我的猜测:

ID 256 gen 26393 top level 5 path @          ← 根子卷
ID 257 gen 26392 top level 5 path @home      ← 家目录子卷
ID 258 gen 24911 top level 5 path @swap      ← swapfile所在子卷
Default subvolume: ID 5 (FS_TREE)            ← ⚠️ 默认是顶层!

关键点:get-default 返回的是 FS_TREE(即顶层),而不是 @。这意味着挂载根分区时必须手动加 -o subvol=/@,绝不能偷懒裸挂。

接下来验证fstab是否正确:

sudo mount -o subvol=/@ /dev/nvme0n1p7 /mnt/probe
cat /mnt/probe/etc/fstab
sudo umount /mnt/probe

fstab全文(节选核心部分):

UUID=40F8-3C82                            /boot/efi      vfat    defaults   0 2
UUID=b5ef84f3-cce2-4cc8-8022-622e4a6927c0 /              btrfs   subvol=/@,defaults,noatime,autodefrag 0 0
UUID=b5ef84f3-cce2-4cc8-8022-622e4a6927c0 /home          btrfs   subvol=/@home,defaults,noatime,autodefrag 0 0
UUID=b5ef84f3-cce2-4cc8-8022-622e4a6927c0 /swap          btrfs   subvol=/@swap,defaults 0 0
UUID=0696a29e-62a7-46f8-ac12-f43212e6b14e swap           swap    defaults   0 0
/swap/swapfile                            swap           swap    defaults   0 0

三个UUID与 lsblk -f 实测值逐一吻合:

  • p7 (Btrfs): b5ef84f3-cce2-4cc8-8022-622e4a6927c0 ✓
  • p6 (ESP): 40F8-3C82 ✓
  • p8 (Swap): 0696a29e-62a7-46f8-ac12-f43212e6b14e ✓

太好了,fstab一个字都不用改! 正如我之前分析的,Btrfs的FS UUID不受分区位移影响,这次侥幸躲过了一劫。


0x04 终极修复三部曲

现在障碍只剩最后一个:GPT主头还没修好。只要跨过这道坎,剩下的就是标准的chroot重装GRUB流程了。

Step 1: 用gdisk重写主头

sudo apt update && sudo apt install -y gdisk
sudo gdisk /dev/nvme0n1

进入交互界面后,严格按照以下按键操作(切记不要乱按其他键):

  1. 按 p 回车 → 打印当前分区表,核对是否是8个分区、大小是否与预期一致;
  2. 确认无误 → 按 w 回车 → 系统询问 Do you want to write...? (Y/N) → 按 Y 回车;
  3. gdisk会将内存中(来自完好备份头的)正确分区表同时写回主头和备份头,并重新计算CRC校验位。

⚠️ 红线警告:全程只允许按 p 和 w。绝对禁止触碰以下杀手键:

  • o: 创建新的空白分区表(清空一切)
  • z: 销毁GPT结构(不可逆)
  • d: 删除分区
  • n: 新建分区(可能导致重叠覆盖)

做完后再跑一次验证:

sudo fdisk -l /dev/nvme0n1 2>&1 | head -3

这次第一行干干净净,不再有 corrupt 字样了。病根①解除。

Step 2: chroot进入Ubuntu环境重建引导

# 挂载Ubuntu根分区(务必带subvol参数!)
sudo mkdir -p /mnt/probe
sudo mount -o subvol=/@,noatime,autodefrag /dev/nvme0n1p7 /mnt/probe

# 挂载Ubuntu的ESP(注意是p6不是p1!)
sudo mkdir -p /mnt/probe/boot/efi
sudo mount /dev/nvme0n1p6 /mnt/probe/boot/efi

# 绑定运行时必需的伪文件系统
for i in dev dev/pts proc sys run; do
  sudo mount --bind /$i /mnt/probe/$i
done

# 让efivarfs可写(用于更新UEFI固件启动项)
sudo mount -t efivarfs efivarfs /mnt/probe/sys/firmware/efi/efivars 2>/dev/null || true

# 进入chroot
sudo chroot /mnt/probe /bin/bash

此时提示符变为 root@kubuntu:/#,我们已经身处真实的Ubuntu环境中。

Step 3: 治本三连击

在chroot内依次执行:

# 1) 核对fstab中的UUID是否仍然有效(应该原样输出,无需修改)
grep -E 'b5ef84f3|40F8-3C82|0696a29e' /etc/fstab

# 2) 重建initramfs(确保Btrfs驱动和新root定位方式被打入早期启动镜像)
update-initramfs -u -k all

# 3) ★核心★ 重装GRUB本体(解决core image物理定位过期的问题)
grub-install --target=x86_64-efi \
             --efi-directory=/boot/efi \
             --bootloader-id=ubuntu \
             --recheck

# 4) 重新生成GRUB配置文件(扫描/boot下的内核和initrd,构建菜单)
update-grub

# 5) 检查UEFI固件是否收到新的启动项
efibootmgr -v | grep -i ubuntu

每一步的预期输出:

Step 2 应该生成两个内核的initramfs:

update-initramfs: Generating /boot/initrd.img-7.0.0-38-generic
update-initramfs: Generating /boot/initrd.img-7.0.0-34-generic

Step 3 最关键,应该报告:

Installing for x86_64-efi platform.
Installation finished. No error reported.

这句"No error reported"意味着GRUB的core image已成功嵌入ESP并重写了所有必要的EFI二进制文件(shimx64.efi、grubx64.efi等)。病根②解除。

Step 4 应该找到所有内核并生成完整菜单:

Found linux image: /boot/vmlinuz-7.0.0-38-generic
Found initrd image: /boot/initrd.img-7.0.0-38-generic
Found linux image: /boot/vmlinuz-7.0.0-34-generic
Found initrd image: /boot/initrd.img-7.0.0-34-generic
Found memtest86+ ...
Warning: os-prober will be executed to detect other bootable partitions...
Found Windows Boot Manager on /dev/nvme0n1p1@/efi/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings ...
done

惊喜来了!os-prober居然检测到了Windows Boot Manager! 尽管C盘和D盘都是BitLocker加密的,但p1那个FAT格式的ESP里的 bootmgfw.efi 引导文件是明文的,不需要解密就能被发现。这意味着重启后GRUB菜单里会有Windows选项,不用再单独按F12选了。

Step 5 见证最终胜利:

Boot0000* Kubuntu       HD(6,GPT,...)/\EFI\ubuntu\shimx64.efi

解读这条记录:

  • Boot0000*: 星号表示激活状态,编号0000说明排在启动顺序第一位;
  • HD(6,...): 指向第6个分区(正是我们的Ubuntu ESP p6);
  • \EFI\ubuntu\shimx64.efi: 正确的Secure Boot兼容引导链入口。

至此,整条链路彻底打通:GPT主头✓ → GRUB core✓ → EFI启动项✓ → NVRAM注册✓。

退出chroot并卸载:

exit
sudo umount -R /mnt/probe

0x05 重启验收——从绝望到狂喜

拔掉LiveUSB(避免Ventoy污染启动流程),按下Ctrl+Alt+Del。

屏幕亮起的那一刻,我的心提到了嗓子眼。熟悉的BIOS Logo闪过,紧接着——

GRUB菜单出现了!

黑底白字的经典界面,上面整齐排列着:

Ubuntu
Advanced options for Ubuntu
Windows Boot Manager
UEFI Firmware Settings

选中第一项 Ubuntu,回车。进度条飞速滚动,几秒钟后,Kubuntu登录界面温柔地浮现。输入密码,进入桌面。

打开Dolphin文件管理器,导航到我的项目文件夹 ~/文档/Proj03——那些 .cpp 源代码、.docx 课程报告、编译产物,全都安安静静地躺在那里,一个字节都没少。

打开Konsole终端,敲下 uname -r,返回 7.0.0-38-generic。再敲 df -hT | grep btrfs,看到根分区稳稳挂载在 /dev/nvme0n1p7 上,类型为 btrfs。

成功了。彻彻底底地成功了。


0x06 复盘:这场灾难的本质是什么?

回头来看,整个事故链条其实很简单:

  1. 诱因:在Windows磁盘管理中格式化了一个从未使用的320GB预留分区(p5);
  2. 连锁反应:格式化操作涉及写入分区表元数据,可能在某个环节打断了GPT主头的原子性更新,导致其CRC校验位损坏;
  3. 症状爆发:

    • Windows自己不受影响(因为它有自己的ESP p1,且不依赖Ubuntu分区);
    • Linux内核启动时发现主头坏但不肯回退,于是拒绝创建分区节点;
    • GRUB更惨,直接两眼一抹黑,掉进 rescue> 模式;
  4. 修复关键:认识到这只是"封面破损"而非"书籍丢失",用gdisk从完好的备份头恢复主头,再标准流程重装GRUB即可。

整个过程没有动用任何数据恢复工具(TestDisk/PhotoRec之类),因为没有必要——数据一直都在,只是索引暂时失效了。

给后来者的忠告

如果你也遇到了类似的 grub> unknown filesystem 困境,请记住以下几点:

🚫 千万不要做的事

  1. 不要在 grub> / grub rescue> 里死磕:尤其是Btrfs用户,GRUB命令行的subvolume支持太差,效率极低且容易误操作;
  2. 不要格式化任何"看起来奇怪"的分区:Windows显示的"未知主分区"很可能就是你的Linux根分区;
  3. 不要在KDE Partition Manager/GParted里点Apply:当它们显示Type为unknown时,贸然应用变更可能真的改写分区边界;
  4. 不要运行 mkfs.btrfs / mkfs.ext4:即使挂载失败也不要怀疑是文件系统坏了,先排查分区表和挂载参数;
  5. 不要在gdisk里乱按键:o/z/d/n 任何一个都是一键毁尸灭迹。

✅ 一定要做的事

  1. 第一时间制作LiveUSB:用官方Ubuntu/Kubuntu ISO,推荐Ventoy或balenaEtcher;
  2. 优先以UEFI模式启动LiveUSB:否则后续efibootmgr/grub-install全部失效;
  3. 用 lsblk -f 和 sudo blkid 看清真实身份:不要被Windows的"未知"表象迷惑;
  4. Btrfs用户必须先探测subvolume结构:btrfs subvolume list + get-default,确认根子卷名字(通常是 @);
  5. 挂载根分区永远带上 -o subvol=/@:这是Btrfs修复的头号命门;
  6. 区分"主头损坏"和"数据丢失":前者用gdisk几秒搞定,后者才需要专业恢复手段;
  7. 修复顺序严格遵循:先修GPT → 再chroot → 然后update-initramfs → 接着grub-install → 最后update-grub;
  8. 善用 fdisk 的诊断能力:它会明确告诉你主头是否corrupt、备份是否OK,这是最快的确诊方式。

💡 预防胜于治疗

  1. 定期备份分区表:

    sudo sgdisk --backup=nvme-partition-table.bak /dev/nvme0n1
  2. 重要变更前先用 gparted 模拟演练:它会显示将要调整的起止扇区,让你心中有数;
  3. 考虑启用Timeshift快照:Btrfs的原生子卷快照功能可以做到分钟级系统回滚;
  4. 双系统尽量分离ESP:像我这样Windows用p1、Ubuntu用p6的设计,即使一方炸了另一方也能独善其身;
  5. 谨慎对待"未使用的大分区":如果要格式化长期闲置的空间,最好先从Linux侧操作(GParted),它对跨平台文件系统更安全。

0xff 结语:技术之外的心得

这次救援历时数小时,跨越十余轮对话,从最初的惊慌失措到最后的从容淡定,我深刻体会到几点:

第一,冷静是最大的武器。 面对 grub> unknown filesystem 这种恐怖画面,最容易犯的错误就是病急乱投医——格式化、重装、甚至砸键盘。但只要记住"数据还在盘上躺着"这个基本事实,就有底气一步步抽丝剥茧。

第二,工具的选择决定成败。 同样是查看分区,Windows磁盘管理给你的是迷雾,Linux的 lsblk -f 给你的是真相;同样是修复引导,在 grub> 里摸索是缘木求鱼,在LiveUSB里chroot才是正道。工欲善其事必先利其器,这话永不过时。

第三,知识储备的价值在此刻具象化了。 如果我事先不了解"Btrfs的FS UUID不随分区移动而变化"这个特性,可能会徒劳地去修改fstab;如果我不知道"GPT有主备双头"的设计哲学,可能会被fdisk那句英文提示吓得魂飞魄散。每一个看似冷门的细节,关键时刻都能救命。

第四,社区的力量无可替代。 这篇文章的记录过程本身就是一次复盘和学习。希望它能帮助下一个在深夜对着黑屏发呆的人,告诉他:别怕,你的数据还在,跟我一起把它找回来。

最后,感谢那个没有放弃的自己。🍻

(End)


PS:这次修复本来是想找大肥鱼寻求帮助的,但是“屋漏偏逢连夜雨”,Deepseek 在那时候出现故障了qwq,最后找的千问协助排查的(abab)
大肥鱼崩溃实录
Qwen协助排查实录