从零写OS内核 | GPT 分区:MBR 的 2TB 上限怎么破,128 个分区怎么管

上一篇文章我们讲了 MBR——512 字节的第一个扇区,446 字节 bootloader 加 64 字节分区表。MBR 统治了 PC 分区标准 30 年,但它有两个绕不过去的硬伤:最多 4 个分区,和最大 2TB 磁盘容量

进入 2000 年代,大容量硬盘普及,MBR 开始成为瓶颈。Intel 牵头设计了新一代分区标准——GPT(GUID Partition Table),它不是 MBR 的升级版,而是一次完全重写:分区数从 4 个扩展到 128 个,容量上限从 2TB 跳到 8ZB(ZettaByte),启动方式也从”磁盘第一个扇区”变成了”专用 EFI 分区”。

今天,我们来拆解 GPT 的完整结构——它是怎么组织的,MBR 原来承担的功能去哪了,以及 GPT 带来了哪些 MBR 根本没有的新玩法。


MBR 的三件事,GPT 全重新做了

MBR 在磁盘上”一肩挑”了三件完全不同的事:

MBR 承担的功能 具体内容 GPT 的对应方案
启动代码 BOOTMGR / GRUB Stage 1,占 446 字节 移到 ESP(EFI System Partition) 里,不再在 MBR 里
分区表 4 条 × 16 字节 = 64 字节,最多 4 个主分区 独立分区表区域(128 条 × 128 字节 = 16KB),在 LBA 2-33
启动标志 0x55AA,判断磁盘是否可启动 不需要,GPT Header 里用 CRC32 校验替代

MBR 把三件完全不同性质的事混在一起——代码 + 数据 + 元数据。GPT 的思路是:启动的事归启动,分区的事归分区,各司其职,互不干扰


GPT 的物理布局:磁盘是怎么排列的

GPT 把磁盘从头到尾分成了几个区域,每个区域有明确的职责:

Bash
GPT 磁盘完整布局(以 1TB 磁盘为例):

┌─────────────────────────────────────────────────────────────────┐
│ LBA 0512 字节):Protective MBR                               │
│   • 防止旧系统误识别 GPT 磁盘为"未分区"                         │
│   • 第一条分区条目:type=0xEE,start=1,size=整个磁盘             │
│   • 最后两字节:0x55AA(让 BIOS 认为这是"可启动"的 MBR 磁盘)   │
├─────────────────────────────────────────────────────────────────┤
│ LBA 1512 字节):GPT Header                                   │
│   • 签名 "EFI PART"(46 49 52 50 41 52 54)                     │
│   • 分区表位置(在 LBA 2)                                      │
│   • 分区表大小(128 条 × 128 字节 = 16KB)                      │
│   • 分区条目数量(128)                                         │
│   • 当前磁盘的 GUID(唯一标识符)                               │
│   • 备份 GPT Header 位置(LBA -1,即倒数第二扇区)               │
├─────────────────────────────────────────────────────────────────┤
│ LBA 2 - LBA 3332 扇区 = 16KB):主分区表                      │
│   • 128 条分区条目,每条 128 字节                               │
│   • 总容量:128 × 128 = 16,384 字节(正好 32 个扇区)           │
├─────────────────────────────────────────────────────────────────┤
│ LBA 34+:用户数据区(分区实际开始的地方)                        │
│   分区 1:ESP(EFI System Partition,FAT32,≥100MB)           │
│   分区 2:Linux root                                            │
│   分区 3:Linux swap                                            │
│   分区 N:...                                                   │
├─────────────────────────────────────────────────────────────────┤
│ 倒数第 2 扇区(LBA -1):备份 GPT Header                        │
│   • GPT Header 的完整副本,CRC32 校验                            │
│   • 磁盘损坏时能恢复 GPT 信息                                    │
├─────────────────────────────────────────────────────────────────┤
│ 倒数第 1 扇区(LBA -2 开始,16KB):备份分区表                  │
│   • 分区表的完整副本,与主分区表内容相同                         │
│   • 主分区表损坏时,工具可以读取备份并恢复                       │
└─────────────────────────────────────────────────────────────────┘

保护性 MBR(LBA 0)的作用:这不是给 GPT 用的,是给不识别 GPT 的旧工具(MS-DOS fdisk、Windows 安装程序)看的——它们看到一个”类型为 0xEE 的分区,占据整个磁盘”,就知道”这个盘我管不了,别动它”,避免误格式化。


分区条目结构:128 字节怎么描述一个分区

GPT 的每条分区记录是 128 字节,容量是 MBR(16 字节)的 8 倍,信息量也丰富得多:

Bash
GPT 分区条目(128 字节):
┌──────────┬──────┬────────────────────────────────────────────────┐
│ 偏移     │ 大小 │ 含义                                            │
├──────────┼──────┼────────────────────────────────────────────────┤
│ 0x00     │ 16B  │ 分区类型 GUID(告诉操作系统"这是什么文件系统")  │
│ 0x10     │ 16B  │ 唯一分区 GUID(这个分区的全球唯一标识)         │
│ 0x20     │ 8B   │ 起始 LBA(64 位,终于没有 2TB 上限了)          │
│ 0x28     │ 8B   │ 结束 LBA(64 位)                              │
│ 0x30     │ 8B   │ 属性标志(64 位,位图)                        │
│ 0x38     │ 72B  │ 分区名称(36 个 UTF-16 字符,可显示中文)       │
└──────────┴──────┴────────────────────────────────────────────────┘

分区类型 GUID(取代 MBR 的 1 字节类型码)

MBR 用 1 字节的类型码(0x07=NTFS,0x83=Linux),但 1 字节只有 256 种可能,不够用且容易冲突——比如 0x83 本意是”Linux Native 分区”,但谁都可以声称自己是 0x83,没有约束力。

GPT 用 16 字节的 GUID(Globally Unique Identifier)代替类型码,每个分区类型有官方分配的 GUID:

分区类型 GUID 说明
ESP(EFI System Partition) C12A7328-F81F-11D2-BA4B-00A0C93EC93B EFI 启动管理器代码
Linux filesystem data 0FC63DAF-8483-4772-8E79-3D69D8477DE4 通用 Linux 数据区(ext4/XFS)
Linux swap 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F Linux swap
Linux LVM E6D6D379-F507-44C2-A23C-238F2A3DF928 LVM 物理卷
Windows Recovery Environment DE94BBA4-06D1-4D40-A16A-BFD50179D6AC Windows RE
Microsoft Basic Data EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 Windows 数据分区(NTFS/FAT)

16 字节 GUID 的排列组合足够给每种操作系统、每种文件系统、每种特殊用途一个永不冲突的标识符。这是 MBR 类型码完全做不到的事。

分区名称(72 字节 UTF-16)

MBR 的分区没有名称,只有”主分区 1″、”主分区 2″这样的序号。GPT 给每个分区一个 36 字符的 UTF-16 名称——比如”Arch Linux”、”Windows RE”、”Data Volume”,可以显示中文:

Bash
0x38 偏移开始的 72 字节(36 × UTF-16):
48 00 61 00 72 00 63 00 20 00 4C 00 69 00 6E 00  # "Arch Linux"
75 00 78 00 20 00 00 00 00 00 00 00 00 00 00 00  # (不足部分填 0)

属性标志(64 位位图)

Bash
属性标志(64 位)的常用位:
Bit 0:系统分区(System partition)
         - OS 启动必需的分区,不能随便删
         - 对应 ESP 或 Windows 的 "System Reserved"
Bit 1:EFI 固件隐藏属性
         - 固件不显示这个分区
Bit 2:Legacy BIOS 启动分区(BIOS Boot Partition)
         - GPT 里用于 BIOS 启动支持(很少用)

Bit 48-63:用户定义属性(每 OS 自己解释)
         - Linux 用 Bit 60 表示"必须备份"
         - Linux 用 Bit 54 表示"只读"

GPT 的新能力:从 4 分区到 128 分区

为什么是 128 个分区

主分区表在 LBA 2-33,占 16KB。每条分区记录 128 字节,所以最多 128 条。

Bash
128 × 128 字节 = 16,384 字节 = 32 个扇区(正好填满 LBA 2-33)

如果想支持更多分区?
  - 需要更大的分区表 → 更大分区表区域 → 浪费更多空间
  - 实际上 128 个分区已经远超任何单磁盘需求
  - 如果需要更多,用 LVM 把多块磁盘合并成逻辑卷

8ZB 的容量上限

GPT 用 64 位记录起始/结束 LBA。每个 LBA 是 512 字节(标准扇区大小)。

Bash
2^64 × 512 = 9.4 ZB(ZettaByte,1 ZB = 10^21 字节)

实际限制:磁盘厂商用 512 字节模拟 4K 物理扇区(4Kn),
或用 4K 原生扇区(4Kn),但 64 位 LBA 对现代磁盘绰绰有余。
主流硬盘(≤20TB)用 LBA 48 位就够了(48 位也支持 128PB)。

CRC32 校验

GPT Header 有一个 Header CRC32 字段(偏移 0x10,4 字节)。每次读取 Header 时,内核工具(gdisk / parted)会验证 CRC32 是否匹配,如果不匹配说明 Header 损坏,从磁盘末尾读取备份恢复。

Bash
# 用 gdisk 查看 GPT Header CRC
sudo gdisk -l /dev/sda | head -20
# 输出:
# Partition table scan:
#   MBR: protective
#   BSD: not present
#   APM: not present
#   GPT: present
#
# Disk /dev/sda: 500118192 sectors, 238.5 GiB
# Model: Samsung SSD 970 EVO Plus 250GB
# Sector size (logical/physical): 512/512
# Partition table holds 128 entries
# Total free space is 0 sectors

备份与恢复:GPT 自愈机制

GPT 最大的工程优势是双副本:主分区表在 LBA 2-33,备份在磁盘倒数 16KB;主 Header 在 LBA 1,备份在倒数第二个扇区。任何一边损坏,另一边可以完整恢复。

Bash
# 用 gdisk 检查 GPT 的完整性
sudo gdisk /dev/sda

# 在 gdisk 里:
#   x → 进入专家模式
#   e → 重建主 GPT Header(从备份恢复)
#   v → 验证分区表(检查 CRC)
#   d → 重建主分区表(从备份恢复)

实际恢复案例

假设磁盘的分区表损坏(但数据区完好):

Bash
# 1. 用 testdisk 或 gpted 扫描丢失的分区
sudo testdisk /dev/sda

# 2. 或者用 gdisk 从备份恢复
# 启动 gdisk,选择 "r"(恢复)模式
# 选项 "e" 从备份重建主分区表
# 选项 "d" 用备份恢复损坏的主分区表

# 3. 验证修复后的分区表
sudo gdisk -v /dev/sda
# 如果输出 "OK: header CRC matches",修复成功

UEFI + GPT:启动链路的完整变化

MBR 时代,bootloader 代码塞在 MBR 的 446 字节里,够塞 GRUB Stage 1 但不够塞 GRUB Stage 2,所以要 Stage 1.5 做文件系统桥接。

UEFI + GPT 完全重新设计了启动链路:

Bash
UEFI + GPT 启动链路:

① 上电 → UEFI 固件初始化
② 读取 NVRAM 里的 BootOrder(保存在闪存里)
③ 按 BootOrder 顺序尝试加载 EFIBootBootX64.efi(或 NVRAM 里的自定义条目)
④ 验证 Secure Boot 签名(或跳过验证)
⑤ 找到 .efi 文件后,直接执行 → 控制权交给 Bootloader(如 GRUB)
⑥ GRUB 读取 /boot/grub/grub.cfg,显示启动菜单
⑦ 用户选 Linux → GRUB 加载 vmlinuz + initramfs,跳转执行
   用户选 Windows → GRUB chainload EFIMicrosoftBootbootmgfw.efi

ESP(EFI System Partition)是整个启动链路的核心——它是一个 FAT32 格式的小分区(≥100MB),存放所有 .efi 文件,包括:

  • EFI/BOOT/BOOTX64.EFI(默认启动项,回退用)
  • EFI/ubuntu/grubx64.efi(Ubuntu 的 GRUB)
  • EFI/Microsoft/Boot/bootmgfw.efi(Windows Boot Manager)
  • EFI/arch/grubx64.efi(Arch 的 GRUB)

Bash
ESP 分区内容示例:
/
├── EFI/
│   ├── Boot/
│   │   └── BOOTX64.EFI         ← 回退启动项(每台机器都有)
│   ├── ubuntu/
│   │   ├── grubx64.efi          ← Ubuntu GRUB
│   │   ├── grub.cfg             ← GRUB 配置文件
│   │   └── shimx64.efi          ← Secure Boot 签名垫片
│   ├── Microsoft/
│   │   └── Boot/
│   │       └── bootmgfw.efi    ← Windows Boot Manager
│   └── arch/
│       └── grubx64.efi          ← Arch GRUB
└── .disk_info(可选,元数据)

GPT 工具:Linux 上的 GPT 操作

gdisk / sgdisk:最完整的 GPT 工具

Bash
# 查看 GPT 分区表
sudo gdisk -l /dev/sda

# 创建新分区
sudo gdisk /dev/sda
#   n → 新建分区
#   分区号 → 默认(下一个可用)
#   起始扇区 → 默认
#   结束扇区 → +50G(创建 50GB 分区)
#   类型 → 输入 L 或 hexcode
#     0700 = Microsoft basic data (NTFS/FAT32)
#     8200 = Linux swap
#     8300 = Linux filesystem (ext4/XFS)
#     fd00 = Linux LVM
#   w → 写入保存

# 高级操作(sgdisk,非交互)
sudo sgdisk --new=1:::+50G /dev/sda           # 创建 50GB 分区
sudo sgdisk --typecode=1:0FC63DAF-8483-4772-8E79-3D69D8477DE4 /dev/sda  # 设类型为 Linux
sudo sgdisk --change-name=1:"Arch Linux" /dev/sda   # 设分区名
sudo sgdisk --delete=1 /dev/sda             # 删除分区 1

parted:MBR 和 GPT 通吃

Bash
# parted 适合创建/删除/调整分区
sudo parted /dev/sda

# 创建 GPT 分区表
(parted) mklabel gpt

# 创建分区
(parted) mkpart primary ext4 0% 100%   # 占满整个盘
(parted) mkpart primary fat32 0MB 512MB  # 创建 512MB ESP

# 查看
(parted) print

# 删除
(parted) rm 2

# 退出
(parted) quit

fdisk 也支持 GPT(但功能有限)

Bash
# fdisk -l 也能显示 GPT 信息(只读)
sudo fdisk -l /dev/sda

# 实际上 fdisk 更推荐用于 MBR,GPT 用 gdisk
# 因为 fdisk 对 GPT 的操作容易出错(GPT 结构比 MBR 复杂得多)

gptedit:GUI 工具

Bash
# 如果习惯 GUI
sudo apt install gpted
gpted /dev/sda
# 点几下就能创建/删除/调整 GPT 分区

用 dd 备份 GPT(注意别破坏分区)

Bash
# 备份主 GPT Header(LBA 1)
sudo dd if=/dev/sda of=/home/$USER/gpt-header-backup.img bs=512 count=1 skip=1

# 备份分区表(LBA 2-33,共 32 个扇区)
sudo dd if=/dev/sda of=/home/$USER/gpt-parttable-backup.img bs=512 count=32 skip=2

# 备份 Protective MBR(LBA 0)
sudo dd if=/dev/sda of=/home/$USER/protective-mbr.img bs=512 count=1

# 恢复(从备份文件)
sudo dd if=/home/$USER/gpt-header-backup.img of=/dev/sda bs=512 count=1 seek=1
sudo dd if=/home/$USER/gpt-parttable-backup.img of=/dev/sda bs=512 count=32 seek=2

MBR → GPT 转换:不丢数据的迁移

想把 MBR 磁盘换成 GPT,但不想重装系统?Linux 的 gdisk 可以帮你做这件事:

Bash
# 警告:操作前务必备份数据!

# 1. 用 fdisk 确认当前是 MBR
sudo fdisk -l /dev/sda
# 输出里如果有 "GPT is not present" 说明是 MBR

# 2. 用 gdisk 打开这个磁盘
sudo gdisk /dev/sda

# 3. gdisk 会检测到 MBR 分区表,询问:
#    "Found valid MBR with 3 partitions"
#    "Convert to GPT? (Y/N):"
#    输入 Y

# 4. gdisk 会保留所有原有分区(只转换元数据,不改数据区)
#    分区内容、文件系统、数据全部保留

# 5. 查看转换结果
#    p → 显示分区表
#    w → 写入保存

# 6. 验证
sudo gdisk -l /dev/sda | grep "GPT:"
# 输出:GPT is present

关键点:MBR → GPT 转换不会破坏磁盘上的数据——它只改变了分区表的格式。分区表从 MBR 的 64 字节格式,转换为 GPT 的 16KB 格式,原有数据的起止位置不变。

但要注意

  • Windows 用户:Windows 需要用 mbr2gpt.exe 工具,否则直接转换会破坏 Windows 的启动路径(UEFI 的 BCD 存储依赖 ESP 分区)
  • BIOS 模式系统:如果原本是 Legacy BIOS 启动,换成 GPT 后需要改 BIOS 设置为 UEFI 模式,否则无法启动

GPT 新玩法:绕过 MBR 的限制

超过 2TB 的磁盘:必须用 GPT

Bash
# 创建 4TB 分区(MBR 做不到)
sudo parted /dev/sdb
(parted) mklabel gpt
(parted) mkpart primary ext4 0% 100%
(parted) mkfs.ext4 /dev/sdb1

多系统引导(UEFI 模式)

UEFI 的 NVRAM 里保存了启动条目列表(BootOrder),每个条目指向一个 .efi 文件的路径。安装多系统时,每个 OS 在 ESP 里安装自己的 .efi,GRUB 可以自动扫描并生成菜单。

Bash
# 查看 NVRAM 里的启动条目(efibootmgr)
sudo efibootmgr -v

# 输出示例:
# BootOrder: 0001, 0000
# Boot0001* Arch Linux    HD(1,GPT,...)/File(EFIarchgrubx64.efi)
# Boot0000* Windows Boot Manager   HD(2,GPT,...)/File(EFIMicrosoftBootbootmgfw.efi)

# 新增一个启动条目
sudo efibootmgr -c -d /dev/sda -p 1 -l "EFIarchgrubx64.efi" -L "Arch Linux"

分区名称中文支持

GPT 的 36 字符 UTF-16 名称支持中文,这在服务器管理多硬盘时很有用:

Bash
sudo sgdisk --change-name=1:"数据盘-备份" /dev/sda
sudo sgdisk --change-name=2:"系统盘-SSD" /dev/sdb

# 查看
sudo gdisk -l /dev/sda | grep "Partition number"

踩坑与注意事项

坑 1:GPT 磁盘在 BIOS 模式下无法启动

现象:磁盘已经换成 GPT,但 BIOS 还是 Legacy 模式,开机直接跳过磁盘。

原因:BIOS/Legacy 模式不认识 GPT(它只看 MBR)。GPT 盘需要 UEFI 模式才能启动。

解决:在 BIOS 设置里切换到 UEFI Only(不开 CSM)。

坑 2:ESP 分区太小(装不下 GRUB 更新)

现象:GRUB 更新时报错 “No space left on ESP”。

原因:ESP 默认 100MB,有些 GRUB 包(特别是带有多个 .efi 变体)会超过 100MB。

解决:重新分区 ESP(至少 300MB):

Bash
# 用 gdisk 缩小 ESP(谨慎操作)
# 先备份 ESP 内容,重建后再恢复

坑 3:Windows 安装在 MBR 磁盘,克隆到 GPT 磁盘后无法启动

现象:用克隆工具把 MBR 磁盘克隆到 GPT 磁盘,但 Windows 报告 INACCESSIBLE_BOOT_DEVICE。

原因:Windows 的 BCD(Boot Configuration Data)存储在系统分区里,克隆只复制了数据,没有更新 NVRAM 启动条目。

解决:用 Windows PE(WinPE)启动,运行 bcdboot C:Windows /s S: 重建 BCD,或在 UEFI 设置里手动添加启动项。

坑 4:gdisk 转换 MBR 时选错了磁盘

现象:输入 sudo gdisk /dev/sdb,结果 sdb 是另一个磁盘。

原因:Linux 的设备名(sda/sdb)在热拔插时可能变化。

解决:用 lsblk 确认磁盘的 WWN(全球唯一标识)或 UUID:

Bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,UUID | grep -E "disk|part"
# 或者用
sudo blkid | grep -E "sda|sdb"

写在最后

GPT 的本质,是把 MBR 混在一起的三件事(启动代码、分区表、启动标志)拆开了——启动归 ESP(FAT32 分区),分区表归 LBA 2-33 的 16KB 独立区域,校验归 CRC32 + 备份副本。

结果:

  • 128 个分区(不需要扩展分区/逻辑分区的 EBR 链了)
  • 8ZB 容量上限(64 位 LBA,彻底告别 2TB 天花板)
  • 自愈能力:备份 Header + 备份分区表,损坏可恢复
  • 分区命名:36 字符 UTF-16,中文友好
  • GUID 类型:永不冲突的分区类型标识

wandos 目前没有磁盘驱动,也不支持 GPT 分区解析。如果要做完整的 GPT 支持,需要先实现 NVMe/AHCI 驱动,然后实现 GPT 分区表解析,最后实现 FAT32 ESP 访问——这是教育内核走向”真实磁盘启动”的关键一步。

下篇预告(363):候选方向——VFS:虚拟文件系统层,ext2/fat/proc 文件系统是怎么通过统一接口向用户暴露的;或者 系统调用(0x80):软中断到内核的完整路径。你选哪个?

仓库https://github.com/golang12306/os-kernel-from-scratch


相关阅读


动手环节

想深入理解本文内容?动手实践是最好的方式:

今天的目标:用 gdisk 查看你的 GPT 磁盘,备份 GPT Header,然后练习 MBR → GPT 转换。

  1. 查看你当前磁盘的分区类型

    Bash
    sudo fdisk -l /dev/sda | grep "GPT"
    # 输出 "GPT is not present" = MBR,"nGPT is present" = GPT
  2. 如果是 GPT 磁盘,备份 GPT Header 和分区表

    Bash
    mkdir -p ~/gpt-backup
    sudo dd if=/dev/sda of=~/gpt-backup/gpt-header.img bs=512 count=1 skip=1
    sudo dd if=/dev/sda of=~/gpt-backup/gpt-parttable.img bs=512 count=32 skip=2
    ls -la ~/gpt-backup/
  3. 在测试盘上创建 GPT 分区

    Bash
    # 找一个测试 U 盘(/dev/sdc)
    sudo parted /dev/sdc mklabel gpt
    sudo gdisk /dev/sdc
    #   n → 新建分区
    #   起始 → 默认,结束 → +1G(创建 1GB ESP 测试分区)
    #   类型 → 0700(Microsoft basic data),名称 → "Test ESP"
    #   w → 写入
  4. 模拟 GPT 损坏恢复

    Bash
    # 备份后,手动用 dd 写一个错误的 Header(测试用)
    # 然后用 gdisk 的 "e" 命令从备份恢复
    sudo gdisk /dev/sdc
    #   x → 专家模式
    #   e → 从备份恢复主 Header
    #   v → 验证

提示:操作真实磁盘的 GPT 前,务必备份。GPT Header 损坏虽然可以恢复,但需要正确步骤,盲目操作可能破坏分区表。


仓库:https://github.com/golang12306/os-kernel-from-scratch

最后修改: 2024年3月11日

作者

评论

发表评论

您的邮箱地址不会被公开。