深入UEFI内核
前面通过《UEFI原理与编程》一书介绍了如何使用UEFI编写应用程序和驱动,编程一书是从上层应用和驱动开发者的角度认识UEFI的,UEFI就像一个黑盒子,书中详细介绍了这个黑盒子的表面(即UEFI提供给上层开发者的接口和服务)。接口通过Protocol呈现给开发者。主要的Protocol包括控制台输入输出Protocol;文件及硬盘Protocol;操作外部设备的Protocol(PciIo等);驱动框架Protocol;人机交互接口Protocol;网络Protocol。服务通过启动服务和运行时服务提供,主要包括Protocol服务,内存管理服务,事件管理服务等。UEFI虽然庞大,但它通过模块化被很清晰的组织在一起,当逐步掌握了这些主要的Protocol和服务之后,UEFI也就变得简单起来,那么是时候深入到这个盒子内部,了解UEFI内核的运行机制了。
下面将以系统启动过程为主线介绍UEFI内核。
第一条指令(ResetVector)
先说结论:X86 CPU启动后,将从地址0xFFFFFFF0处开始执行(此地址并非内存地址。此时,内存还远远没有初始化。)。这一章来看X86系统是如何实现这一点的。
加电或者RESET针脚被激发(Assert)后[ref intel] CPU会经历如下几个过程:
1. CPU首先会进行硬件初始化(hardware reset)。
2. 然后是可选的自检过程(BIST built-in self-test)。
3. CPU开始执行第一条指令。从此开始CPU进入软件初始化过程。
1.CPU硬件初始化
CPU硬件初始化完成后,CPU被设置为实地址模式,地址无分页。所有寄存器被初始化为特定的值, Cache、TLB(Translation Lookup Table)、BLB(Branch Target Buffer)这三个部件的内容被清空(Invalidate)。
2.自检
CPU硬件初始化过程中,硬件可能请求执行自检。如果执行自检,自检完成后,EAX的值为自检错误码,0表示没有任何错误;
3.第一条指令
现在,完事俱备,CPU已经准备好,迫不及待地要执行第一条指令了。且慢,这是一个重要的时刻,此刻决定了CPU能否正常指令,让我们详细了解一下CPU目前的状态。
表1-1 CPU初始化后的寄存器(部分)RegisterPentium 4 and Intel Xeon ProcessorP6 Family Processor Including DisplayFamily = 06H)Pentium ProcessorEFLAGS100000002H00000002H00000002H
EIP0000FFF0H0000FFF0H0000FFF0H
CR060000010H60000010H60000010H
CR2, CR3, CR400000000H00000000H00000000H
CSSelector = F000H
Base = FFFF0000H
Limit = FFFFH
AR = Present, R/W, AccessedSelector = F000H
Base = FFF0000H
Limit = FFFFH
AR = Present, R/W, AccessedSelector = F000H
Base = FFFF0000H
Limit = FFFFH
AR = Present, R/W, Accessed
SS, DS, ES, FS, GSSelector = 0000H
Base = 00000000H
Limit = FFFFH
AR = Present, R/W, AccessedSelector = 0000H
Base = 00000000H
Limit = FFFFH
AR = Present, R/W, AccessedSelector = 0000H
Base = 00000000H
Limit = FFFFH
AR = Present, R/W, Accessed
EDX00000FxxH000n06xxH000005xxH
EAX000
EBX, ECX, ESI, EDI, EBP,ESP00000000H00000000H00000000H
此处我们最关心的是指令执行相关的两个寄存器EIP(Instruction Pointer)、CS(Code Segment)。
在实地址模式下(寄存器字长为16位),指令的物理地址是CS << 4 + EIP。段寄存器CS左移四位作为基址,再加上作为偏移的EIP,最终形成指令的物理地址。现代CPU中为了加速指令地址的计算,为每个段寄存器增加了两个寄存器:Base和Limit。Base存放基址,Limit存放最大偏移值。Base和Limit寄存器不能通过指令直接读写,他们的值是在写段寄存器时由CPU自动设置的。通常Base等于段寄存器左移四位,如果CS的值为0xF000,CS的Base寄存器则为0xF0000,但CPU初始化时例外。从表1-1可以看出CS的值为0xF000, 但其Base为0xFFFF0000,EIP为0xFFF0,此时对应的指令地址为0xFFFF0000+0xFFF0 = 0xFFFFFFF0。0xFFFFFFF0就是CPU将要执行的第一条指令。这造成这样一个有趣的事实,16位程序眼中的指令地址空间0x0000~0xFFFF(大小为64K)被CPU翻译到物理地址空间(0xFFFF0000~0xFFFFFFFF)。也就是说,从CPU初始化,到段寄存器被重写(通过跨段跳转指令)前,指令空间0x0000~0xFFFF通过段寄存器被映射到物理地址空间0xFFFF0000~0xFFFFFFFF。
前面讲到第一条指令地址为0xFFFFFFF0,X86系统初始化时会将ROM中的固件映射的(0xFFFFFFFF-固件大小)~0xFFFFFFFF的地址空间。故而0xFFFFFFF0对应ROM中的某条指令,无论ROM中存放的是传统的BIOS固件,还是存放的UEFI固件,这个规则都是一样的。下面将从这天指令开始继续CPU初始化之旅。
开始讲0xFFFFFFF0对应的指令之前,还要熟悉UEFI ROM的的结构。
ROM固件(Flash Device binary image)由一个或多个Firmware volume(FV)构成,每个FV里存放了FFS Image(EFI Firmware File system),FFS Image则由