分区表基础:存储设备的“房产证”
做BSP开发这么多年,我越来越觉得分区表就像存储设备的“房产证”。没有它,CPU根本不知道哪里能放代码,哪里能存数据。说白了,分区表就是一张地图,告诉Bootloader和操作系统:“你的地盘在哪儿,边界在哪儿。”
我记得刚入行那会儿,有一次调试一块NAND Flash,死活启动不了。查了两天,最后发现是分区表被擦掉了。嗯,从那以后,我每次烧写Bootloader都会先确认分区表还在不在。
分区表到底是什么?
分区表是一段存储在存储设备起始位置的数据结构。它记录了整个存储介质被划分成几个区域,每个区域的起始地址、大小、类型等信息。对于Bootloader来说,分区表决定了它该从哪里加载内核,从哪里读取设备树,又该把根文件系统放在哪儿。
核心要点:分区表是存储设备上第0个扇区(或第0个块)开始的一段元数据。没有它,存储设备就是一块“荒地”。
你想想看,一块64MB的SPI Flash,如果不分区,Bootloader、内核、文件系统全混在一起,升级的时候怎么办?总不能每次都全擦全写吧。分区表就是用来解决这个问题的。
分区类型:主分区、扩展分区、逻辑分区
这里我要先澄清一个概念——我们做嵌入式BSP开发时,接触最多的是MBR(Master Boot Record)分区表。它把分区分为三种类型:
| 分区类型 | 最大数量 | 特点 | 典型用途 |
|---|---|---|---|
| 主分区 | 最多4个 | 可直接引导系统,分区表项直接写在MBR中 | Bootloader分区、内核分区 |
| 扩展分区 | 最多1个 | 不能直接使用,是逻辑分区的“容器” | 容纳多个逻辑分区 |
| 逻辑分区 | 无限制(受地址空间限制) | 嵌套在扩展分区内,通过EBR(Extended Boot Record)链式管理 | 文件系统分区、数据分区 |
为什么会有这种设计?其实这是历史遗留问题。MBR分区表只有64字节的空间来存放分区信息,每个分区表项占16字节,所以最多只能记录4个分区。后来大家发现4个不够用,才搞出了扩展分区和逻辑分区的概念。
我的经验:在嵌入式系统中,我一般只用主分区。逻辑分区在Bootloader阶段处理起来比较麻烦,很多轻量级Bootloader根本不支持。如果你需要超过4个分区,我建议直接上GPT(GUID Partition Table),而不是用扩展分区绕弯子。
分区表在存储设备中的位置
这个问题很关键。不同的存储设备,分区表的位置略有不同,但原理是相通的。
1. MBR分区表(传统BIOS启动)
MBR位于存储设备的第0个扇区(LBA 0),也就是最开头的位置。它占512字节,结构如下:
偏移量 大小 内容
0x000 446字节 Bootloader代码(第一阶段引导程序)
0x1BE 16字节 第1个分区表项
0x1CE 16字节 第2个分区表项
0x1DE 16字节 第3个分区表项
0x1EE 16字节 第4个分区表项
0x1FE 2字节 结束标志(0x55AA)
看到没?分区表项从偏移0x1BE开始,一共4个,每个16字节。最后两个字节必须是0x55AA,否则BIOS会认为这个设备不可引导。
注意:我曾经遇到过一块Flash,前512字节被意外写坏了,结果MBR里的分区表全丢了。从那以后,我养成了一个习惯——在Flash的末尾备份一份分区表。虽然麻烦,但关键时刻能救命。
2. GPT分区表(UEFI启动)
GPT比MBR先进得多。它不再局限于512字节,而是使用整个存储设备的前34个扇区(LBA 0~33)和后33个扇区作为备份。结构如下:
LBA 0: Protective MBR(兼容MBR,防止旧工具误操作)
LBA 1: GPT Header(主GPT头)
LBA 2~33: GPT分区表项(每个分区表项128字节,最多128个分区)
...
LBA -33~-1: GPT分区表项备份
LBA -34: GPT Header备份
我个人更喜欢GPT。原因很简单:它支持更大的磁盘(超过2TB),分区数量不受限,而且有备份机制。在嵌入式领域,如果你的芯片支持UEFI启动,我建议优先用GPT。
分区表在Bootloader中的实际应用
说了这么多理论,咱们来点实际的。在BSP开发中,分区表通常是在烧写脚本或设备树里定义的。比如在U-Boot中,你可以这样定义分区:
// U-Boot环境变量中的mtdparts定义
mtdparts=nand0:1M(u-boot),4M(kernel),-(rootfs)
// 或者通过设备树描述
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x00000000 0x00100000>; // 1MB
};
partition@1 {
label = "kernel";
reg = <0x00100000 0x00400000>; // 4MB
};
partition@2 {
label = "rootfs";
reg = <0x00500000 0x0FB00000>; // 剩余空间
};
};
这里要注意,分区表的定义必须和Bootloader的烧写地址严格对应。比如U-Boot烧在0x0~0x100000,内核在0x100000~0x500000。如果对不上,系统就起不来。
避坑指南:我曾经在项目里把内核分区大小写小了,结果内核编译后超过了分区边界,烧写时没报错,但启动时直接挂掉。查了三天才发现是分区大小不够。所以,分区大小一定要留余量,尤其是内核分区,建议多留30%的空间。
分区表与Bootloader的协作关系
为了让你更直观地理解,我画了一张图:
从这张图可以看出,分区表是整个存储布局的“总指挥”。Bootloader启动时,首先读取分区表,找到自己的位置,然后根据分区表找到内核和设备树的位置,最后加载内核并挂载根文件系统。
嵌入式系统中分区表的特殊之处
和PC不同,嵌入式系统的分区表往往不是由用户手动创建的,而是在烧写镜像时就固化好的。我们通常会在烧写脚本里直接指定每个分区的偏移和大小。比如:
# 烧写脚本示例(fastboot)
fastboot flash partition:1 bootloader.bin
fastboot flash partition:2 kernel.img
fastboot flash partition:3 rootfs.img
或者更常见的,通过分区表镜像一次性烧写:
# 使用dd命令烧写整个分区表+数据
dd if=full_image.bin of=/dev/mmcblk0 bs=1M
重要提醒:千万不要在生产环境中随意修改分区表!我见过有同事在产线上为了测试临时改了分区大小,结果忘了改回来,导致后续批次的产品全部变砖。分区表一旦确定,就要作为基线管理起来,任何修改都要走变更流程。
小结
分区表是BSP开发中最基础也最容易忽略的一环。它决定了Bootloader、内核、文件系统在存储设备上的布局。理解主分区、扩展分区、逻辑分区的区别,知道MBR和GPT的位置,能帮你避免很多低级错误。
我个人建议,在开始任何Bootloader开发之前,先把分区表画出来。用一张纸,把每个分区的起始地址、大小、用途写清楚。这张纸,就是你的“藏宝图”。