启动管理
启动管理(Boot Manager)是UEFI BIOS中重要的一部分,它通过一系列的变量来确定启动策略,包括:
- 执行启动还是恢复操作
- 启动顺序是如何
本文会介绍下面的内容:
- 与启动管理相关的变量
- 启动或恢复的流程策略
- 加载项结构体
- 创建加载项
注意本文只关注UEFI BIOS的启动项,涉及的是UEFI BIOS启动管理,至于Legacy的,这里不会描述。
另外,虽然是“启动管理”,但是这里的“启动”并不都是“启动操作系统”的意思,代码操作的也不叫“启动项”,而是“加载项”(Load Option),后面文档中出现的“启动”也并不一定是启动操作系统的意思,也可能仅仅是加载一个UEFI应用或UEFI驱动。
启动一般流程
以下是《UEFI Spec》中的一般规定:
- 如果
OsIndications
指定了要执行恢复操作,则不会进入通常的启动流程,而是进入恢复选项。 Driver####
会优先于Boot####
执行。- 执行Boot加载项时,先执行
BootNext
对应的加载项,然后按照BootOrder
中的顺序执行Boot####
。 - 如果
BootNext
和Boot####
中的执行都失败了,则进入启动项恢复操作,主要就是PlatformRecovery####
。
实际上真实的BIOS中,其实现可以有很多中,因此并不完全按照上述的规则。比如最后一步BootNext
和Boot####
中的执行都失败,一般会选择进入Setup,而不是PlatformRecovery####
。
启动管理变量
- 真正描述加载项的变量:
变量名 | 作用域 | 说明 |
---|---|---|
Boot#### | NV、BS、RT | A boot load option. |
Driver#### | NV、BS、RT | A driver load option. |
SysPrep#### | NV、BS、RT | A System Prep application load option containing a EFI_LOAD_OPTION descriptor. |
OsRecovery#### | NV、BS、RT | |
PlatformRecovery#### | NV、BS、RT | Platform-specified recovery options. These variables are only modified by firmware and are read-only to the OS. |
需要注意这些变量的名称是Load Option,并不是叫启动项,虽然流程上差不多,但是很多并不是为了启动操作系统,而是一些额外的操作:
Boot####
:这些算是普通启动时的启动项。Driver####
:这些是用来加载驱动的。SysPrep####
:对应的时UEFI应用,用来为系统启动做准备的,它们会在Boot####
之前执行。PlatformRecovery####
:这些是在最后执行的,所以其它的启动都失败之后就会执行这些。
这四种类型对应到代码中:
//
// Load Option Type
//
typedef enum {
LoadOptionTypeDriver,
LoadOptionTypeSysPrep,
LoadOptionTypeBoot,
LoadOptionTypePlatformRecovery,
LoadOptionTypeMax
} EFI_BOOT_MANAGER_LOAD_OPTION_TYPE;
有几点需要注意的:
Boot####
和Driver####
是正常启动的时候都是执行的。- 启动恢复项有两类,
PlatformRecovery####
和OsRecovery####
,后者没有出现在《UEFI Spec》的“Table 10. Global Variables”中,因为它使用的是其它的GUID(称为Vendor Specific VendorGuid)。
上述的变量因为有多个,所以还有一个顺序关系,对应到一个变量:
变量名 | 作用域 | 说明 |
---|---|---|
BootOrder | NV、BS、RT | The ordered boot option load list. |
DriverOrder | NV、BS、RT | The ordered driver load option list. |
SysPrepOrder | NV、BS、RT | The ordered System Prep Application load option list. |
OsRecoveryOrder | NV、BS、RT | OS-specified recovery options. |
但是没有PlatformRecoveryOrder
,可以认为只有一个PlatformRecovery应用,所以不需要顺序吧。
另外OsRecoveryOrder
出现在《UEFI Spec》的“Table 10. Global Variables”中也比较奇怪,因为前面已经说了它应该属于VendorGuid的变量才对,并且OsRecoveryOrder
也没有出现在EDK代码中。
- 启动相关的变量:
变量名 | 作用域 | 说明 |
---|---|---|
BootCurrent | BS、RT | The boot option that was selected for the current boot. |
BootNext | NV、BS、RT | The boot option for the next boot only. |
BootCurrent
因为是随时可变的,所以不会存放到非易失变量中。
- 配置相关的变量:
变量名 | 作用域 | 说明 |
---|---|---|
BootOptionSupport | BS、RT | The types of boot options supported by the boot manager. Should be treated as read-only. |
OsIndications | NV、BS、RT | Allows the OS to request the firmware to enable certain features and to take certain actions. |
OsIndicationsSupported | BS、RT | Allows the firmware to indicate supported features and actions to the OS. |
BootOptionSupport
对应的取值:
#define EFI_BOOT_OPTION_SUPPORT_KEY 0x00000001
#define EFI_BOOT_OPTION_SUPPORT_APP 0x00000002
#define EFI_BOOT_OPTION_SUPPORT_SYSPREP 0x00000010
#define EFI_BOOT_OPTION_SUPPORT_COUNT 0x00000300
这表示的其实是启动管理的能力级,其能力说明如下:
EFI_BOOT_OPTION_SUPPORT_KEY
置位表示Boot####
可以通过快捷键启动。EFI_BOOT_OPTION_SUPPORT_APP
置位表示支持启动带LOAD_OPTION_CATEGORY_APP
属性的项。EFI_BOOT_OPTION_SUPPORT_SYSPREP
置位表示支持启动SysPrep####
。EFI_BOOT_OPTION_SUPPORT_COUNT
指定了快捷键支持的按键数。
OsIndicationsSupported
表示BIOS告知OS的BIOS所支持的特性,而OsIndications
是OS告知BIOS的目前使用的特性。所以OsIndications
是OsIndicationsSupported
的一个子集,目前支持的值有:
//
// Firmware should stop at a firmware user interface on next boot
//
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001
#define EFI_OS_INDICATIONS_TIMESTAMP_REVOCATION 0x0000000000000002
#define EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 0x0000000000000004
#define EFI_OS_INDICATIONS_FMP_CAPSULE_SUPPORTED 0x0000000000000008
#define EFI_OS_INDICATIONS_CAPSULE_RESULT_VAR_SUPPORTED 0x0000000000000010
#define EFI_OS_INDICATIONS_START_PLATFORM_RECOVERY 0x0000000000000040
#define EFI_OS_INDICATIONS_JSON_CONFIG_DATA_REFRESH 0x0000000000000080
其中有一些跟启动有关:
EFI_OS_INDICATIONS_BOOT_TO_FW_UI
:表示进入启动管理菜单。EFI_OS_INDICATIONS_START_PLATFORM_RECOVERY
:表示进入到平台恢复项。
启动项结构体
其结构体位于edk2\MdePkg\Include\Uefi\UefiSpec.h:
#pragma pack(1)
typedef struct _EFI_LOAD_OPTION {
///
/// The attributes for this load option entry. All unused bits must be zero
/// and are reserved by the UEFI specification for future growth.
///
UINT32 Attributes;
///
/// Length in bytes of the FilePathList. OptionalData starts at offset
/// sizeof(UINT32) + sizeof(UINT16) + StrSize(Description) + FilePathListLength
/// of the EFI_LOAD_OPTION descriptor.
///
UINT16 FilePathListLength;
///
/// The user readable description for the load option.
/// This field ends with a Null character.
///
// CHAR16 Description[];
///
/// A packed array of UEFI device paths. The first element of the array is a
/// device path that describes the device and location of the Image for this
/// load option. The FilePathList[0] is specific to the device type. Other
/// device paths may optionally exist in the FilePathList, but their usage is
/// OSV specific. Each element in the array is variable length, and ends at
/// the device path end structure. Because the size of Description is
/// arbitrary, this data structure is not guaranteed to be aligned on a
/// natural boundary. This data structure may have to be copied to an aligned
/// natural boundary before it is used.
///
// EFI_DEVICE_PATH_PROTOCOL FilePathList[];
///
/// The remaining bytes in the load option descriptor are a binary data buffer
/// that is passed to the loaded image. If the field is zero bytes long, a
/// NULL pointer is passed to the loaded image. The number of bytes in
/// OptionalData can be computed by subtracting the starting offset of
/// OptionalData from total size in bytes of the EFI_LOAD_OPTION.
///
// UINT8 OptionalData[];
} EFI_LOAD_OPTION;
#pragma pack()
其成员说明如下(注意代码中后面三个成员注释掉了,但是并不表示它们不存在,只是因为Description
的长度是不确定的,所以后面成员没有固定位置,导致这个结构体是不定长结构体):
Attributes
:启动项的属性,包括:
//
// EFI Load Options Attributes
//
#define LOAD_OPTION_ACTIVE 0x00000001 // 该属性决定启动管理会不会自动从这个启动项启动
#define LOAD_OPTION_FORCE_RECONNECT 0x00000002 // 如果DRIVER####类型的启动项有该属性,则当所有DRIVER####类型的启动项执行完毕之后
// 会对所有的UEFI Dirver进行disconnect和reconnect
#define LOAD_OPTION_HIDDEN 0x00000008 // 表示这个选项不会出现在启动项选择界面
#define LOAD_OPTION_CATEGORY 0x00001F00 // 这个要结合下面两个一起使用,相当于一个掩码
// 例如 if ((Attributes & LOAD_OPTION_CATEGORY) == LOAD_OPTION_CATEGORY_BOOT)
#define LOAD_OPTION_CATEGORY_BOOT 0x00000000 // 表示这个启动项是普通启动的一部分
#define LOAD_OPTION_CATEGORY_APP 0x00000100 // 表示这个启动项虽然不用于普通启动,但是可以通过快捷键或者启动选项选择界面启动
FilePathListLength
:FilePathList
的长度,这个成员的存在是为了计算OptionalData
在结构体中的偏移,并且根据整个结构体的大小就可以知道OptionalData
的大小。Description
:描述启动项的字符串,会在启动项选择界面显示,以供选择。FilePathList
:一个Device Path数组,第一个成员(即FilePathList[0]
)是必须的,表示该启动项对应镜像(比如bootx64.efi)所在的设备及位置,BIOS本身只需要使用这一项即可,但是OS可能使用该数组的其它选项。OptionalData
:可选的数组,会传递给加载后的镜像。
以上是在《UEFI Spec》中定义的启动项,是作为BOOT####
或Driver####
启动项存放到非易失介质中的就是这个结构体。当启动管理使用这些启动项的时候,会有进一步的扩展:
//
// Common structure definition for DriverOption and BootOption
//
typedef struct {
//
// Data read from UEFI NV variables
//
UINTN OptionNumber; // #### numerical value, could be LoadOptionNumberUnassigned
EFI_BOOT_MANAGER_LOAD_OPTION_TYPE OptionType; // LoadOptionTypeBoot or LoadOptionTypeDriver
UINT32 Attributes; // Load Option Attributes
CHAR16 *Description; // Load Option Description
EFI_DEVICE_PATH_PROTOCOL *FilePath; // Load Option Device Path
UINT8 *OptionalData; // Load Option optional data to pass into image
UINT32 OptionalDataSize; // Load Option size of OptionalData
EFI_GUID VendorGuid;
//
// Used at runtime
//
EFI_STATUS Status; // Status returned from boot attempt gBS->StartImage ()
CHAR16 *ExitData; // Exit data returned from gBS->StartImage ()
UINTN ExitDataSize; // Size of ExitData
} EFI_BOOT_MANAGER_LOAD_OPTION;
成员说明如下:
OptionNumber
:就是####
的值。OptionType
:启动项的属性,对应的取值:
//
// Load Option Type
//
typedef enum {
LoadOptionTypeDriver,
LoadOptionTypeSysPrep,
LoadOptionTypeBoot,
LoadOptionTypePlatformRecovery,
LoadOptionTypeMax
} EFI_BOOT_MANAGER_LOAD_OPTION_TYPE;
实际上就对应到了不同的启动项变量。
Attributes
:同EFI_LOAD_OPTION
中的同名成员变量。Description
:同EFI_LOAD_OPTION
中的同名成员变量,不过需要注意一个是数组,一个是指针。显然数组才能存放到非易失介质中。FilePath
:指向EFI_LOAD_OPTION
中的成员FilePathList[0]
。OptionalData
:同EFI_LOAD_OPTION
中的同名成员变量。OptionalDataSize
:OptionalData
的大小。VendorGuid
:启动项变量对应的GUID,它一般是固定的EFI_GLOBAL_VARIABLE
。Status
、ExitData
、ExitDataSize
:gBS->StartImage()
的返回值,返回数据和返回数据大小。
启动管理实际上用的更多的是这个结构体。从EFI_LOAD_OPTION
到EFI_BOOT_MANAGER_LOAD_OPTION
的转变可以参考函数(位于edk2\MdeModulePkg\Library\UefiBootManagerLib\BmLoadOption.c):
/**
Build the Boot#### or Driver#### option from the VariableName.
@param VariableName Variable name of the load option
@param VendorGuid Variable GUID of the load option
@param Option Return the load option.
@retval EFI_SUCCESS Get the option just been created
@retval EFI_NOT_FOUND Failed to get the new option
**/
EFI_STATUS
EFIAPI
EfiBootManagerVariableToLoadOptionEx (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN OUT EFI_BOOT_MANAGER_LOAD_OPTION *Option
)
创建启动项
BIOS进入到BDS阶段,其流程中跟启动项有关的操作大致包含如下的内容(edk2\MdeModulePkg\Universal\BdsDxe\BdsEntry.c):
当然这只是一个简单的版本,实际上它包含很多变量获取以及根据变量值执行不同的启动策略的操作,不过这里主要关心的是启动项的创建,所以比较重要的是函数PlatformBootManagerBeforeConsole()
和PlatformBootManagerAfterConsole()
,它们是库函数(库名PlatformBootManagerLib
),这意味着不同的平台代码可以有自己的实现,在EDK代码中就有很多的不同实现:
不过无论是哪种实现,创建启动项都是其中很重要的一部分。
启动项的创建依赖于特定的几个Protocol:
gEfiBlockIoProtocolGuid
gEfiSimpleFileSystemProtocolGuid
gEfiLoadFileProtocolGuid
在《UEFI Spec》中只描述了后面两个,但是在EDK代码中会首先处理BlockIoProtocol(不过不同版本的EDK代码可能处理方式也不一样)。由于目前的启动主要是通过BootLoader或者网络启动,所以重要的还是后面两个,SimpleFileSystemProtocol表明了有文件系统可以获取BootLoader然后启动,而LoadFileProtocol说明了有网络可以进行PXE或Http等启动。代码处理主要在如下的函数:
/**
The function creates boot options for all possible bootable medias in the following order:
1. Removable BlockIo - The boot option only points to the removable media
device, like USB key, DVD, Floppy etc.
2. Fixed BlockIo - The boot option only points to a Fixed blockIo device,
like HardDisk.
3. Non-BlockIo SimpleFileSystem - The boot option points to a device supporting
SimpleFileSystem Protocol, but not supporting BlockIo
protocol.
4. LoadFile - The boot option points to the media supporting
LoadFile protocol.
Reference: UEFI Spec chapter 3.3 Boot Option Variables Default Boot Behavior
The function won't delete the boot option not added by itself.
**/
VOID
EFIAPI
EfiBootManagerRefreshAllBootOption (
VOID
);
除了Protocol对应的启动设备之后,还有一种常见的启动方式就是进入Setup、启动管理菜单和UEFI Shell,它们也同样是通过创建启动项来完成,下面是创建Shell启动项的一个例子:
//
// Register UEFI Shell
//
PlatformRegisterFvBootOption (
&gUefiShellFileGuid,
L"EFI Internal Shell",
LOAD_OPTION_ACTIVE
);
创建启动管理菜单也是要给创建的例子:
//
// If not found the BootManagerMenuApp, create it.
//
OptionNumber = (UINT16)RegisterBootManagerMenuAppBootOption (&mBootMenuFile, L"UEFI BootManagerMenuApp", (UINTN)-1, FALSE);
最终两者调用的函数都是:
/**
Initialize a load option.
@param Option Pointer to the load option to be initialized.
@param OptionNumber Option number of the load option.
@param OptionType Type of the load option.
@param Attributes Attributes of the load option.
@param Description Description of the load option.
@param FilePath Device path of the load option.
@param OptionalData Optional data of the load option.
@param OptionalDataSize Size of the optional data of the load option.
@retval EFI_SUCCESS The load option was initialized successfully.
@retval EFI_INVALID_PARAMETER Option, Description or FilePath is NULL.
**/
EFI_STATUS
EFIAPI
EfiBootManagerInitializeLoadOption (
IN OUT EFI_BOOT_MANAGER_LOAD_OPTION *Option,
IN UINTN OptionNumber,
IN EFI_BOOT_MANAGER_LOAD_OPTION_TYPE OptionType,
IN UINT32 Attributes,
IN CHAR16 *Description,
IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
IN UINT8 *OptionalData,
IN UINT32 OptionalDataSize
);
不过这个函数并不会设置变量,该操作有下面的函数完成:
/**
This function will register the new Boot####, Driver#### or SysPrep#### option.
After the *#### is updated, the *Order will also be updated.
@param Option Pointer to load option to add. If on input
Option->OptionNumber is LoadOptionNumberUnassigned,
then on output Option->OptionNumber is updated to
the number of the new Boot####,
Driver#### or SysPrep#### option.
@param Position Position of the new load option to put in the ****Order variable.
@retval EFI_SUCCESS The *#### have been successfully registered.
@retval EFI_INVALID_PARAMETER The option number exceeds 0xFFFF.
@retval EFI_ALREADY_STARTED The option number of Option is being used already.
Note: this API only adds new load option, no replacement support.
@retval EFI_OUT_OF_RESOURCES There is no free option number that can be used when the
option number specified in the Option is LoadOptionNumberUnassigned.
@return Status codes of gRT->SetVariable ().
**/
EFI_STATUS
EFIAPI
EfiBootManagerAddLoadOptionVariable (
IN OUT EFI_BOOT_MANAGER_LOAD_OPTION *Option,
IN UINTN Position
)