这一章我们主要介绍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全称GUIDPartitionTable,它是为了替代MBR分区表,使得能够支持更大空间的磁盘而设计的分区方案。现代的操作系统基本上都支持GPT分区表。UEFI规范明确使用GPT分区表的EFI系统分区进行系统引导工作。我们先看一下GPT的全景图
GPT分区同样使用LBA寻址,正如在图上显示的磁盘中的第一个LBA0中保存的是保护性MBR(protectiveMBR),这个扇区是磁盘的第一个扇区,早期为了向前兼容,通常保留不用,但现在也为了防止基于MBR的工具错误识别从而导致破坏GPT分区的作用,而且这个扇区的类型设置为0xEE来表示它是一个保护性的MBR。LBA1(第二个扇区)保存的是GPT分区头,这个分区中包含了有关GPT分区详细的特征数据。对于扇区大小为512字节的硬盘,从LBA2-LBA33保存的是分区表数据,一个分区项占用128个字节。而对于4096字节大小的扇区只需要4个扇区保留分区表就可以。磁盘的最后保存了分区表的一个备份,这个备份包含了全部的分区表和分区头信息。
对于LBA1的GPT头的详细数据如下
偏移量 | 长度 | 描述 |
0(0x00) | 8bytes | 签名("EFIPART",45h46h49h20h50h41h52h54h/0x5452415020494645ULL[a]小端序) |
8(0x08) | 4bytes | 版本1.0(00h00h01h00h)forUEFI2.8 |
12(0x0C) | 4bytes | 以小端序的头大小(二进制5Ch00h00h00h或者92字节) |
16(0x10) | 4bytes | 以小端序头的CRC32 |
20(0x14) | 4bytes | 保留,设置为0 |
24(0x18) | 8bytes | 头的位置 |
32(0x20) | 8bytes | 头的备份位置 |
40(0x28) | 8bytes | 第一个可用的LBA |
48(0x30) | 8bytes | 最后一个可用的LBA |
56(0x38) | 16bytes | 混合字节序的GUID |
72(0x48) | 8bytes | 分区表的起始位置 |
80(0x50) | 4bytes | 有多少个分区项 |
84(0x54) | 4bytes | 每个分区项的大小 |
88(0x58) | 4bytes | 分区项的CRC32 |
92(0x5C) | * | 保留,后面的直接设置为0 |
分区表项中的详细数据
偏移量 | 长度 | 描述 |
0(0x00) | 16bytes | 具有特殊含义的混合字节序GUID,表明分区类型 |
16(0x10) | 16bytes | 混合字节序的GUID,标志这个分区 |
32(0x20) | 8bytes | 此分区第一个LBA,小段序 |
40(0x28) | 8bytes | 此分区最后 |
48(0x30) | 8bytes | 属性字段 |
56(0x38) | 72bytes | 分区名称 |
这个表主要重点关注的是第一项和第二项,第一项由预定义的值,这些值表明不同的含义,比如C12A7328-F81F-11D2-BA4B-00A0C93EC93B就表示EFI系统分区,是EFI专用的分区。而第二个是可以随机生成的,标志这个分区。
一个GPT的例子
在这里我们使用的系统是使用efi方式安装的ubuntu虚拟机,首先我们先使用fdisk工具打印磁盘的一些信息
$sudofdisk-l/dev/sdaDisk/dev/sda:50GiB,53687091200bytes,104857600sectorsDiskmodel:VBOXHARDDISKUnits:sectorsof1*512=512bytesSectorsize(logical/physical):512bytes/512bytesI/Osize(minimum/optimal):512bytes/512bytesDisklabeltype:gptDiskidentifier:2A7B0333-1C74-443D-8BA2-162A6F4733F7DeviceStartEndSectorsSizeType/dev/sda1204810506231048576512MEFISystem/dev/sda2105062410485555110380492849.5GLinuxfilesystem
从这个信息中,我们知道这个虚拟机使用了两个分区,sda1是EFI系统分区的类型,sda2是Linux文件系统的分区。下面我们将使用gpt分析磁盘中的真实数据是否如fdisk中显示的一致。
和在MBR中一样我们使用的还是gpt工具,首先我们需要先获取第一个lba,即lba0
$pipinstallgpt#pip我一般通过virtualenv使用$sudoddif=/dev/sbabs=512count=1skip=0>lba.0
这个命令将会将sda设备中的第一个扇区读取出来并且保存到lba.0中,然后我们使用gpt工具进行
$catlba.0|print_mbr<<<MBR>>>BootCode:0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000UniqueMBRDiskSignature:0x00000000Unknown:0x0000PartitionRecord:0x00000200eeffffff01000000ffff3f06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Signature:0xAA55<<<MBRPartition#0>>>#0.BootIndicator:0x0#0.IsBootable?(syn):No#0.StartingCHS:0,0,2#0.OSType:0xEE#0.OSType(syn):GPTProtective#0.EndingCHS:255,255,255#0.StartingLBA:1#0.SizeInLBA:104857599<<<MBRPartition#1>>>#1.BootIndicator:0x0#1.IsBootable?(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<<<MBRPartition#2>>>#2.BootIndicator:0x0#2.IsBootable?(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<<<MBRPartition#3>>>#3.BootIndicator:0x0#3.IsBootable?(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的数据
$sudoddif=/dev/sdbbs=512count=1skip=1>lba.1$catlba.1|print_gpt_headerWarning:Usingonlythefirst92bytesofinput<<<GPTHeader>>>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找到对应的备份数据
$catlba.104857599|print_gpt_headerWarning:Usingonlythefirst92bytesofinput<<<GPTHeader>>>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中。找到对应的分区表数据
$sudoddif=/dev/sdabs=512count=32skip=2>lba.2-34$catlba.2-34|print_gpt_partition_entry_array<<<GPTPartitionEntry#0>>>#0.PartitionTypeGUID:0x28732ac11ff8d211ba4b00a0c93ec93b#0.PartitionTypeGUID(syn):c12a7328-f81f-11d2-ba4b-00a0c93ec93b#0.PartitionType(syn):EFISystemPartition#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):EFISystemPartition<<<GPTPartitionEntry#1>>>#1.PartitionTypeGUID:0xaf3dc60f838472478e793d69d8477de4#1.PartitionTypeGUID(syn):0fc63daf-8483-4772-8e79-3d69d8477de4#1.PartitionType(syn):Linuxfilesystemdata#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规范中有一段描述说明了这个原因:"avoidtheneedtodeterminethephysicalblocksizeandtheoptim altransferlengthgranularity,softwaremayalignGPTpartitionsatsignificantlylargerboundaries.Forexample,assuminglogicalblock0isaligned,itmayuseLBAsthataremultiplesof2,048toalignto1,048,576byte(1MiB)boundaries,whichsupportsmostcommonphysicalblocksizesandRAIDstripesizes."
UEFI
UEFI通常有一个固件策略引擎,它可以通过efibootmgr进行配置,而且UEFI还支持安全启动,这里不会介绍关于安全的。使用efibootmgr来配置启动项,也可以在UEFI的GUI中配置,每个厂商进入UEFI配置界面的方式也不一样。
efibootmgr
我们可以使用efibootmgr-v查看引导顺序
$sudoefibootmgr-vBootCurrent:0005Timeout:0secondsBootOrder:0005,0000,0001,0002,0003,0004Boot0000*UiAppFvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331)Boot0001*UEFIVBOXCD-ROMVB0-01f003f6PciRoot(0x0)/Pci(0x1,0x1)/Ata(0,0,0)N.....YM....R,Y.Boot0002*UEFIVBOXCD-ROMVB2-01700376PciRoot(0x0)/Pci(0x1,0x1)/Ata(1,0,0)N.....YM....R,Y.Boot0003*UEFIVBOXHARDDISKVBf5cb0757-31383519PciRoot(0x0)/Pci(0xd,0x0)/Sata(0,65535,0)N.....YM....R,Y.Boot0004*EFIInternalShellFvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1)Boot0005*ubuntuHD(1,GPT,6dee24a7-4fe0-466f-b6f8-546e2048aceb,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
这个显示默认从编号0005进行引导(BootCurrent:0005),而Boot0005对应的是Boot0005*ubuntuHD(1,GPT,6dee24a7-4fe0-466f-b6f8-546e2048aceb,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
这里我们不会详细的介绍EFI对Linux的操作系统的引导,后面会有一个详细的介绍UEFI在linux引导中的使用。
总结
通过这篇文章我们了解EFI的工作机制,它使用一个固件策略引擎来根据配置来初始化计算机,引导操作系统启动,它通常和GPT分区一起使用,虽然BIOS也支持GPT中启动,我们这里不关心这种场景。这个章节我们也详细了解GPT的分区格式。