这一章我们主要介绍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分区表又是什么?下面我们来揭开这些面纱吧!

GPT

GPT全称GUID Partition Table,它是为了替代MBR分区表,使得能够支持更大空间的磁盘而设计的分区方案。现代的操作系统基本上都支持GPT分区表。UEFI规范明确使用GPT分区表的EFI系统分区进行系统引导工作。我们先看一下GPT的全景图

计算机的启动方式有哪些(计算机启动知识系列UEFI/GPT)(1)

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: 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000UniqueMBRDiskSignature:                                         0x00000000Unknown:                                                            0x0000PartitionRecord: 0x00000200eeffffff01000000ffff3f06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Signature:                                                          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的头信息,这个头显示了一些详细的信息

然后,我们按照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规范

计算机的启动方式有哪些(计算机启动知识系列UEFI/GPT)(2)

这个图非常详细展示了一个磁盘的空间布局,开始是一个保护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个有效的分区数据,其中第一分区

第二个分区

这两个分区显示的和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."

UEFI

UEFI通常有一个固件策略引擎,它可以通过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的分区格式。