GraphicsOutPutProtocol图形输出协议

GraphicsOutPutProtocol作为学习的第一个协议,它的头文件位于MdePkg/Include/Protocol/GraphicsOutput.h,Guid如下:

1
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID gEfiGraphicsOutputProtocolGuid = { 0x9042A9DE, 0x23DC, 0x4A38, { 0x96, 0xFB, 0x7A, 0xDE, 0xD0, 0x80, 0x51, 0x6A }};

除去最后一个PixelFormatMax用于范围检查,GraphicsOutPut总计提供了4种可选格式,小端存储下使用32位扩展RGB888格式的应该选择第二个PixelBlueGreenRedReserved8BitPerColor,而使用24位RGB888、16位RGB565、RGB555或其他格式的则应该选择第三个PixelBitMask,这点需要注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct {
UINT32 RedMask;
UINT32 GreenMask;
UINT32 BlueMask;
UINT32 ReservedMask;
} EFI_PIXEL_BITMASK;
typedef enum {
// PhysicalFrameBuffer的RGBA格式,字节0-2分别为红绿蓝,字节3保留。
PixelRedGreenBlueReserved8BitPerColor,
// PhysicalFrameBuffer的BGRA格式,字节0-2分别为蓝绿红,字节3保留。
PixelBlueGreenRedReserved8BitPerColor,
// PhysicalFrameBuffer的BitMask格式,需要EFI_PIXEL_BITMASK确定各颜色位置
PixelBitMask,
// 不支持PhysicalFrameBuffer意味着无法直接通过物理内存地址写入,仅支持Blt操作
PixelBltOnly,
// 格式范围检查
PixelFormatMax
} EFI_GRAPHICS_PIXEL_FORMAT;

对比传统BIOS下VESA的定义(不了解VESA的需要重点看下面内容),X对应Width,Y对应Height,像素格式对应BPP(Bits Per Pixel),每行像素数量对应Pitch(Bytes Per Scanline)。

不使用Blt直接操作FrameBuffer时需要注意,Pitch并不一定等于Width,因为各种原因(比如内存对齐,像素格式等),像素数据不一定是连续的,像素行与像素行之间可能存在填充(Padding),所以在计算第Y行,第X列像素的偏移时,计算公式应该是:Offset =(Y * Pitch + X)* BPP,禁止将Pitch替换为Width避免出现花屏等问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct {
// 版本号固定放在开头,值为0代表当前的EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
UINT32 Version;
// 视频屏幕的水平方向X的像素尺寸
UINT32 HorizontalResolution;
// 视频屏幕的垂直方向Y的像素尺寸
UINT32 VerticalResolution;
// 像素格式
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
// 仅像素格式为BitMask此位有效,置位的bit定义了哪些位是红、绿、蓝或保留。
EFI_PIXEL_BITMASK PixelInformation;
// 定义视频屏幕每行包含的像素单元数量
UINT32 PixelsPerScanLine;
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;

过段时间再写:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 返回图形设备和当前视频输出设备组合所支持的可用图形模式信息
typedef
EFI_STATUS // 发生硬件错误返回EFI_DEVICE_ERROR,模式编号无效(超出范围)返回EFI_INVALID_PARAMETER
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE)(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, // 第一个参数必须是EFI_GRAPHICS_OUTPUT_PROTOCOL指针模拟This指针
IN UINT32 ModeNumber, // 要查询的模式编号
OUT UINTN *SizeOfInfo, // 指向缓冲区大小的指针
OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info // 指向Info缓冲区大小的指针,注意它由驱动程序使用AllocatePool()分配内存,完成调用后,必须使用FreePool()释放内存避免内存泄漏
);
// 将视频设备设置为指定模式,并将屏幕可见区域清空为黑色
typedef
EFI_STATUS // 发生硬件错误返回EFI_DEVICE_ERROR,不支持此模式编号返回EFI_UNSUPPORTED
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE)(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, // This指针
IN UINT32 ModeNumber // 目标视频模式的编号
);
// 在图形屏幕上Blt一个像素矩形,Blt表示块传输(Block Transfer)
typedef
EFI_STATUS // 发生硬件错误返回EFI_DEVICE_ERROR,BltOperation参数无效返回EFI_INVALID_PARAMETER
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT)(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, // This指针
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer OPTIONAL, // 要传输到屏幕的数据缓冲区,要求大小至少要Width * Heigth * BPP
IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, // 执行复制操作的具体类型
IN UINTN SourceX, // 源X坐标
IN UINTN SourceY, // 源Y坐标
IN UINTN DestinationX, // 目的X坐标
IN UINTN DestinationY, // 目的Y坐标
IN UINTN Width, // 矩形的宽度
IN UINTN Height, // 矩形的高度
IN UINTN Delta OPTIONAL // 对于EfiBltVideoFill或EfiBltVideoToVideo无效,如果Delta为0,表示操作整个BltBuffer,如果使用的是BltBuffer的子矩阵,则Delta表示BltBuffer每行的字节跨度(Stride)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct {
// QueryMode()和SetMode()支持的模式数量
UINT32 MaxMode;
// 图形设备的当前模式,有效编号为0到最大-1
UINT32 Mode;
// 指向只读的EFI_GRAPHICS_OUTPUT_MODE_INFORMATION数据的指针
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
// Info结构的大小(单位为Byte)
UINTN SizeOfInfo;
// FrameBuffer基地址,偏移量0表示显示器的左上角像素
EFI_PHYSICAL_ADDRESS FrameBufferBase;
// 支持当前模式所需的帧缓冲区大小,由PixelsPerScanLine * VerticalResolution * PixelElementSize定义
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;
// 提供抽象层,用于设置视频模式以及在图形控制器的帧缓冲区之间复制像素
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
// 指向EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE数据的指针
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
};

Uefi应用程序加载过程

当执行.efi文件时,Shell首先使用gBS->LoadImage()将.efi文件加载到内存生成Image对象,然后调用gBS->StartImage(Image)启动这个Image对象。

1
2
3
4
5
6
7
8
9
// 用于通过设备路径加载和运行镜像,EFI_SUCCESS命令执行成功,EFI_INVALID_PARAMETER参数无效,EFI_OUT_OF_RESOURCES资源耗尽,EFI_UNSUPPORTED不允许嵌套shell调用
EFI_STATUS
InternalShellExecuteDevicePath (
IN CONST EFI_HANDLE *ParentImageHandle, // 执行指定的image handle
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath, // 执行文件的设备路径
IN CONST CHAR16 *CommandLine OPTIONAL, // 指向包含命令行的UCS-2编码的字符串,NULL结尾
IN CONST CHAR16 **Environment OPTIONAL, // 环境变量数组,NULL结尾,格式为"x=y",其中x是环境变量名,y是值,为NULL则使用当前Shell环境
OUT EFI_STATUS *StartImageStatus OPTIONAL // 从gBS->StartImage()返回的状态
)

第一步:将UefiMain.efi文件加载到内存,生成Image对象,NewHandle是这个对象的句柄。

1
2
3
4
5
6
7
8
9
10
// 加载镜像时,FALSE表示不从boot manager加载,NULL和0表示镜像尚未加载到内存中
Status = gBS->LoadImage (FALSE, *ParentImageHandle, (EFI_DEVICE_PATH_PROTOCOL *)DevicePath, NULL, 0, &NewHandle);
if (EFI_ERROR (Status)) {
// 在NewHandle不为NULL(参数无效等情况下NewHandle为NULL就不需要卸载镜像)后卸载镜像并释放通过AllocatePool()为NewCmdLine分配的内存
if (NewHandle != NULL) {
gBS->UnloadImage (NewHandle);
}
FreePool (NewCmdLine);
return (Status);
}

函数指针LoadImage()指向函数CoreLoadImage(),函数内部再调用CoreLoadImageCommon()将EFI镜像加载到内存中,并返回镜像句柄,函数内部会优先判断是从内存加载还是从设备加载,如果SourceBuffer不为NULL,则返回指向内存位置的指针,此时DevicePath仅作为标识,不参与实际读取,SourceBuffer为NULL是最常见的Shell加载方式,此时会按照FirmwareVolume2(BIOS芯片内部的固件卷),SimpleFileSystem(文件系统),LoadFile2和LoadFile(PXE网络启动或没有本地存储的设备)的顺序解析HandleFilePath,然后解析的HandleFilePath获取源文件指针并保存到FHand.Source中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 如果传递了文件副本,就直接使用
if (SourceBuffer != NULL) {
FHand.Source = SourceBuffer;
FHand.SourceSize = SourceSize;
Status = CoreLocateDevicePath (&gEfiDevicePathProtocolGuid, &HandleFilePath, &DeviceHandle);
if (EFI_ERROR (Status)) {
DeviceHandle = NULL;
}
// 这里的错误处理后没有使用goto Done,因为SourceBuffer的优先级是高于DevicePath的,如果文件大小没问题,状态会被重置回EFI_SUCCESS,加载流程仍将继续
if (SourceSize > 0) {
Status = EFI_SUCCESS;
} else {
Status = EFI_LOAD_ERROR;
}
} else {
if (FilePath == NULL) {
// SourceBuffer和DevicePath都为NULL则直接返回
return EFI_INVALID_PARAMETER;
}
// 尝试通过检查匹配协议来获取镜像设备的句柄
Node = NULL;
Status = CoreLocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &HandleFilePath, &DeviceHandle);
if (!EFI_ERROR (Status)) {
// 针对FV需要特殊处理
ImageIsFromFv = TRUE;
} else {
HandleFilePath = FilePath;
Status = CoreLocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &HandleFilePath, &DeviceHandle);
if (EFI_ERROR (Status)) {
if (!BootPolicy) {
HandleFilePath = FilePath;
Status = CoreLocateDevicePath (&gEfiLoadFile2ProtocolGuid, &HandleFilePath, &DeviceHandle);
}
if (EFI_ERROR (Status)) {
HandleFilePath = FilePath;
Status = CoreLocateDevicePath (&gEfiLoadFileProtocolGuid, &HandleFilePath, &DeviceHandle);
if (!EFI_ERROR (Status)) {
// 针对PXE启动环境需要特殊处理
ImageIsFromLoadFile = TRUE;
Node = HandleFilePath;
}
}
}
}
// 通过设备路径获取源文件缓冲区
FHand.Source = GetFileBufferByFilePath (BootPolicy, FilePath, &FHand.SourceSize, &AuthenticationStatus);
if (FHand.Source == NULL) {
Status = EFI_NOT_FOUND;
} else {
// 标记内存用于临时存放文件数据,PE镜像被解析并完成移动后临时内存将被释放
FHand.FreeBuffer = TRUE;
// 针对PXE网络启动的环境需要重新修正完整路径,第二个文件节点路径将被附加到第一个网络设备路径,从而创建一个新的设备路径
if (ImageIsFromLoadFile) {
// LoadFile()可能会导致句柄的设备路径更新
OriginalFilePath = AppendDevicePath (DevicePathFromHandle (DeviceHandle), Node);
if (OriginalFilePath == NULL) {
// OriginalFilePath为NULL,说明新分配的缓冲区内存不足,返回EFI_OUT_OF_RESOURCES表示资源不足Image未能加载
Image = NULL;
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
}
}

读取文件后构建其对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 分配新的镜像结构
Image = AllocateZeroPool (sizeof (LOADED_IMAGE_PRIVATE_DATA));
if (Image == NULL) {
// 内存分配失败,goto Done处理错误
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
// 仅从LoadedImage完整的设备路径中提取文件路径中的文件部分
FilePath = OriginalFilePath;
if (DeviceHandle != NULL) {
Status = CoreHandleProtocol (DeviceHandle, &gEfiDevicePathProtocolGuid, (VOID **)&HandleFilePath);
if (!EFI_ERROR (Status)) {
FilePathSize = GetDevicePathSize (HandleFilePath) - sizeof (EFI_DEVICE_PATH_PROTOCOL);
FilePath = (EFI_DEVICE_PATH_PROTOCOL *)(((UINT8 *)FilePath) + FilePathSize);
}
}
// 初始化内部驱动程序的字段
Image->Signature = LOADED_IMAGE_PRIVATE_DATA_SIGNATURE;
Image->Info.SystemTable = gDxeCoreST;
Image->Info.DeviceHandle = DeviceHandle;
Image->Info.Revision = EFI_LOADED_IMAGE_PROTOCOL_REVISION;
Image->Info.FilePath = DuplicateDevicePath (FilePath);
Image->Info.ParentHandle = ParentImageHandle;
if (NumberOfPages != NULL) {
Image->NumberOfPages = *NumberOfPages;
} else {
Image->NumberOfPages = 0;
}

镜像安装过程分四步,其中任何一步出错都必须使用goto Done回滚操作:

  1. CoreInstallProtocolInterfaceNotify()为Image安装第一个LoadedImage协议接口,内部AllocateZeroPool()为Image->Handle和Prot分配内存并初始化,Image->Handle->AllHandles加入gHandleList全局链表,Prot->Handle指向Image->Handle,Prot->Protocol指向ProtEntry,Prot->Interface指向Image->Info,Prot->Link加入Image->Handle->Protocols私有链表,Prot->ByProtocol加入Image->ProtEntry->Protocols全局链表;
  2. CoreLoadPeImage()加载并重定位PE镜像,内部PeCoffLoaderGetImageInfo()解析PE头,PeCoffLoaderLoadImage()加载镜像到分配的内存,PeCoffLoaderRelocateImage()重定位镜像在内存的地址,最后判断EntryPoint不为NULL后通过指针返回,Image->Info的ImageBase,ImageSize,ImageCodeType,ImageDataType等结果将从Image->ImageContext写回(为了避免产生脏数据,中间的计算结果会保存在Context,并在最后全部同步到Image->Info);
  3. CoreReinstallProtocolInterface()为Image更新协议接口,内部CoreRemoveInterfaceFromProtocol()将(旧)Image->Info从Image->Handle移除,更新Prot->Interface指向(新)Image->Info,Prot->ByProtocol重新加入ProtEntry->Protocols,最后CoreNotifyProtocolEntry()遍历Prot->Protocol->Notify通告其他模块LoadedImage协议接口就绪;
  4. CoreInstallProtocolInterface()作为公共API,内部调用CoreInstallProtocolInterfaceNotify()继续为Image安装第二个LoadedImageDevicePath协议接口,初始化过程与安装LoadedImage协议接口类似,不同的是此时Notify参数为True会触发CoreNotifyProtocolEntry()通告其他模块LoadedImageDevicePath协议接口就绪。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 安装镜像的协议接口,暂不触发通告
Status = CoreInstallProtocolInterfaceNotify (&Image->Handle, &gEfiLoadedImageProtocolGuid, EFI_NATIVE_INTERFACE, &Image->Info, FALSE);
if (EFI_ERROR (Status)) {
goto Done;
}
// 加载镜像,如果EntryPoint为NULL则不会被设置
Status = CoreLoadPeImage (BootPolicy, &FHand, Image, DstBuffer, EntryPoint, Attribute);
if (EFI_ERROR (Status)) {
if ((Status == EFI_BUFFER_TOO_SMALL) || (Status == EFI_OUT_OF_RESOURCES)) {
if (NumberOfPages != NULL) {
*NumberOfPages = Image->NumberOfPages;
}
}
goto Done;
}
if (NumberOfPages != NULL) {
*NumberOfPages = Image->NumberOfPages;
}
// 重新安装已加载的镜像协议以触发任何通告
Status = CoreReinstallProtocolInterface (Image->Handle, &gEfiLoadedImageProtocolGuid, &Image->Info, &Image->Info);
if (EFI_ERROR (Status)) {
goto Done;
}
// 为镜像安装设备路径协议
Status = CoreInstallProtocolInterface (&Image->Handle, &gEfiLoadedImageDevicePathProtocolGuid, EFI_NATIVE_INTERFACE, Image->LoadedImageDevicePath);
if (EFI_ERROR (Status)) {
goto Done;
}
// 成功返回镜像句柄
*ImageHandle = Image->Handle;

第二步:取得命令行参数,并将命令行参数交给UefiMain.efi的Image对象,即NewHandle。

1
2
3
4
5
6
7
8
9
10
Status = gBS->OpenProtocol (NewHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!EFI_ERROR (Status)) {
// 如果镜像不是应用,终止操作
if (LoadedImage->ImageCodeType != EfiLoaderCode) {
ShellPrintHiiDefaultEx (
STRING_TOKEN (STR_SHELL_IMAGE_NOT_APP),
ShellInfoObject.HiiHandle
);
goto UnloadImage;
}

第三步:启动所加载的Image。

1
2
3
4
5
6
7
8
9
if (!EFI_ERROR (Status)) {
StartStatus = gBS->StartImage (NewHandle, 0, NULL);
if (StartImageStatus != NULL) {
*StartImageStatus = StartStatus;
}
CleanupStatus = gBS->UninstallProtocolInterface (NewHandle, &gEfiShellParametersProtocolGuid, &ShellParamsProtocol);
ASSERT_EFI_ERROR (CleanupStatus);
goto FreeAlloc;
}

函数指针StartImage()指向函数CoreStartImage(),函数内部将控制权转移到已加载镜像的入口点,通过AllocatePool()申请内存,使用ALIGN_POINTER保证JumpContext符合CPU架构的内存对齐要求(这一步主要针对XMM和SSE寄存器),对于X64的函数调用规则,SetJump()会负责将需要的Rbx,Rsp,Rbp,Rdi,Rsi,R12,R13,R14,R15,Rip,MxCsr,XmmBuffer(交接前会主动保存浮点上下文环境),Ssp寄存器的内容保存到JumpBuffer,首次调用SetJumpFlag必定为0,控制权的交接发生在Image->EntryPoint()(应用程序的ImageHandle和SystemTable在这里传递),应用程序返回有两种方式,一种是使用return EFI_SUCCESS正常退出,这样将向下执行CoreExit(),内部调用LongJump()重新回到SetJump(),此时SetJumpFlag为-1(值-1由LongJump()传递,注意真正的InternalLongJump()函数内容在MdePkg/Library/BaseLib/X64/LongJump.nasm文件,X64调用约定JumpBuffer在rcx,Value在rdx),跳过分支执行后续清理流程;另一种是使用gBS->Exit()强制退出,内部调用LongJump(),过程类似,唯一不同的是此时SetJumpFlag为传给Exit()的非0状态码(不了解这两个函数功能的搜索C语言使用setjmp()和longjmp()实现异常机制(这种非局部跳转的特点解决了goto局部跳转的问题),应用在这里是为了避免程序嵌套过深陷入兔子洞的情况,这种方法可以将堆栈立即重置回原来状态而无需解析整个程序的调用链,并且提供了返回值和字符串指针说明原因,需要注意除非遇到非常具体的情况,否则最好避免使用它)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 设置Exit()支持的长跳转JmpContext必须与CPU特定的边界对齐,重新分配缓冲区并进行所需的强制对齐
Image->JumpBuffer = AllocatePool (sizeof (BASE_LIBRARY_JUMP_BUFFER) + BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);
if (Image->JumpBuffer == NULL) {
// 返回失败后镜像可能会被卸载,此时ImageHandle可能无效,使用NULL句柄记录性能日志
PERF_START_IMAGE_END (NULL);
// 弹出当前起始镜像上下文
mCurrentImage = LastImage;
return EFI_OUT_OF_RESOURCES;
}
Image->JumpContext = ALIGN_POINTER (Image->JumpBuffer, BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);
SetJumpFlag = SetJump (Image->JumpContext);
// 首次调用SetJump()必须始终返回0,后续调用LongJump()会导致SetJump()返回非0值
if (SetJumpFlag == 0) {
RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
// 调用镜像入口点
Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
// 如果镜像返回错误,添加调试信息让用户了解情况,并检查驱动程序镜像在这种情况下是否已经释放所有资源
DEBUG_CODE_BEGIN ();
if (EFI_ERROR (Image->Status)) {
DEBUG ((DEBUG_ERROR, "Error: Image at %11p start failed: %r\n", Image->Info.ImageBase, Image->Status));
}
DEBUG_CODE_END ();
// 如果镜像返回,通过Exit()退出
CoreExit (ImageHandle, Image->Status, 0, NULL);
}

引言

统一可扩展固件接口(UEFI)规范是操作系统与平台固件之间的接口描述,其前身是可扩展固件接口规范 1.10(EFI),规范中出现的EFI名称均可视为UEFI的一部分(除非另有说明)。

UEFI是纯粹的接口规范,以数据表的形式提供平台相关信息以及操作系统(OS)和其加载器(OS Loader)所使用的启动服务(Boot Services)和运行时服务(Runtime Services),通过仅传递引导所需的关键信息,在平台固件与操作系统之间构建了一个完全抽象的软件接口,从而实现了对硬件复杂性的解耦。

比如同样进行内存探测,早期的x86架构依赖实模式下的E820、E801、0x88中断,而ARM架构则需要ATAGS传参或解析设备树信息,内核(Kernel)必须针对不同平台固件实现不同的底层探测逻辑,而在UEFI环境下,内核只需要找到EFI_BOOT_SERVICES表并调用GetMemoryMap()接口,返回的永远是标准的EFI_MEMORY_DESCRIPTOR结构体数组。

UEFI规范实现了从移动端到服务器的全平台覆盖,其提供的核心服务及接口服务在实现标准化的同时允许平台引入增强性能的新特性和功能,为OEM厂商提供了最大的扩展性和定制能力,实现其差异化。UEFI的目标就是实现从传统”PC-AT”式启动环境转向现代无传统API环境。

概述

UEFI允许通过加载UEFI驱动程序(UEFI driver)和UEFI应用程序(UEFI application)扩展平台固件。UEFI驱动程序和UEFI应用程序被加载后可以访问所有UEFI定义的运行时和启动服务。

图2.1 启动顺序

UEFI允许将操作系统加载器和平台固件的启动菜单合并到一个平台固件菜单,其允许用户从任何受UEFI启动服务支持的启动介质上的任何分区中选择任何UEFI操作系统加载程序,一个UEFI操作系统加载器可以支持多个选项,另外平台固件的启动菜单还可以包含传统的启动选项。

相较于传统引导环境必须通过MBR启动后才能实现多重引导(Multiboot),UEFI规范允许固件直接解析并读取系统分区(ESP)中的程序,实现固件菜单和引导菜单的逻辑合一,操作系统加载器通过在NVRAM中注册多个启动项让用户可以直接在固件层面进行选择,同时兼容性支持模块(CSM)的存在确保UEFI对传统引导模式的向下兼容。

UEFI支持从包含UEFI操作系统加载程序或UEFI定义的系统分区的介质启动,UEFI需要UEFI定义的系统分区才能从块设备启动,UEFI不需要对分区的首扇区进行任何更改,因此满足条件的介质既能在传统架构上启动,也能在UEFI平台上启动。