【UEFI基础】BIOS下的启动项管理

启动管理

启动管理(Boot Manager)是UEFI BIOS中重要的一部分,它通过一系列的变量来确定启动策略,包括:

  • 执行启动还是恢复操作
  • 启动顺序是如何

本文会介绍下面的内容:

  • 与启动管理相关的变量
  • 启动或恢复的流程策略
  • 加载项结构体
  • 创建加载项

注意本文只关注UEFI BIOS的启动项,涉及的是UEFI BIOS启动管理,至于Legacy的,这里不会描述。

另外,虽然是“启动管理”,但是这里的“启动”并不都是“启动操作系统”的意思,代码操作的也不叫“启动项”,而是“加载项”(Load Option),后面文档中出现的“启动”也并不一定是启动操作系统的意思,也可能仅仅是加载一个UEFI应用或UEFI驱动。

启动一般流程

以下是《UEFI Spec》中的一般规定:

  • 如果OsIndications指定了要执行恢复操作,则不会进入通常的启动流程,而是进入恢复选项。
  • Driver####会优先于Boot####执行。
  • 执行Boot加载项时,先执行BootNext对应的加载项,然后按照BootOrder中的顺序执行Boot####
  • 如果BootNextBoot####中的执行都失败了,则进入启动项恢复操作,主要就是PlatformRecovery####

实际上真实的BIOS中,其实现可以有很多中,因此并不完全按照上述的规则。比如最后一步BootNextBoot####中的执行都失败,一般会选择进入Setup,而不是PlatformRecovery####

启动管理变量

  1. 真正描述加载项的变量:
变量名作用域说明
Boot####NV、BS、RTA boot load option.
Driver####NV、BS、RTA driver load option.
SysPrep####NV、BS、RTA System Prep application load option containing a EFI_LOAD_OPTION descriptor.
OsRecovery####NV、BS、RT
PlatformRecovery####NV、BS、RTPlatform-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)。

上述的变量因为有多个,所以还有一个顺序关系,对应到一个变量:

变量名作用域说明
BootOrderNV、BS、RTThe ordered boot option load list.
DriverOrderNV、BS、RTThe ordered driver load option list.
SysPrepOrderNV、BS、RTThe ordered System Prep Application load option list.
OsRecoveryOrderNV、BS、RTOS-specified recovery options.

但是没有PlatformRecoveryOrder,可以认为只有一个PlatformRecovery应用,所以不需要顺序吧。

另外OsRecoveryOrder出现在《UEFI Spec》的“Table 10. Global Variables”中也比较奇怪,因为前面已经说了它应该属于VendorGuid的变量才对,并且OsRecoveryOrder也没有出现在EDK代码中。

  1. 启动相关的变量:
变量名作用域说明
BootCurrentBS、RTThe boot option that was selected for the current boot.
BootNextNV、BS、RTThe boot option for the next boot only.

BootCurrent因为是随时可变的,所以不会存放到非易失变量中。

  1. 配置相关的变量:
变量名作用域说明
BootOptionSupportBS、RTThe types of boot options supported by the boot manager. Should be treated as read-only.
OsIndicationsNV、BS、RTAllows the OS to request the firmware to enable certain features and to take certain actions.
OsIndicationsSupportedBS、RTAllows 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的目前使用的特性。所以OsIndicationsOsIndicationsSupported的一个子集,目前支持的值有:

//
// 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	// 表示这个启动项虽然不用于普通启动,但是可以通过快捷键或者启动选项选择界面启动
  • FilePathListLengthFilePathList的长度,这个成员的存在是为了计算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中的同名成员变量。
  • OptionalDataSizeOptionalData的大小。
  • VendorGuid:启动项变量对应的GUID,它一般是固定的EFI_GLOBAL_VARIABLE
  • StatusExitDataExitDataSizegBS->StartImage()的返回值,返回数据和返回数据大小。

启动管理实际上用的更多的是这个结构体。从EFI_LOAD_OPTIONEFI_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):

启动项相关变量处理
创建PlatformDefaultBootOption
PlatformBootManagerBeforeConsole
EfiBootManagerStartHotkeyService
PlatformBootManagerAfterConsole
根据启动策略执行各种启动项

当然这只是一个简单的版本,实际上它包含很多变量获取以及根据变量值执行不同的启动策略的操作,不过这里主要关心的是启动项的创建,所以比较重要的是函数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
  )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值