这一章我们主要介绍UEFI固件和GPT分区格式,通过上面文章我们也知道BIOS所存在的缺点,而UEFI就是为了解决这些问题。UEFI除了提供BIOS解决的问题外,它同时也提供了更加丰富的图形界面,对用户更加的友好,而且EFI程序支持C语言编写,提升了工作效率。但相对了,我们的工作都是在重复一个步骤,就是简单,发现简单无法解决更多的问题,而引入了复杂化,而UEFI就存在这个问题,UEFI的引入使得设计更加的复杂。也许,以后为了简单性,由出现了新的固件技术也说不定。
UFEI与GPT在这里我们不会介绍UEFI的历史,我们重点关注UEFI的角色,如何编写UEFI程序以及支持UEFI的计算机如何启动的。我们关于UEFI的文章主要围绕这些问题展开,首先我们需要了解UEFI,UEFI是为了解决BIOS所存在的问题而从新设计的一套固件系统,它不是BIOS,虽然UEFI也引入了兼容模式,兼容模式可以让我们像BIOS那样启动系统,但记住 UEFI不是BIOS。
在概述中,我们知道支持UEFI引导的计算机启动后交给UEFI固件,后续由UEFI进行后续的工作,在这里我们重点关注UEFI的引导功能,同时为了解决MBR分区格式无法支持更大的磁盘空间引入了GPT分区,GPT分区也是UEFI规范中的内容。UEFI 会根据设置的启动顺序,查找GPT分区表中 GUID 为 C12A7328-F81F-11D2-BA4B-00A0C93EC93B 的EFI分区表。找到EFI分区表后,UEFI 将会把系统权限交给对应的引导程序,后续的引导程序负责引导操作系统启动。这里GPT分区是什么?C12A7328-F81F-11D2-BA4B-00A0C93EC93B 这串莫名其妙的数据又是什么?EFI分区表又是什么?下面我们来揭开这些面纱吧!
GPTGPT全称GUID Partition Table,它是为了替代MBR分区表,使得能够支持更大空间的磁盘而设计的分区方案。现代的操作系统基本上都支持GPT分区表。UEFI规范明确使用GPT分区表的EFI系统分区进行系统引导工作。我们先看一下GPT的全景图
GPT分区同样使用LBA寻址,正如在图上显示的磁盘中的第一个LBA0中保存的是保护性MBR(protective MBR ),这个扇区是磁盘的第一个扇区,早期为了向前兼容,通常保留不用,但现在也为了防止基于MBR的工具错误识别从而导致破坏GPT分区的作用,而且这个扇区的类型设置为 0xEE 来表示它是一个保护性的MBR。LBA1(第二个扇区)保存的是GPT分区头,这个分区中包含了有关GPT分区详细的特征数据。对于扇区大小为512字节的硬盘,从LBA2-LBA33保存的是分区表数据,一个分区项占用128个字节。而对于4096字节大小的扇区只需要4个扇区保留分区表就可以。磁盘的最后保存了分区表的一个备份,这个备份包含了全部的分区表和分区头信息。
对于LBA1的GPT头的详细数据如下
偏移量 | 长度 | 描述 |
0 (0x00) | 8 bytes | 签名 ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h / 0x5452415020494645ULL[a] 小端序) |
8 (0x08) | 4 bytes | 版本 1.0 (00h 00h 01h 00h) for UEFI 2.8 |
12 (0x0C) | 4 bytes | 以小端序的头大小 (二进制5Ch 00h 00h 00h或者92字节) |
16 (0x10) | 4 bytes | 以小端序头的CRC32 |
20 (0x14) | 4 bytes | 保留,设置为0 |
24 (0x18) | 8 bytes | 头的位置 |
32 (0x20) | 8 bytes | 头的备份位置 |
40 (0x28) | 8 bytes | 第一个可用的LBA |
48 (0x30) | 8 bytes | 最后一个可用的LBA |
56 (0x38) | 16 bytes | 混合字节序的GUID |
72 (0x48) | 8 bytes | 分区表的起始位置 |
80 (0x50) | 4 bytes | 有多少个分区项 |
84 (0x54) | 4 bytes | 每个分区项的大小 |
88 (0x58) | 4 bytes | 分区项的CRC32 |
92 (0x5C) | * | 保留,后面的直接设置为0 |
分区表项中的详细数据
偏移量 | 长度 | 描述 |
0 (0x00) | 16 bytes | 具有特殊含义的混合字节序GUID,表明分区类型 |
16 (0x10) | 16 bytes | 混合字节序的GUID,标志这个分区 |
32 (0x20) | 8 bytes | 此分区第一个LBA,小段序 |
40 (0x28) | 8 bytes | 此分区最后 |
48 (0x30) | 8 bytes | 属性字段 |
56 (0x38) | 72 bytes | 分区名称 |
这个表主要重点关注的是第一项和第二项,第一项由预定义的值,这些值表明不同的含义,比如 C12A7328-F81F-11D2-BA4B-00A0C93EC93B 就表示EFI系统分区,是EFI专用的分区。而第二个是可以随机生成的,标志这个分区。
一个GPT的例子在这里我们使用的系统是使用efi方式安装的ubuntu虚拟机,首先我们先使用fdisk工具打印磁盘的一些信息
$ sudo fdisk -l /dev/sdaDisk /dev/sda: 50 GiB, 53687091200 bytes, 104857600 sectorsDisk model: VBOX HARDDISKUnits: sectors of 1 * 512 = 512 bytesSector size 电脑 (logical/physical): 512 bytes / 512 bytesI/O size (minimum/optimal): 512 bytes / 512 bytesDisklabel type: gptDisk identifier: 2A7B0333-1C74-443D-8BA2-162A6F4733F7Device Start End Sectors Size Type/dev/sda1 2048 1050623 1048576 512M EFI System/dev/sda2 1050624 104855551 103804928 49.5G Linux filesystem
从这个信息中,我们知道这个虚拟机使用了两个分区,sda1是EFI系统分区的类型,sda2是Linux文件系统的分区。下面我们将使用gpt分析磁盘中的真实数据是否如fdisk中显示的一致。
和在MBR中一样我们使用的还是gpt工具,首先我们需要先获取第一个lba,即lba0
$ pip install gpt # pip 我一般通过virtualenv使用$ sudo dd if=/dev/sba bs=512 count=1 skip=0 > lba.0
这个命令将会将sda设备中的第一个扇区读取出来并且保存到lba.0中,然后我们使用gpt工具进行
$ cat lba.0 | print_mbr<<< MBR >>>BootCode: 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000电脑000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000UniqueMBRDiskSignature: 0x00000000Unknown: 0x0000PartitionRecord: 0x00000200eeffffff01000000ffff3f060000000000000电脑00000000000000000000000000000000000000000000000000000000000000000000000000000000000Signature: 0xAA55<<< MBR Partition #0 >>>#0.BootIndicator: 0x0#0.Is Bootable? (syn): No#0.StartingCHS: 0, 0, 2#0.OSType: 0xEE#0.OSType (syn): GPT Protective#0.EndingCHS: 255, 255, 255#0.StartingLBA: 1#0.SizeInLBA: 104857599<<< MBR Partition #1 >>>#1.BootIndicator: 0x0#1.Is Bootable? (syn): No#1.StartingCHS: 电脑 0, 0, 0#1.OSType: 0x0#1.OSType (syn): Empty#1.EndingCHS: 0, 0, 0#1.StartingLBA: 0#1.SizeInLBA: 0<<< MBR Partition #2 >>>#2.BootIndicator: 电脑 0x0#2.Is Bootable? (syn): No#2.StartingCHS: 0, 0, 0#2.OSType: 0x0#2.OSType (syn): Empty#2.EndingCHS: 0, 0, 0#2.StartingLBA: 0#2.SizeInLBA: 0<<< MBR Partition #3 >>>#3.BootIndicator: 0x0#3.Is Bootable? (syn): No#3.StartingCHS: 0, 0, 0#3.OSType: 0x0#3.OSType (syn): Empty#3.EndingCHS: 0, 0, 0#3.StartingLBA: 0#3.SizeInLBA: 0
0xAA55 的签名显示这个一个有效的MBR,它的446个字节的引导代码都是0,并且类型显示是 0xEE 说明它是GPT的保护性MBR,GPT的实际分区以LBA1开始,下面打印lba1的数据
$ sudo dd if=/dev/sdb bs=512 count=1 skip=1 > lba.1$ cat lba.1 | print_gpt_headerWarning: Using only the first 92 bytes of input<<< GPT Header >>>Signature: 0x4546492050415254Revision: 0x00000100HeaderSize: 92HeaderCRC32: 0xad41c4b0HeaderCRC32 (calculated): 0xad41c4b0Reserved: 0x00000000MyLBA: 1AlternateLBA: 104857599FirstUsableLBA: 34LastUsableLBA: 104857566PartitionEntryLBA: 2NumberOfPartitionEntries: 128SizeOfPartitionEntry: 128PartitionEntryArrayCRC32: 0x1bc39ab0
LBA1是GPT的头信息,这个头显示了一些详细的信息
首先头位于LBA1
备份信息位于104857599(AlternateLBA)
第一个可用的扇区位于LBA-34(FirstUsableLBA),和我们之前说的一致,512字节扇区需要33个扇区包含分区信息,实际数据从34开始
最后一个可用的扇区位于LBA-104857566(LastUsableLBA)
分区表信息从LBA-2开始(PartitionEntryLBA)
这个分区表有128项(NumberOfPartitionEntries),每一项都有128个字节(SizeOfPartitionEntry)
然后,我们按照AlternateLBA找到对应的备份数据
$ cat lba.104857599 | print_gpt_headerWarning: Using only the first 92 bytes of input<<< GPT Header >>>Signature: 0x4546492050415254Revision: 0x00000100HeaderSize: 92HeaderCRC32: 0x4d7ce45aHeaderCRC32 (calculated): 0x4d7ce45aReserved: 0x00000000MyLBA: 104857599AlternateLBA: 1FirstUsableLBA: 34LastUsableLBA: 104857566PartitionEntryLBA: 104857567NumberOfPartitionEntries: 128SizeOfPartitionEntry: 128PartitionEntryArrayCRC32: 0x1bc39ab0
需要注意的是这里的AlternateLBA和MyLBA和主gpt头正好相反。GPT中提供了校验数据HeaderCRC32和PartitionEntryArrayCRC32用于计算分区表数据是否有错误。这个机制在MBR中是没有的。下面这个图来自UEFI规范
这个图非常详细展示了一个磁盘的空间布局,开始是一个保护MBR,后面跟着的是主分区表然后就是各个分区,最后保存了分区表的一个备份。
那么,分区表中是什么样的呢,我们还是使用GPT,GPT的分区表在512字节的扇区保存在LBA2到LBA33中。找到对应的分区表数据
$ sudo dd if=/dev/sda bs=512 count=32 skip=2 > lba.2-34$ cat lba.2-34 | print_gpt_partition_entry_array<<< GPT Partition Entry #0 >>>#0.PartitionTypeGUID: 0x28732ac11ff8d211ba4b00a0c93ec93b#0.PartitionTypeGUID (syn): c12a7328-f81f-11d2-ba4b-00a0c93ec93b#0.PartitionType (syn): EFI System Partition#0.UniquePartitionGUID: 0xa724ee6de04f6f46b6f8546e2048aceb#0.UniquePartitionGUID (syn): 6dee24a7-4fe0-466f-b6f8-546e2048aceb#0.StartingLBA: 2048#0.EndingLBA: 1050623#0.Attributes: 0x0#0.Attributes (syn): []#0.PartitionName: 0x4500460049002000530079007300740065006d00200050006100720074006900740069006f006e000000000000000000000000000000000000000000000000000000000000000000#0.PartitionName (syn): EFI System Partition<<< GPT Partition Entry #1 >>>#1.PartitionTypeGUID: 0xaf3dc60f838472478e793d69d8477de4#1.PartitionTypeGUID (syn): 0fc63daf-8483-4772-8e79-3d69d8477de4#1.PartitionType (syn): Linux filesystem data#1.UniquePartitionGUID: 0xc070b4b9e4c6b94ba06df0ed5791f322#1.UniquePartitionGUID (syn): b9b470c0-c6e4-4bb9-a06d-f0ed5791f322#1.StartingLBA: 1050624#1.EndingLBA: 104855551#1.Attributes: 0x0#1.Attributes (syn): []#1.PartitionName: 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000#1.PartitionName (syn):<<< Calculated >>>PartitionEntryArrayCRC32 (calculated): 0x1bc39ab0
这里显示了2个有效的分区数据,其中第一分区
开始于LBA2048,结束于1050623扇区
由于PartitionTypeGUID的值为 c12a7328-f81f-11d2-ba4b-00a0c93ec93b 它是一个EFI系统分区
第二个分区
开始于LBA1050624,结束于104855551
它是一个普通的数据分区
这两个分区显示的和fdisk显示的是一致的。需要注意的是数据分区开始于2048,而不是34,这说明中间还有一块没有使用的间隙。在GPT规范中有一段描述说明了这个原因:"avoid the need to determine the physical block size and the optimal transfer length granularity, software may align GPT partitions at significantly larger boundaries. For example, assuming logical block 0 is aligned, it may use LBAs that are multiples of 2,048 to align to 1,048,576 byte (1 MiB) boundaries, which supports most common physical block sizes and RAID stripe sizes."
UEFIUEFI通常有一个固件策略引擎,它可以通过efibootmgr进行配置,而且UEFI还支持安全启动,这里不会介绍关于安全的。使用efibootmgr来配置启动项,也可以在UEFI的GUI中配置,每个厂商进入UEFI配置界面的方式也不一样。
efibootmgr我们可以使用 efibootmgr -v 查看引导顺序
$ sudo efibootmgr -vBootCurrent: 0005Timeout: 0 secondsBootOrder: 0005,0000,0001,0002,0003,0004Boot0000* UiApp FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331)Boot0001* UEFI VBOX CD-ROM VB0-01f003f6 PciRoot(0x0)/Pci(0x1,0x1)/Ata(0,0,0)N.....YM....R,Y.Boot0002* UEFI VBOX CD-ROM VB2-01700376 PciRoot(0x0)/Pci(0x1,0x1)/Ata(1,0,0)N.....YM....R,Y.Boot0003* UEFI VBOX HARDDISK VBf5cb0757-31383519 PciRoot(0x0)/Pci(0xd,0x0)/Sata(0,65535,0)N.....YM....R,Y.Boot0004* EFI Internal Shell FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1)Boot0005* ubuntu HD(1,GPT,6dee24a7-4fe0-466f-b6f8-546e2048aceb,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
这个显示默认从编号0005进行引导(BootCurrent: 0005),而Boot0005对应的是 Boot0005* ubuntu HD(1,GPT,6dee24a7-4fe0-466f-b6f8-546e2048aceb,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
这里我们不会详细的介绍EFI对Linux的操作系统的引导,后面会有一个详细的介绍UEFI在linux引导中的使用。
总结通过这篇文章我们了解EFI的工作机制,它使用一个固件策略引擎来根据配置来初始化计算机,引导操作系统启动,它通常和GPT分区一起使用,虽然BIOS也支持GPT中启动,我们这里不关心这种场景。这个章节我们也详细了解GPT的分区格式。
电脑 电脑