BDS阶段的功能:

开机时的启动菜单会在BDS阶段生成,所有的设备都会列出来,比如U盘,其搜索原理就是寻找具有FAT32分区的设备,这些会生成UEFI认可的菜单项,老一些的电脑还支持MBR分区的硬盘,以及网络适配器,UEFI固件也可以支持从网络远程系统镜像启动,选择启动设备后就进入了TSL阶段。

  • 执行启动策略(主要功能)。
    1. 初始化控制台console设备。(看系统有多少可以启动的设备)
    2. 加载必要的设备驱动。(启动所有检测到的设备,加载driver)
    3. 加载必要的设备驱动。(检测启动console设备,即输入输出设备)
    4. 根据系统设置和执行启动项(根据收集到的所有信息,提供一个setup的UI界面,终端用户可以在此选择,当用户真正选择启动选项的时候,BDS就会加载启动选项里的OS loader,最后移交真正的控制权给OS Loader ,由OS Loader 转移控制权给OS)
      BDS三大任务:console初始化、Driver初始化、BootDeviceSelect。

具体包括:初始化快捷键服务、初始化SystemTable 中的FirmwareVendor 和FirmwareRevision 域、平台相关BDS 初始化、初始化HwErrRecSupport 系统变量、加载操作系统 等
当加载项其启动失败时,系统将重新执行DXE dispatcher以加载更多的驱动,然后重新尝试加载驱动项。BDS策略通过全局NVRAM变量配置,这些变量可以被运行时服务的GetVariable()读取,通过SetVariable()设置(如BootOrder定义了启动顺序,Boot####对应不同的启动项,#为十六进制数)。当用户选中某个启动项(或进入系统默认启动项)后,OS Loader启动,系统进入TSL阶段。

BDS说明

BDS 的全称是启动设备选择(Boot Device Select), DXE 阶段最终会调用BDS ARCH protocol 的接口EFI_BDS_ARCH_PROTOCOL.Entry() 转入BDS阶段。BDS 阶段负责加载额外的驱动,与用户交互,必要的硬件初始化,并转入操作系统。

这个阶段 前面是DXE后面是OS Loader 或Shell,一个承前启后的作用,同时所有定制化的东西都在这个阶段完成,最主要的Setup page(UI界面)肯定是不同的,不同的BIOS Vendor的UI是不一样的。造成直接影响的三个决策就是:启动策略的管理、启动设备的选择、Setup界面。下图为DXE到OS Loader 的最简流程。
在这里插入图片描述

BOOT DEVICE SELECTION

从DXE拿到控制权后由用户去选择启动设备,开始控制权转交。在过程中可能会失败,为了防止此类现象出现定义了watch dog来实时监控状态,如果失败则会引导返回启动界面,BDS再从下一个启动选项开始启动,直到启动成功。所以在BDS阶段要通过boot option 这个Variable准备好所有可能的BOOT选项,可以提供给用户选择。

在整个BDS phase还需要提供用户一个可配置的UI界面,界面包含若干配置选项信息,这些信息完全是客户定制化的,客户想给用户暴露什么信息,就提供什么信息,正常时候报给客户的信息有:启动的列表(哪些设备是可以启动的)、基本的一些配置(securite boot如果这个选项是强制选项,那么根本不会暴露给用户)、显示的页面(宽屏还是窄屏)、安全相关的password、硬件相关的CPU 芯片组的工作,硬盘的工作模式等。

BDS Steps:

  1. 初始化语言和字符串数据库。
  2. 获得当前启动模式。
  3. 基于启动模式建立设备清单。
  4. 连接设备。
  5. 检测input output设备。
  6. 执行内存测试,
  7. 过程引导选项。
    在这里插入图片描述

EFI的引导顺序包括以下内容:

DriverOrder 一个UINT16的列表,组成一个Driver####变量的有序列表。
Driver#### 驱动程序加载选项。 BDS将尝试加载指定的驱动程序。 包含一个EFI_LOAD_OPTION。 一个例子是PCI根桥或串行I/O驱动程序。

平台固件从全局定义的NVRAM变量中读取boot order 引导顺序列表,引导顺序列表定义了一个NVRAM变量列表,其中包含要引导的信息。每个NVRAM变量为引导选项定义一个Unicode名称,可以显示给用户。该变量还包含一个指向硬件设备和该硬件设备上包含要加载的EFI图像的文件的指针。该变量还可能包含到OS分区和目录以及其他特定于配置的目录的路径。

NVRAM还可以包含将目录传递给EFI映像的加载选项。平台固件不知道加载选项中包含什么。加载选项,加载选项是由更高级别的软件设置的,当它写一个全局NVRAM变量来设置平台固件启动策略。如果操作系统内核的位置与EFI OS加载程序的位置不同,则可以使用此信息定义操作系统内核的位置,说穿了就是指向一个可以启动的 EFI image。

Boot#### 描述一个启动选 项,#### 表示一个四位16进制数,由数字0-9 和大写字母A-F组成。比如Boot0000, Boot0001, Boot0A02等。
BootNext 仅用于下次启动的Boot选项。 该选项优先于Boot####变量。
BootOrder 表示加载操作系统的顺序。其在内存中对应的数据结构如下所示:UINT16 BootOrder[ ];
BootOrder 数组中每一项对应于已经添加的启动选项Boot#### 的####。举例来说当添加一个新的启动选项时,先找到一个未被使用的选项数字000A, Boot000A将被创建,并且对应的选项数字000A 将被加到BootOrder 数组中。而当调整启动选项的顺序时,只要更新BootOrder 即可。

系统全局环境变量

其他模块和BDS 交互是通过一系列系统全局环境变量进行的。其他模块会设置这些变量,BDS在启动后会读取这些变量来决定诸如加载哪些额外的驱动、如何与用户交互、启动哪一个操作系统等。这些全局环境变量包括 :Langcode , Lang, Timeout, Platformlangcode , Conin, ConOut

Firmware Boot Manager:

引导管理器是EFI固件中的一个组件,它决定应该显式加载哪些EFI驱动程序和EFI应用程序以及何时加载。EFI固件初始化后,它将控制权传递给启动管理器。然后,引导管理器负责决定加载什么以及与用户的任何交互,这些交互可能需要做出这样的决定。特别是,可能的实现选项可能包括与启动相关的任何控制台接口、启动选择的集成平台管理、可能的其他内部应用程序知识或可能通过启动管理器集成到系统中的恢复驱动程序。与启动管理器的编程交互是通过全局定义的变量完成的。

BDS流程概述

BDS阶段是可以用于定制化的module ,可以根据客户要求进行相应的设计,但大体功能如下:

VOID
EFIAPI
BdsEntry (
IN EFI_BDS_ARCH_PROTOCOL *This
)
{
//
// Initialize hotkey service
// 
InitializeHotkeyService ();//
// Validate Variable.
// 存放BootOrder等变量,通过getVariable获取数值从而知晓启动策略
BdsFormalizeEfiGlobalVariable();//
// platform specific code
// Initialize the platform specific string and language
// 和Setup相关的code ,初始化 字符串 语言等,FrontPage为Setup前的页面,
// 通过FrontPage可以进入Device Module 、Boot Module、SetUp等,相当于一个分岔路口 
// 由于客户看到的是精简版的界面 所以FrontPage一般是看不到的
InitializeStringSupport ();
InitializeLanguage (TRUE);
InitializeFrontPage (TRUE);//
// Do the platform init, can be customized by OEM/IBV
// 一般是一个空函数,可以由OEM/IBV自己定义,比如在BDS阶段想要添加什么功能可以在这里实现
// 或者通过编写protocol调用 ,该函数相当于提供了一个接口
PlatformBdsInit ();//
// Set up the device list based on EFI 1.1 variables
// process Driver#### and Load the driver's in the
// driver option list
// BDS 阶段第二个任务就是运行Driver ,驱动加载完了就可以找到Device Path和BootList等,从bootList进行启动
// 在类似BdsLibConnectAllEfi()的函数中 install 所有的 binding Driver
// 这时候如果开了全打印 就可以在log看到所有的Driver信息
BdsLibBuildOptionFromVar (&DriverOptionList, L"DriverOrder");
if (!IsListEmpty (&DriverOptionList)) {
BdsLibLoadDrivers (&DriverOptionList);
}//
// Setup some platform policy here
// 一个空函数,可以进行个性化定制,关于平台策略的处理
PlatformBdsPolicyBehavior (&DriverOptionList, &BootOptionList, BdsProcessCapsules, BdsMemoryTest);//
// BDS select the boot device to load OS
// 整个启动函数,包括枚举所有boot option ,然后判断UEFI还是Legacy启动,
// Legacy启动通过读MBI进行;UEFI启动通过读取特定路径下的BOOt信息 根据boot option优先级选择设备启动
BdsBootDeviceSelect ();
}

细说BdsEntry ()

Instance->Entry()的服务例程。连接好设备,初始化控制台,并尝试启动选项。BDS 的入口函数,负责安装EFI_BDS_ARCH_PROTOCOL 协议,以让DXE Foundation 调用 。以EDK2源码为例:\MdeModulePkg\Universal\BdsDxe\BdsEntry.c

VOID
EFIAPI
BdsEntry (
  IN EFI_BDS_ARCH_PROTOCOL  *This
  )
{
  EFI_BOOT_MANAGER_LOAD_OPTION    *LoadOptions;
  UINTN                           LoadOptionCount;
  CHAR16                          *FirmwareVendor;
  EFI_EVENT                       HotkeyTriggered;
  UINT64                          OsIndication;
  UINTN                           DataSize;
  EFI_STATUS                      Status;
  UINT32                          BootOptionSupport;
  UINT16                          BootTimeOut;
  EDKII_VARIABLE_LOCK_PROTOCOL    *VariableLock;
  UINTN                           Index;
  EFI_BOOT_MANAGER_LOAD_OPTION    LoadOption;
  UINT16                          *BootNext;//BootNext是一个可有可无的环境变量,当存在时,BDS 将优先启动选项数字等于BootNext 的启动选项。
  CHAR16                          BootNextVariableName[sizeof ("Boot####")];
  EFI_BOOT_MANAGER_LOAD_OPTION    BootManagerMenu;
  BOOLEAN                         BootFwUi;
  BOOLEAN                         PlatformRecovery;
  BOOLEAN                         BootSuccess;
  EFI_DEVICE_PATH_PROTOCOL        *FilePath;
  EFI_STATUS                      BootManagerMenuStatus;

  HotkeyTriggered = NULL;
  Status          = EFI_SUCCESS;
  BootSuccess     = FALSE;

  //
  // Insert the performance probe
  //
  PERF_END (NULL, "DXE", NULL, 0);
  PERF_START (NULL, "BDS", NULL, 0);
  DEBUG ((EFI_D_INFO, "[Bds] Entry...\n"));

  PERF_CODE (
  // 为操作系统分配一块包含性能数据的内存。
    BdsAllocateMemoryForPerformanceData ();
  );

  //
  // Fill in FirmwareVendor and FirmwareRevision from PCDs
  // PcdGetPtr根据令牌名称检索指向PCD令牌缓冲区的指针。返回一个指针,指向TokenName指定的标记的缓冲区。
  // FirmWareVendor为指向null结束字符串的指针,该字符串标识为平台生产系统固件的供应商。
  FirmwareVendor = (CHAR16 *) PcdGetPtr (PcdFirmwareVendor);
  // 将缓冲区复制到类型为EfiRuntimeServicesData的已分配缓冲区。分配类型为EfiRuntimeServicesData的AllocationSize指定的字节数,
  // 将AllocationSize字节从缓冲区复制到新分配的缓冲区,并返回一个指向已分配缓冲区的指针。
  // 如果AllocationSize为0,则返回一个大小为0的有效缓冲区。如果没有足够的剩余内存来满足请求,则返回NULL。
  gST->FirmwareVendor = AllocateRuntimeCopyPool (StrSize (FirmwareVendor), FirmwareVendor);
  ASSERT (gST->FirmwareVendor != NULL);
  gST->FirmwareRevision = PcdGet32 (PcdFirmwareRevision);

  //
  // Fixup Tasble CRC after we updated Firmware Vendor and Revision(10H 循环冗余校验(CRC)错。)
  //
  gST->Hdr.CRC32 = 0;
  // 计算并返回一个32位的数据缓冲区CRC。
  gBS->CalculateCrc32 ((VOID *) gST, sizeof (EFI_SYSTEM_TABLE), &gST->Hdr.CRC32);

  //
  // Validate Variable.
  //
  BdsFormalizeEfiGlobalVariable ();

  //
  // Mark the read-only variables if the Variable Lock protocol exists
  //
  Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **) &VariableLock);
  DEBUG ((EFI_D_INFO, "[BdsDxe] Locate Variable Lock protocol - %r\n", Status));
  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < ARRAY_SIZE (mReadOnlyVariables); Index++) {
      Status = VariableLock->RequestToLock (VariableLock, mReadOnlyVariables[Index], &gEfiGlobalVariableGuid);
      ASSERT_EFI_ERROR (Status);
    }
  }
 // 由平台实现设置HwErrRecSupport变量包含一个二进制的UINT16,它提供了硬件错误记录持久性的支持级别。
  InitializeHwErrRecSupport ();

  //
  // Initialize L"Timeout" EFI global variable.
  // timeout用于在例如设备启动过程中时间的累计计算,如果在时间内一直未成功,则会进行下一个设备启动
  BootTimeOut = PcdGet16 (PcdPlatformBootTimeOut);
  if (BootTimeOut != 0xFFFF) {
    //
    // If time out value equal 0xFFFF, no need set to 0xFFFF to variable area because UEFI specification
    // define same behavior between no value or 0xFFFF value for L"Timeout".
    // 函数设置变量并在失败时通过状态码报告错误。为BootManagerMenu创建一个启动选项(如果还没有创建的话)
    BdsDxeSetVariableAndReportStatusCodeOnError (
                      EFI_TIME_OUT_VARIABLE_NAME,
                      &gEfiGlobalVariableGuid,
                      EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                      sizeof (UINT16),
                      &BootTimeOut
      );
  }

  //
  // Initialize L"BootOptionSupport" EFI global variable.
  // Lazy-ConIn implictly disables BDS hotkey.
  //
  BootOptionSupport = EFI_BOOT_OPTION_SUPPORT_APP | EFI_BOOT_OPTION_SUPPORT_SYSPREP;
  if (!PcdGetBool (PcdConInConnectOnDemand)) {
    BootOptionSupport |= EFI_BOOT_OPTION_SUPPORT_KEY;
    SET_BOOT_OPTION_SUPPORT_KEY_COUNT (BootOptionSupport, 3);
  }
  Status = gRT->SetVariable (
                  EFI_BOOT_OPTION_SUPPORT_VARIABLE_NAME,
                  &gEfiGlobalVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                  sizeof (BootOptionSupport),
                  &BootOptionSupport
                  );
  //
  // Platform needs to make sure setting volatile variable before calling 3rd party code shouldn't fail.
  // https://blog.csdn.net/u010710458/article/details/77848538  关于volatile的解读
  ASSERT_EFI_ERROR (Status);

  //
  // Cache and remove the "BootNext" NV variable.可以避免在此引导中使用PlatformBootManagerLib设置的“BootNext”。  
  // 函数返回一个指向已分配缓冲区的指针,该缓冲区包含通过UEFI运行时服务GetVariable()检索到的变量的内容。
  // 这个函数总是使用EFI_GLOBAL_VARIABLE GUID来检索变量。返回的缓冲区是使用AllocatePool()分配的。调用者负责用FreePool()释放这个缓冲区。
  GetEfiGlobalVariable2 (EFI_BOOT_NEXT_VARIABLE_NAME, (VOID **) &BootNext, &DataSize);
  if (DataSize != sizeof (UINT16)) {
    if (BootNext != NULL) {
      FreePool (BootNext);
    }
    BootNext = NULL;
  }
  Status = gRT->SetVariable (
                  EFI_BOOT_NEXT_VARIABLE_NAME,
                  &gEfiGlobalVariableGuid,
                  0,
                  0,
                  NULL
                  );
  //
  // Deleting NV variable shouldn't fail unless it doesn't exist.
  // 
  ASSERT (Status == EFI_SUCCESS || Status == EFI_NOT_FOUND);

  //
  // Initialize the platform language variables
  // 根据与语言相关的EFI变量确定将使用的当前语言。
  InitializeLanguage (TRUE);

  //
  // System firmware must include a PlatformRecovery#### variable specifying
  // a short-form File Path Media Device Path containing the platform default
  // file path for removable media
  // 系统固件必须包含一个PlatformRecovery####变量,指定一个短格式文件路径媒体设备路径,其中包含可移动媒体的平台默认文件路径。
  FilePath = FileDevicePath (NULL, EFI_REMOVABLE_MEDIA_FILE_NAME);
  // 函数用于初始化一个加载选项。
  Status = EfiBootManagerInitializeLoadOption (
             &LoadOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypePlatformRecovery,
             LOAD_OPTION_ACTIVE,
             L"Default PlatformRecovery",
             FilePath,
             NULL,
             0
             );
  ASSERT_EFI_ERROR (Status);
  // 返回一个基于EFI变量L“BootOrder”/L“DriverOrder”和L“Boot####”/L“Driver####”变量的加载选项数组。
  // ####是每个BootOrder/DriverOrder条目中UINT16的十六进制值。
  LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionTypePlatformRecovery);
  // 返回加载选项数组中加载选项的索引。当OptionType、Attributes、Description、FilePath和OptionalData相等时,函数认为两个加载选项是相等的。
  if (EfiBootManagerFindLoadOption (&LoadOption, LoadOptions, LoadOptionCount) == -1) {
    for (Index = 0; Index < LoadOptionCount; Index++) {
      //
      // The PlatformRecovery#### options are sorted by OptionNumber.
      // Find the the smallest unused number as the new OptionNumber.
      //
      if (LoadOptions[Index].OptionNumber != Index) {
        break;
      }
    }
    LoadOption.OptionNumber = Index;
    // 从加载选项创建Boot####,Driver####,SysPrep####, PlatformRecovery####变量。
    Status = EfiBootManagerLoadOptionToVariable (&LoadOption);
    ASSERT_EFI_ERROR (Status);
  }
  // 释放由库分配的EFI_BOOT_MANGER_LOAD_OPTION entry。
  EfiBootManagerFreeLoadOption (&LoadOption);
  FreePool (FilePath);
  EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);

  //
  // Report Status Code to indicate connecting drivers will happen
  // 报告状态代码以指示连接驱动程序将会发生
  REPORT_STATUS_CODE (
    EFI_PROGRESS_CODE,
    (EFI_SOFTWARE_DXE_BS_DRIVER | EFI_SW_DXE_BS_PC_BEGIN_CONNECTING_DRIVERS)
    );

  //
  // Initialize ConnectConIn event before calling platform code.
  // 在调用平台代码之前初始化ConnectConIn事件。
  // 通过PcdGetbool来判定条件进而触发事件函数 (用于实现定制化要求)
  if (PcdGetBool (PcdConInConnectOnDemand)) {
    Status = gBS->CreateEventEx (
                    EVT_NOTIFY_SIGNAL,
                    TPL_CALLBACK,
                    BdsDxeOnConnectConInCallBack,
                    NULL,
                    &gConnectConInEventGuid,
                    &gConnectConInEvent
                    );
    if (EFI_ERROR (Status)) {
      gConnectConInEvent = NULL;
    }
  }

  //
  // 执行平台初始化,可以通过OEM/IBV定制
  // 可以在PlatformBootManagerBeforeConsole中完成的事情:
  // 更新控制台变量:1。包括热插拔设备;2. 清除ConIn并为AMT添加SOL
  // > 注册new Driver#### or Boot####
  // > 注册 new Key####: e.g.: F12 
  // >信号ReadyToLock事件
  // >认证动作:1。身份验证连接设备;2. 识别自动登录用户。
  PERF_START (NULL, "PlatformBootManagerBeforeConsole", "BDS", 0);
  // 函数在连接控制台之前执行平台特定的操作。
  // 如:更新控制台变量;注册新驱动####或引导####;信号ReadyToLock事件。
  PlatformBootManagerBeforeConsole ();
  PERF_END   (NULL, "PlatformBootManagerBeforeConsole", "BDS", 0);

  //
  // Initialize hotkey service
  // 启动热键服务,按下热键即可触发启动项。返回一个可等待的事件,当一个有效的热键被按下时,它将被通知。
  EfiBootManagerStartHotkeyService (&HotkeyTriggered);

  //
  // Execute Driver Options
  // 函数返回一个基于EFI变量L“BootOrder”/L“DriverOrder”和L“Boot####”/L“Driver####”变量的加载选项数组。
  // ####是每个BootOrder/DriverOrder条目中UINT16的十六进制值。
  LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionTypeDriver);
  // 该函数将加载和启动每个驱动####、SysPrep####或PlatformRecovery####。
  ProcessLoadOptions (LoadOptions, LoadOptionCount);
  // 释放由EfiBootManagerGetLoadOptions()分配的EFI_BOOT_MANGER_LOAD_OPTION数组。
  EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);

  //
  // Connect consoles
  //
  PERF_START (NULL, "EfiBootManagerConnectAllDefaultConsoles", "BDS", 0);
  if (PcdGetBool (PcdConInConnectOnDemand)) {
  // 基于变量ConsoleType连接控制台设备。
    EfiBootManagerConnectConsoleVariable (ConOut);
    EfiBootManagerConnectConsoleVariable (ErrOut);
    //
    // Do not connect ConIn devices when lazy ConIn feature is ON.
    //
  } else {
  // 此函数将基于控制台设备变量ConIn、ConOut和ErrOut连接所有控制台设备。
    EfiBootManagerConnectAllDefaultConsoles ();
  }
  PERF_END   (NULL, "EfiBootManagerConnectAllDefaultConsoles", "BDS", 0);

  //
  // 在控制台准备好后执行平台特定的操作
  // 可以在PlatformBootManagerAfterConsole中完成的事情:
  // >控制台操作
  // >动态切换输出模式从100x31到80x25某些senarino
  // 信号控制台准备平台自定义事件
  // 运行诊断程序,例如内存测试
  // >连接某些设备
  // >发送额外的rom选项
  // >特殊启动:例如:USB启动,输入UI
  PERF_START (NULL, "PlatformBootManagerAfterConsole", "BDS", 0);
  // 函数在连接控制台之后执行平台特定的操作。
  // 如:动态切换输出模式;信号控制台准备平台定制事件;运行诊断,如内存测试;连接某些设备;分派额外的rom选项。
  PlatformBootManagerAfterConsole ();
  PERF_END   (NULL, "PlatformBootManagerAfterConsole", "BDS", 0);
  //
  // Boot to Boot Manager Menu when EFI_OS_INDICATIONS_BOOT_TO_FW_UI is set. Skip HotkeyBoot
  //
  DataSize = sizeof (UINT64);
  Status = gRT->GetVariable (
                  EFI_OS_INDICATIONS_VARIABLE_NAME,
                  &gEfiGlobalVariableGuid,
                  NULL,
                  &DataSize,
                  &OsIndication
                  );
  if (EFI_ERROR (Status)) {
    OsIndication = 0;
  }

  DEBUG_CODE (
    EFI_BOOT_MANAGER_LOAD_OPTION_TYPE LoadOptionType;
    DEBUG ((EFI_D_INFO, "[Bds]OsIndication: %016x\n", OsIndication));
    DEBUG ((EFI_D_INFO, "[Bds]=============Begin Load Options Dumping ...=============\n"));
    for (LoadOptionType = 0; LoadOptionType < LoadOptionTypeMax; LoadOptionType++) {
      DEBUG ((
        EFI_D_INFO, "  %s Options:\n",
        mBdsLoadOptionName[LoadOptionType]
        ));
      // 返回一个基于EFI变量L“BootOrder”/L“DriverOrder”和L“Boot####”/L“Driver####”变量的加载选项数组。
      // ####是每个BootOrder/DriverOrder条目中UINT16的十六进制值。
      LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionType);
      for (Index = 0; Index < LoadOptionCount; Index++) {
        DEBUG ((
          EFI_D_INFO, "    %s%04x: %s \t\t 0x%04x\n",
          mBdsLoadOptionName[LoadOptionType],
          LoadOptions[Index].OptionNumber,
          LoadOptions[Index].Description,
          LoadOptions[Index].Attributes
          ));
      }
      // 释放由EfiBootManagerGetLoadOptions()分配的EFI_BOOT_MANGER_LOAD_OPTION数组。
      EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);
    }
    DEBUG ((EFI_D_INFO, "[Bds]=============End Load Options Dumping=============\n"));
  );

  //
  // BootManagerMenu doesn't contain the correct information when return status is EFI_NOT_FOUND.
  // 返回与启动管理器菜单对应的启动选项。如果还没有创建启动选项,它可能会自动创建一个。
  BootManagerMenuStatus = EfiBootManagerGetBootManagerMenu (&BootManagerMenu);
  
  BootFwUi         = (BOOLEAN) ((OsIndication & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) != 0);
  PlatformRecovery = (BOOLEAN) ((OsIndication & EFI_OS_INDICATIONS_START_PLATFORM_RECOVERY) != 0);
  //
  // Clear EFI_OS_INDICATIONS_BOOT_TO_FW_UI to acknowledge OS
  // 
  if (BootFwUi || PlatformRecovery) {
    OsIndication &= ~((UINT64) (EFI_OS_INDICATIONS_BOOT_TO_FW_UI | EFI_OS_INDICATIONS_START_PLATFORM_RECOVERY));
    Status = gRT->SetVariable (
               EFI_OS_INDICATIONS_VARIABLE_NAME,
               &gEfiGlobalVariableGuid,
               EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
               sizeof(UINT64),
               &OsIndication
               );
    //
    // Changing the content without increasing its size with current variable implementation shouldn't fail.
    // 更改内容而不使用当前变量实现增加其大小 不应该会失败,失败便ASSERT。
    ASSERT_EFI_ERROR (Status);
  }

  //
  // Launch Boot Manager Menu directly when EFI_OS_INDICATIONS_BOOT_TO_FW_UI is set. Skip HotkeyBoot
  // 设置“EFI_OS_INDICATIONS_BOOT_TO_FW_UI”时,直接启动启动管理器菜单。跳过HotkeyBoot
  if (BootFwUi && (BootManagerMenuStatus != EFI_NOT_FOUND)) {
    //
    // Follow generic rule, Call BdsDxeOnConnectConInCallBack to connect ConIn before enter UI
    //
    if (PcdGetBool (PcdConInConnectOnDemand)) {
    // 事件来连接ConIn。
      BdsDxeOnConnectConInCallBack (NULL, NULL);
    }

    //
    // Directly enter the setup page.
    // 尝试启动EFI启动选项。这个例程设置L“BootCurent”,并通知EFI准备好启动事件。
    // 如果该选项的设备路径以BBS设备路径启动,则尝试通过注册的gllegacyboot函数进行遗留引导。短形式的设备路径也支持通过这个例程。
    // 展开以MEDIA_HARDDRIVE_DP、MSG_USB_WWID_DP、MSG_USB_CLASS_DP开头的设备路径,以找到第一个匹配的设备。
    // 如果引导设备路径失败,尝试可移动媒体引导算法(\EFI\BOOTIA32。EFI, \ EFI \ BOOTX64.EFI,…每个处理器类型只尝试一个文件类型)
    EfiBootManagerBoot (&BootManagerMenu);
  }

  if (!PlatformRecovery) {
    //
    // Execute SysPrep####
    //
    LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionTypeSysPrep);
    ProcessLoadOptions (LoadOptions, LoadOptionCount);
    EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);

    //
    // Execute Key####
    //
    PERF_START (NULL, "BdsWait", "BDS", 0);
    // 该函数等待启动管理器超时或热键被按下。它每秒钟都会调用PlatformBootManagerWaitCallback。
    BdsWait (HotkeyTriggered);
    PERF_END   (NULL, "BdsWait", "BDS", 0);

    //
    // BdsReadKeys() can be removed after all keyboard drivers invoke callback in timer callback.
    // 该函数读取用户输入。
    BdsReadKeys ();
    // 尝试启动由热键触发的启动选项。
    EfiBootManagerHotkeyBoot ();

    //
    // Boot to "BootNext"
    //
    if (BootNext != NULL) {
      UnicodeSPrint (BootNextVariableName, sizeof (BootNextVariableName), L"Boot%04x", *BootNext);
    // 从变量名构建Boot####或Drive####选项。
      Status = EfiBootManagerVariableToLoadOption (BootNextVariableName, &LoadOption);
      if (!EFI_ERROR (Status)) {
        EfiBootManagerBoot (&LoadOption);
        EfiBootManagerFreeLoadOption (&LoadOption);
        if ((LoadOption.Status == EFI_SUCCESS) && 
            (BootManagerMenuStatus != EFI_NOT_FOUND) &&
            (LoadOption.OptionNumber != BootManagerMenu.OptionNumber)) {
          //
          // 启动到启动管理器菜单上的EFI_SUCCESS。
          // 例外:当BootNext指向boot Manager菜单时,不要再次启动。
          EfiBootManagerBoot (&BootManagerMenu);
        }
      }
    }

    do {
      //
      // Retry to boot if any of the boot succeeds
      // 返回一个基于EFI变量L“BootOrder”/L“DriverOrder”和L“Boot####”/L“Driver####”变量的加载选项数组。
      // ####是每个BootOrder/DriverOrder条目中UINT16的十六进制值。
      LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionTypeBoot);
      // 尝试引导BootOptions数组中的每个引导选项。
      BootSuccess = BootBootOptions (LoadOptions, LoadOptionCount, (BootManagerMenuStatus != EFI_NOT_FOUND) ? &BootManagerMenu : NULL);
      // 释放由EfiBootManagerGetLoadOptions()分配的EFI_BOOT_MANGER_LOAD_OPTION数组。 
      EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);
    } while (BootSuccess);
  }

  if (BootManagerMenuStatus != EFI_NOT_FOUND) {
    EfiBootManagerFreeLoadOption (&BootManagerMenu);
  }

  if (!BootSuccess) {
    LoadOptions = EfiBootManagerGetLoadOptions (&LoadOptionCount, LoadOptionTypePlatformRecovery);
    // 该函数将加载和启动每个Driver####、SysPrep####或PlatformRecovery####。
    ProcessLoadOptions (LoadOptions, LoadOptionCount);
    EfiBootManagerFreeLoadOptions (LoadOptions, LoadOptionCount);
  }

  DEBUG ((EFI_D_ERROR, "[Bds] Unable to boot!\n"));
  CpuDeadLoop ();
}

剩下的阶段,工作只需简单的了解

TSL(Transient System Load)- 操作系统加载前期

是操作系统加载器(OS Loader)执行的第一阶段也是OS Loader的主战场,在这一阶段OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制。当启动服务的ExitBootServices()服务被调用后,系统进入RunTime阶段。

TSL阶段之所以称为临时系统,在于它存在的目的就是为操作系统加载器准备执行环境。虽然是临时系统,但其功能已经很强大,已经具备了操作系统的雏形,UEFI Shell是这个临时系统的人机交互界面。正常情况下,系统不会进入UEFI Shell,而是直接执行操作系统加载器,只有在用户干预下或操作系统加载器遇到严重错误时才会进入UEFI Shell。

RT(Run Time)

系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,因此要清理和回收一些之前被UEFI占用的资源,RT Services随着操作系统的运行提供相应的运行时的服务,这个期间一旦出现错误和异常,将进入AL进行修复。

AL 系统灾难恢复前期

AL(After Life)阶段,是最终阶段,由常驻的UEFI驱动组成,计算机关机休眠睡眠重启过程的系统信息都会在这一阶段保存

如果系统(硬件或软件)遇到灾难性错误,系统固件需要提供错误处理和灾难恢复机制,这种机制运行在AL(AfterLife)阶段。UEFI和UEFI PI标准都没有定义此阶段的行为和规范,根据厂家自定义修复方案。

OS Loader

在OS Loader的生命周期内,它需要完成的使命,Loader会出现在两种场景中:安装操作系统和开机直接加载系统,开机选择制作好的U盘镜像启动,此时的U盘就被识别为一个启动设备,选择后运行的程序就是安装程序,通常会需要选择语言、选择安装位置、格式化磁盘、连接网络等操作,那么支持这些功能的就是来自于UEFI固件的如Network Protocols ,当系统安装完毕,相应的文件都拷贝到了硬盘上,下次启动就会从硬盘启动。此后就是开机直接加载操作系统,这就是我们需要设计的loader

在这里插入图片描述
Loader可以使用BootServices和RuntimeService获取数据,OS只能使用RuntimeService,所以有些东西是OS自己是拿不到的,比如获取物理内存就需要Loader传给OS,Loader可以使用Gop来显示字符或者图片,但Gop是BootService那么OS就无法调用,所以OS需要知道显卡的Frame Buffer信息比如起始地址和Buffer大小,然后通过往里写入数据来完成屏幕显示。
在这里插入图片描述

同时OS也需要Loader来加载,倘若kernel放在ESP目录,这样Loader就可以直接读取当前路径下的Kernel.elf文件并解析运行即可。ELF格式文件,文件文件进入内存后某些地址是需要重新定位的,所以Loader还需要重定位Kernel.elf,使其在内存中可以正常运行,类似开机转圈圈就是设置显卡分辨率,这些就是Loader最基本的要求,也是下一步要实现的功能,而完成这些功能需要依赖的启动或运行时服务,非常明确的有如下几个:

  • BootService的Memory Allocation Service的接口,如GetMemoryMap()以及为Kernel 开辟内存的AllocatePool()
  • BootServices的protocol Handler Services的接口,如OpenProtocol()函数,用来打开提供更多的功能的Protocol
    UEFI的接口并不是全部封装在BootServices和RuntimeServices ,而是跟EDK2的Pkg一样,按照功能分类到不同的Protocol组成合集,比如ACPI Protocols还有USB Support等等,他们使用前要先打开,然后通过打开Protocol获得的指针,进一步调用具体的功能函数
  • Console Support,主要是Graphics Output Protocol,它能够查询显卡支持的模式,还能获取点卡的Frame Buffer相关信息
  • Media Access, 主要是Simple File System Protocol ,它能够更轻松读取Kernel文件,不需要自己去读写硬盘
    在这里插入图片描述
    本段OS Loader相关来源:[B站谭玉刚教学视频]
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐