作者:一粒星尘ch | 来源:互联网 | 2023-05-17 11:34
目前Linux中使用最广泛的的bootloader是Grub(GRandUnifiedBootloader)。如今Grub2已经替换了早期的0.9x系列版本的GrubLegacy,而且Gru
目前Linux中使用最广泛的的bootloader是Grub(GRand Unified Bootloader)。如今Grub 2已经替换了早期的0.9x系列版本的Grub Legacy, 而且Grub Legacy已经不再开发维护。虽然Grub 2从名字上看像是Grub的升级版,但其源代码实际被完全重构了。现从源代码角度分析基于Grub 2.00的x86内核引导流程。
1. 磁盘简介
由于在Grub进行内核引导的过程中涉及到磁盘操作,先简介一下磁盘为后续引导流程分析作铺垫。磁盘三要素:由所有盘面上相同半径的同心圆形磁道(Track)组成的柱面(Cylinder), 磁头(Head), 扇区(Sector)之间的关系如下图所示:
对于磁盘,其最小存储单位为扇区(Sector),在相当长的一段时间里,扇区的大小固定在512 bytes[1]. 但是从2009年开始出现扇区大小为4096 bytes的磁盘,即Advanced Format disks。
对于扇区的编址,早期的方案是CHS编址(Cylinder-Head-Sector),即用数据元组CHS tuples (c,h,s)的形式表示一个扇区的位置,但是在CHS编址时,扇区号是从1开始的,没有扇区0,但磁头和柱面编号都是从0开始的,即CHS编址起始于地址(0,0,1)。另外一种编址方案是LBA编址(Logical Block Addressing),把整个磁盘的所有扇区资源统一分配序号。在2003年发布的ATA-6标准中,LBA采用48-bit地址。
CHS数据元组(c, h, s)根据如下公式转换成相应的LBA逻辑地址:
LBA = (c×Nheads + h)×Nsectors + (s − 1)
其中:Nheads是硬盘中的磁头数目,Nsectors是每条磁道上可以划分的最大的扇区数目。上面的公式意味着LBA对扇区的编址是从0开始的,所以在Grub的boot.S源代码中,当磁盘不支持LBA模式,代码执行流回退至CHS模式继续进行处理时,会将编址的起始地址调整成从1开始。
==================grub-2.00/grub-core/boot/i386/pc/boot.S=================
288 /* normalize sector start (1-based)*/
289 incb %cl
[1]Floppydisks and controllers use physical sector sizes of 128, 256, 512 and 1024 bytes(e.g., PC/AX), whereby formats with 512 bytes per physical sector becamedominant in the 1980s.
磁盘在使用过程中总是会涉及到分区方案,其中一个典型的四分区MBR磁盘结构如下所示:
其中:磁盘的0柱面、0磁头、1扇区即为主引导记录MBR(MasterBoot Record)扇区。它由三个部分组成:主引导程序bootloader、磁盘分区表和有效标志(0x55AA)。在总共512字节的主引导扇区里主引导程序(boot loader)占446个字节,第二部分是Partition table区(分区表),占64个字节,磁盘中分区有多少以及每一分区的大小都记在其中。第三部分是磁盘有效标志签名,占2个字节,固定为0x55AA。 另外对于MBR磁盘,由于扇区空间限制分区表最多占用64字节,每个分区表项占用16字节,所以其最多有4个分区。
Table 1 典型的MBR结构
Address |
Description |
Size |
Hex |
Dec |
(bytes) |
+000 |
+0 |
Bootloader code area |
446 |
+1BE |
+446 |
Partition entry №1 |
分区表 (Partition table) |
16 |
+1CE |
+462 |
Partition entry №2 |
16 |
+1DE |
+478 |
Partition entry №3 |
16 |
+1EE |
+494 |
Partition entry №4 |
16 |
+1FE |
+510 |
0x55 |
Boot signature |
2 |
+1FF |
+511 |
0xAA |
Total size: 446 + 4×16 + 2 |
512 |
后续对bootloader部分会有详细分析。先来分析磁盘分区表,其定义如下:
Table 2 磁盘分区表项(DiskPartition Table)结构
Offset |
Size |
Description |
0 |
byte |
Boot indicator bit flag: 0 = no, 0x80 = active |
1 |
byte |
Starting Head |
2 |
6 bits |
Starting Sector |
3 |
10 bits |
Starting Cylinder (High two bits are bit 6-7 of Starting Sector) |
4 |
byte |
System ID |
5 |
byte |
Ending Head |
6 |
6 bits |
Ending Sector |
7 |
10 bits |
Ending Cylinder (High two bits are bit 6-7 of Ending Sector) |
8 |
4 bytes |
Relative Sector (The partition's starting LBA value) |
12 |
4 bytes |
Partition Length (Total Sectors in partition) |
磁盘分区表项结构定义对柱面、磁头和扇区的大小分别限定在10 bits、8 bits和6 bits,
该种定义隐含着限制了磁盘的大小。分区表可描述磁盘容量的大小根据如下公式计算:
MaxCapacity =Nheads×Nsectors×Ncylinders×SectorSize
取(c,h,s)的上限便可得到最大磁盘容量:
MaxCapacity =256×64×1024×512 bytes = 8GB
即:现有的分区表项结构定义只能描述8GB容量以下的硬盘。
随着磁盘的容量越来越大,8GB的容量限制显然亟待扩容,最好的方式应该是重新定义分区表项结构,但不幸的是:对于容量大于8GB的磁盘,磁盘分区表项结构定义并没有更新,只是将其中CHS相关的结构体变量设置成一个不合理配置(Cylinder = 1023, Head = 255, Sector = 63),然后用32位LBA扇区地址(Relative Sector)和32位分区长度(PartitionLength)去描述某个磁盘分区,但是该用法依然会限制磁盘容量:
MaxCapacity =232 ⋅ 512 bytes = 2TB
让人感觉更不合理的是:在2003年发布的ATA-6标准中,LBA采用48-bit地址,在此分区表项定义中却只能使用其低32bit,这是一种自废武功的做法。为何不将结构体中空闲的CHS相关字段组合起来保存LBA-48的高16位呢?下面就是一种非官方的定义,悲哀的是:这种合理的定义并没有被实现。
Table 3 "Unofficial" 48bit LBA Proposed MBR Format
Offset |
Size |
Description |
0 |
byte |
Bit flags field: 1 = not bootable, 0x81 = active |
1 |
byte |
Signature-1 (0x14) |
2 |
2 bytes |
Partition Start LBA (high 16-bit of 48 bit value) |
4 |
byte |
System ID |
5 |
byte |
Signature-2 (0xeb) |
6 |
2 bytes |
Partition Length (high 16-bit of 48 bit value) |
8 |
4 bytes |
Partition Start LBA |
12 |
4 bytes |
Partition Length |
追根溯源,对于MBR磁盘,由于扇区空间大小的限制,留给磁盘分区表项的空间只有64 bytes,仅有的这点空间还要平均分配给若干个分区。清晰描述一个分区所需的最小空间需求摆在那里,无论怎么设计磁盘分区表项,用一个受限的分区表空间去描述的磁盘空间也同样是受限的。于是为了解决MBR磁盘分区表可描述磁盘容量受限的问题,在2004年,Wintel在共同推出的一种可扩展固件接口(EFI: Extensible Firmware Interface)的主板升级换代方案中提出了GPT分区模式。
GPT(Globally Unique Identifier Partition Table Format)磁盘分区模式虽然是EFI方案的一部分,但并不依赖于EFI主板,在BIOS主板的PC中也可使用GPT分区。与MBR分区方案的最大4个分区表项的限制相比,GPT对分区数量没有限制。GPT还允许将主磁盘分区表和备份磁盘分区表用于冗余,支持唯一的磁盘和分区GUID。GPT分区方案目前在Windows操作系统上应用比较广泛。GPT分区方案示意图如下:
为了前向兼容性,在GPT分区方案中传统的MBR空间被保留下来,但是其用途是防止基于MBR分区的磁盘工具误判以及可能误写GPT磁盘。这也是第一个逻辑扇区LBA0称为保护分区PMBR(Protective MBR)的缘由。
Linux支持通过BIOS从基于GPT的磁盘中进行引导,即所谓的Hybrid MBR模式。该模式下保护分区PMBR与传统的MBR作用相同:该扇区被用来存放引导程序bootloader第一阶段(Stage 1)的代码,但是磁盘分区表项中仅包含一种类型为0xEE的主分区表项(Primary Partition Entry)。
Note: 后续内核引导流程分析时,以当下主流的MBR分区磁盘为主,GPT磁盘仅作提及,不作详述。