本文是对《AMD64 Architecture Programmer’s Manual Volume 2: System Programming》的第14章?Processor Initialization and Long Mode?Activation 的中文意译,叫意译而不叫翻译是因为我会按照自己的理解和意愿来进行一些结构上的调整、内容上的修改和补充,因此并非是严格遵照原文进行的翻译。之所以意译这一章,是因为我最近开始学习UEFI,这一章的内容对UEFI来说是重中之重,为了巩固对本章知识的掌握,特此意译。
这一章描述了CPU重启时产生的一系列硬件行为和初始化CPU资源并激活long mode必须进行的操作步骤。对不同型号的CPU可能会略有不同,不同之处会在具体CPU的文档中给出说明。
1.? CPU的初始化
当CPU重新加电时,CPU的所有资源处于一种已知的固定的状态,我们叫它初始状态,处于初始状态的CPU会以实模式开始运行IP处的指令。
在CPU有稳定供电的情况下,有两种让CPU进入初始状态的方式。
第一种方式叫RESET, 通过RESET#外部信号触发。
第二种方式叫INIT,通过另一个CPU发出跨CPU中断(interprocessor interrupt, IPI)触发。
这两种途径都会将CPU置于初始状态,但这种说法并不严格,因为INIT方式进入的状态与初始状态有微小的差别,它会保留某些寄存器的状态,比如XMM/YMM寄存器,而初始状态中这些寄存器的值会清零。RESET方式进入的状态是完整的初始状态。
CPU的初始状态如下:
1.1 内置自检 (Built-In Self Test,BIST)
在CPU进入初始状态后,可以进行内置自检,但这是可选的,是否选择进行内置自检可以在CPU的文档中找到设置方法。
内置自检一般需要几百万个时钟周期,那也就是一秒不到的时间。
内置自检能检验系统的完整性,改进系统的可靠性,它基本上检测了所有的内部结构,比如:
(1). 内部缓存
(2). 所有的TLB (TLB是CPU中的一种高速缓存,主要作用是解决虚拟地址和物理地址之间映射关系的快速查找)
(3). 内部ROM,比如微代码ROM和浮点常数ROM(microcode ROM ,floating-point constant ROM)
(4). 分支预测结构(Branch-prediction structures)
当内置自检结束后,如果没有检测到错误,则EAX的值将被设置为0,否则就表示检测到错误了。
1.2 时钟倍率选择(Clock Multiplier Selection)
CPU的内部时钟频率是主板系统时钟频率的整数倍,这个倍率是可以通过硬件或者软件来动态设置的。
1.3 多CPU系统的初始化(Multiple Processor Initialization)
如果一个系统被设置成使用多个CPU,这些CPU将通过多CPU初始化协议(multiple-processor? initialization protocol)协商选出启动CPU(bootstrap processor),然后由启动CPU来执行系统初始化代码,其它的CPU将等待它运行结束。
1.4 获取第一条指令( Fetching the First Instruction)
在通过加电、RESET或者INIT进入初始状态以后,CPU处于16位实模式中。
在实模式中,代码段的基址(code-segment base-address)是将CS寄存器的值左移4位得到的,这个基址加上IP寄存器的值就得到了当前指令在内存中的物理地址,这样,实模式只能寻址1Mbyte的内存。
然而,在现代CPU中,CS寄存器有一个用于保存其基址的附属寄存器,这个附属寄存器是隐性的,在CPU的初始状态,CS寄存器的值被初始化为F000h,而这个隐性的基址寄存器被初始化为FFFF_0000h,也就是说,在CPU的初始状态,代码段的基址暂时性地违背了等于CS左移4位的原则,导致了在16位实模式下能运行32位地址代码的奇怪现象。
此外,EIP的值被初始化为FFF0h,这样,CPU在初始状态下运行的第一条指令的内存物理地址为FFFF_FFF0h (FFFF_0000h + 0000_FFF0h),存储BIOS/UEFI代码的EPROM/NOR?FLASH芯片就必须位于这个地址。
代码段的基址会保持为FFFF_0000h直到CS寄存器的值被更改,CS寄存器的值被修改以后将遵循实模式的寻址规则将CS的值左移4位以后得到代码段的基址。
因此,在运行初始化代码时,为了确保代码段的基址不被更改,要禁止运行far jump,far call指令,以及屏蔽中断,因为它们将导致CS寄存器被修改。
电脑主板上存储BIOS的芯片是NOR?FLASH,NOR?Flash最早由Intel公司于1988年开发,是现在市场上两种主要的非易失性存储器之一,它的出现彻底改变了存储器市场上由EPROM(Erasable?Programmable?Read-Only-Memory电可编程序只读存储器)和EEPROM(电可擦只读存储器Electrically?Erasable?Programmable?Read-Only?Memory)一统天下的局面。NOR?Flash最大特点是支持XIP(Execute?On?Chip),即NOR?flash内的代码可以直接被CPU寻址执行,不需要复制到RAM以后再执行。NOR?FLASH通过SPI接口和南桥PCH相连, 当CPU发送地址0xFFFFFFF0到北桥上解码时,因为该地址没有被北桥上的设备占用,那么就会通过DMI通道发送到南桥上的设备进行解码,此地址会被南桥的SPI控制器接收,SPI控制器负责翻译地址(Address Decode),就从NOR Flash中得到相应的指令和数据,这就是为什么内存尚未初始化BIOS/UEFI代码就能被获取执行的原因。
2. 硬件配置
2.1 CPU信息
在CPU的初始状态中,RDX寄存器中的内容是CPU的特征信息,包括CPU的型号和支持的扩展功能等,说明如下:
? 更新版本号(Stepping ID,bits 3:0) — CPU的更新版本(revision level)
? 扩展型号(Extended Model,bits 19:16) 和自身型号(Model,bits 7:4) — 这2个型号合起来表示了CPU在一个微架构(microarchitecture)中属于哪个型号,比如两个CPU属于同一个微架构,但是所支持的特性集有所不同,这两个CPU的型号就不同。
? 扩展系统(Extended Family,bits 27:20) 和系统(Family,bits 11:8) — 这2个系统合起来表示了CPU所属的微架构(microarchitecture)
上述信息也可以通过CPUID指令(参数 1 或者 8000_0001h)随时获取到,其它更多的关于CPU的信息可以通过CPUID指令的其它参数获取到。
2.2 启动内部缓存(Enabling Internal Caches)&?内存类型区段寄存器(Memory-Type Range Registers, MTRRs)
在CPU的初始状态中,所有的内部缓存都是被禁用的,缓存的内容被设置成无效状态,cache-disable bit (CR0.CD)被设置为1。
通过将cache-disable bit (CR0.CD)设置为0,缓存被启用,还可以通过分页和内存区域来进一步调整缓存的功能。
在CPU的初始状态中,内存类型区段寄存器(MTRRdefType)被设置为0,这表示设置内存类型区段的功能被禁用,同时,指定可变长度和固定长度内存类型区段的寄存器没有被初始化,处于未定义的状态,因此,在启用内存类型区段机制(the MTRR mechanism)之前,要先设置好这些寄存器的值。
内存类型区段寄存器(MTRRdefType)被设置为0,或者可变长度和固定长度内存类型区段寄存器被设置为0,都表示整个内存都被设置成禁止缓存的类型。
通过INIT方式进入的初始状态则保持上述相关寄存器为原来的状态。
2.3 初始化x87处理器(Initializing Media and x87 Processor State)
在使用x87处理器进行媒体和浮点运算前必须先初始化一些资源,通常用FINIT或者FNINIT指令来对x87处理器进行初始化,可以使用CPUID指令来检查CPU是否支持这些指令。FINIT和FNINIT指令对64-bit media状态没有影响。
通过INIT方式进入的初始状态保持x87处理器为原来的状态。
下图显示了CPU初始状态下和执行FINIT/FNINIT指令后x87处理器所处的状态的不同之处。
初始化程序还应该将CR0寄存器的MP, EM和NE bits设置好,推荐的设置是这样的:
? MP=1— 当task-switched bit (CR0.TS)为1时执行FWAIT/WAIT指令时,将触发device-not-available exception (#NM),这将支持操作系统在切换任务时对x87浮点状态进行懒处理。
? EM=0— 允许x87处理器执行指令,而不是发出#NM异常。如果想用软件来模拟x87浮点指令,则将EM设置为1,以便触发#NM异常,在异常中进行模拟处理。
? NE=1— 使x87浮点exceptions由floating-point?exception-pending exception (#MF) handler来处理,NE=0则将浮点exceptions交给外部设备通过触发外部中断(external interrupt)来处理。
64位MMX寄存器的初始化(64-Bit Media State Initialization)
使用64位MMX指令不需要特别的初始化操作,CPU的初始状态就已经将它们初始化好了。
在后续的使用中要注意保持CR0.EM为0才能使用MMX指令,如果EM为1,运行MMX指令将触发#UD异常(invalid-opcode exception)。
INIT不改变64位MMX寄存器的状态。
SSE寄存器的初始化(SSE State Initialization)
使用SSE指令前需要先进行如下初始化操作:
? CR0.EM设为0,如果EM为1,运行SSE指令将触发#UD异常(invalid-opcode exception),运行FXSAVE/FXRSTOR指令将触发#NM exception
??CR4.OSFXSR设为1,这也表示系统软件将使用FXSAVE和FXRSTOR指令来保存和加载SSE状态,这两个指令同时也保存和加载64位的MMX/80位的浮点寄存器状态。
??CR4.OSXMMEXCPT设为1,这表示系统软件将使用SIMD floating-point exception (#XF)来处理SSE浮点异常。
? (可选操作)设置MXCSR掩码来处理SSE浮点异常,因为应用软件也可以设置和读取MXCSR,因此对系统软件来说这个操作不是绝对必要的。
2.4 模块初始化(Model-Specific Initialization)
不同型号的处理器能包含不同的特性和寄存器,CPU进入初始状态时不会对它们进行初始化,因此就需要系统软件来做初始化的工作。
系统软件必须使用CPUID指令来探测哪些特性被支持,对特性的配置一般使用model-specific registers (MSRs),RDMSR 和 WRMSR 指令分别用来读取和写入MSRs.
有一些在很多CPU中都普遍存在的特性会在本册中进行说明,它们包括:
? System-call extensions, 在使用SYSCALL和SYSRET指令前必须通过EFER寄存器启用这个扩展,
? Memory-typing MSRs
? The machine-check mechanism
? Extensions to the debug mechanism
? The performance-monitoring resources
page-translation mechanism 和 long mode 使用到的特性的初始化操作会在接下来进行说明。
一些不是很普遍存在的特性的初始化操作不会在本册中进行说明,可以在
3. 实模式的初始化(Initializing Real Mode)
必须先初始化好基本的实模式(real-mode)操作环境,然后系统软件才能开始初始化保护模式(protected-mode)的操作环境。
实模式环境包括:
? 一个实模式IDT(Interrupt Descriptor Table) 用于获取实模式下中断和异常的处理代码入口地址,IDTR寄存器中的地址可以作为IDT的基址,系统软件也可以修改IDTR寄存器来使用新的基址。
? 与IDT中的处理代码入口地址相应的代码,用于处理相应的中断和异常。
在以上两项设置好之前,系统软件必须禁用non-maskable interrupt (NMI),因为一旦启用,CPU随时都有可能收到non-maskable interrupt (NMI).
另一种方法是,将IDT和NMI的处理代码放在固件中,且让IDTR的初始值指向固件中的IDT.
Maskable interrupts可以通过EFLAGS.IF来设置启用和禁用,当然也需要在IDT和相关的处理代码都设置好以后才能启用。
? 一个可用的stack指针(SS:SP)用于中断和异常处理代码的运行,可以使用SS:SP的初始值。
? 一个或者多个data-segment selectors用于存储protected-mode的数据结构,这是为进入保护模式做准备工作。
然后系统软件就可以进入保护模式了。
4. 保护模式的初始化(Initializing Protected Mode)
在激活长模式(long mode)前必须先进入保护模式(Protected mode),进入保护模式前必须先设置好最基本的保护模式环境,这包括以下内容:
??一个保护模式 IDT (Interrupt Descriptor Table) 用于获取保护模式下中断和异常的处理代码入口地址
??与IDT中的处理代码入口地址相应的保护模式代码,用于处理相应的中断和异常。Gate descriptors(内核级处理代码入口)也必须位于这个IDT中。
? 一个GDT(Global Descriptor Table),包含以下内容:
? ? - 一个在保护模式下运行的code segment descriptor
? ? - 一个可读写的data segment作为保护模式stack,以便中断和异常发生时可以使用它。
除了以上必备的内容,系统软件还可以提供一些可选的内容以供长模式初始化软件使用,比如在GDT中加入一个或多个data segment descriptors,一个TSS descriptor, 一个LDT?descriptor。
做好以上设置工作以后,系统软件还必须将IDTR和GDTR分别填入前面设置好的IDT和GDT的基址,然后就可以设置CR0.PE为1来启动保护模式。
如果在初始化长模式的过程中使用legacy paging,必须先设置好page-translation tables,然后启用paging.
最基本的legacy分页设置需要一个page directory和一个page table,还必须在CR3中载入最高一级的page table的起始物理地址,做好这些设置以后就可以设置CR0.PG为1来启动内存分页机制。
(未完待译)