1. 简介

上电或者重启后 , 系统总线上的每个处理器执行硬件初始化和可选的 BIST , 将寄存器初始化为已知状态 , 进入实地址模式 . 并且无效内部 cache , TLB 和 branch target buffer ( BTB ) .
然后 , 系统总线上的所有处理器执行 MP 初始化协议 , BSP 执行 EIP 指明的当前代码段中的软件初始化代码 , AP 在 BSP 执行初始化代码时进入 wait for Startup IPI ( SIPI ) 状态 .
此时 , MP 系统中 BSP 唤醒每个 AP , 使其执行自配置代码 . 所有的处理器初始化 , 配置 , 同步后 , BSP 开始执行初始的 OS 或者可执行程序 .

使能处理器的 INIT# 引脚的效果和硬件初始化类似 , 主要区别是 INIT 期间 , 内部 cache , MSR , MTRR , x87 FPU 的状态不会改变 ( 尽管 TLB 和 BTB 会无效 ) . INIT 提供了一种维持内部 cache 内容不变的前提下从保护模式切换到实地址模式的方式 .

处理器通过 BIST 后 , 清除 EAX 为 0 , BIST 的开销和处理器的型号相关 .

硬件重置后 , EDX 包含处理器的型号信息和 stepping 信息 .

硬件重置后执行的第一条指令位于物理地址 0xFFFFFFF0H , 即处理器的最高物理地址的 16 字节下 , 包含软件初始化代码的 EPROM 必须位于这个地址 .
实地址模式下 , 0xFFFFFFF0H 超过了处理器的 1MB 可寻址范围 , 处理器通过下面的方式初始化为这个开始地址 :
CS 包含两部分 , 可见的段选择器部分和隐藏的基地址部分 . 实地址模式下 , 基地址通过左移段选择器 4 位得到 . 硬件重置时 , CS 寄存器中的段选择器加载为 0xF000H , 基地址加载为 0xFFFF0000H , 起始地址通过基地址加上 EIP 中的值得到 ( F000H « 4 + FFF0H = FFFFFFF0H ) .

2. x87 FPU 初始化

硬件重置后 x87 的状态和 FINITFNINIT 指令执行后 x87 的状态不同 , 要使用 x87 FPU , 软件初始化代码应该在硬件重置后执行 FINITFNINIT 指令 .
处理器通过 INIT# 引脚重置后 , x87 FPU 的状态不会改变 .

硬件重置后 , CR0 的 MP , EM , NE 标志会被清除 , 初始化代码必须加载合适的值到这些寄存器 .

设置 EM 标志会使处理器在执行浮点指令时产生一个 #NM 异常 , 陷入一个软件异常处理函数 . 这样 , 可以在异常处理函数中实现一个浮点模拟器处理浮点指令 .

3. 实地址模式软件初始化

硬件重置后 , 处理器进入实地址模式 , 从物理地址 0xFFFFFFF0H 执行软件初始化代码 , 后者必须建立基本的系统功能所需要的数据结构 , 比如实地址模式的 IDT , 用于处理中断和异常 .
如果处理器要停留在实地址模式 , 软件还要加载额外的操作系统或者可执行程序和数据结构 , 以便在实地址模式执行应用 .

如果处理器要工作在保护模式 , 软件必须加载保护模式下运行所需必要的数据结构 , 然后切换到保护模式 .

实地址模式下必须加载到内存中的系统数据结构是 IDT , 其默认基地址是 0H , 这个地址可以通过 LIDT 指令修改 . 中断或者异常处理函数的代码必须放在实地址模式可以寻址的 1MB 地址空间内 .

NMI 总是开启的 , 如果 IDT 和 NMI 处理函数需要加载到 RAM , 硬件重置后就会有一段时间无法处理 NMI . 期间 NMI 可以通过下面两种方式处理 :

  • 通过 EPROM 提供一个简单的 IDT 和 NMI 处理函数 .
  • 系统硬件提供一种机制通过 IO 端口内的一个标志控制的与门传递的 NMI# 信号开启和关闭 NMI , 硬件可以在处理器重置后清除标志 , 软件可以再准备好处理 NMI 中断后设置标志 .

4. 保护模式软件初始化

处理器切换到保护模式前 , 软件初始化代码必须加载一些保护模式的数据结构和代码模块到内存 , 支持保护模式下的处理器操作 , 包括 :

  • 一个 IDT
  • 一个 GDT
  • 一个 TSS
  • 可选的一个 LDT
  • 如果要使用分页 , 至少一个页目录和一个页表
  • 一个包含处理器切换到保护模式时要执行的代码的代码段
  • 一个或多个包含必要的中断和异常处理函数的代码模块

软件初始化代码在切换到保护模式前必须初始化下列系统寄存器 :

  • GDTR
  • 可选的 IDTR , 可以在切换到保护模式后 , 开启中断前立即初始化
  • CR1 - CR4
  • MTRR

软件初始化阶段加载到内存中的数据结构主要取决于保护模式下操作系统使用的内存管理模型 .

如果要使用多任务机制 , 并且支持特权级之间的切换 , 需要加载一个 TSS 到内存 , 这是因为特权级 0 , 1 和 2 的栈段指针需要通过 TSS 获取 .

4.1. 初始化 IA-32e 模式

Intel 64 处理器 , IA32_EFER MSR 在系统重置后清除 , 操作系统必须在保护模式下开启分页后才能初始化 IA-32e 模式 , IA-32e 模式操作还需要开启了 4 级或 5 级分页的物理地址扩展 ( PAE ) .
操作系统需要遵守下面的序列初始化 IA-32e 模式 :

  1. 保护模式下 , 设置 CR0.PG = 0 关闭分页 , 使用 MOV CR0 指令 .
  2. 设置 CR4.PAE = 1 开启 PAE , 失败时会产生 #GP fault .
  3. 加载 PML4 或 PML5 的物理基地址到 CR3 .
  4. 设置 IA32_EFER.LME = 1 开启 IA-32e 模式 .
  5. 设置 CR0.PG = 1 开启分页 , 这会导致处理器设置 IA32_EFER.LMA = 1 . 开启分页的 MOV CR0 指令必须和后续的指令位于同一个映射页中 .

激活 IA-32e 模式前 , 64 位模式的分页结构必须位于物理地址空间的前 4GB , 这是因为初始化页目录基地址的 MOV CR3 指令必须在激活 IA-32e 模式前在传统模式下执行 . 由于 MOV CR3 在保护模式下执行 , 只有寄存器的低 32 位被写入 , 限制页表在内存的低 4GB . 软件可以在激活 IA-32e 模式后将页表放到物理内存的任何位置 .

任何时候软件试图修改直接影响激活 IA-32e 模式的使能位 ( IA32_EFER.LME , CR0.PG , CR4.PAE ) 时 , 处理器都会进行 64 位模式一致性检查 , 失败时产生 #GP , 保证处理器不会进入未定义的模式或状态 .

下列情形中 64 位模式一致性检查会失败 :

  • 打开分页时试图开启或关闭 IA-32e 模式
  • 开启了 IA-32e 模式 , 试图在开启 PAE 前打打开分页
  • IA-32e 已经激活 , 试图关闭 PAE
  • 激活 IA-32e 模式时当前的 CS 设置了 L 位
  • 激活 IA-32e 模式时当前的 TR 包含一个 16-bit 的 TSS

激活 IA-32e 模式后 , 系统描述符表寄存器 ( GDTR , LDTR , IDTR , TR ) 引用的都是传统的保护模式下的描述符表 , 都保存在低 4GB 的线性地址空间中 . 激活 IA-32e 模式后 , 操作系统应该使用 LGDT , LLDT , LIDT , LTR 加载 64 位描述符表的引用 .

激活 IA-32e 模式和更新 64 位 IDT 之间 , 软件不能允许中断或异常的发生 .

4.1.1. 64 位模式和兼容模式

IA-32e 模式下 , 两个代码段描述符中的位 ( CS.L 和 CS.D ) 控制初始化后的操作模式 ( 64 位模式或兼容模式 ) . 因此 , 兼容模式的执行基于代码段 , 从而允许传统的应用和 64 位应用在 64 位模式下共存 .

兼容模式下以下系统层机制仍然使用 IA-32e 模式的架构语义 :

  • 线性地址到物理地址的转化使用 64 位模式扩展的页转化机制
  • 中断和异常使用 64 位模式的机制处理
  • 系统调用使用 IA-32e 模式的机制处理

4.1.2. 切换出 IA-32e 模式

要从 IA-32e 模式返回分页的保护模式 , 操作系统必须使用下面的序列 :

  1. 切换到兼容模式
  2. 清除 CR0.PG = 0 , 关闭 IA-32e 模式 , 导致处理器设置 IA32_EFER.LMA = 0 . 用于关闭分页的 MOV CR0 指令必须和后续的指令位于同一个映射页
  3. 加载传统的页表目录的基地址的物理基地址到 CR3
  4. 设置 IA32_EFER.LME = 0 关闭 IA-32e 模式
  5. 设置 CR0.PG = 1 开启传统的分页保护模式
  6. 开启分页的 MOV CR0 指令必须后跟一个分支指令 , 两个指令必须位于同一个映射页

5. 微代码更新工具

P6 以后的处理器支持加载一个 Intel 提供的数据块到处理器 , 达到勘误的目的 . 这个数据块就称作微代码更新 , 由 BIOS 在系统初始化时提供 .

微代码更新由一个 Intel 提供的二进制组成 , 后者包含一个描述性的首部和数据 ; 更新内没有可执行代码 , 包含特定列表的处理器签名 .

处理器的签名列表可以包含多个处理器型号 , 添加到加密数据后 .

在加载微代码更新前 , 软件需要检查校验和判断微代码更新的数据是否有效 .

硬件重置后 , 加载过的微代码更新会被无效 ; 多次加载更新不会有副作用 .

支持超线程技术的处理器 , 物理核心的每个逻辑处理器都必须加载微代码更新 , 每个逻辑处理器独立的加载更新 .

5.1. 可选的处理器微代码更新规范

系统软件可以构建自己的组件管理微代码更新 , 也可以依赖于 BIOS 提供的工具更新微代码 .

实模式 INT 15H 0D042H 功能是 BIOS 可以提供给 OS 的微代码更新工具 , 允许应用读取和修改 NVRAM 中的微代码更新数据 .

5.1.1. BIOS 的职责

如果 BIOS 通过了存在性测试 ( INT 15H , AX = 0D042H , BL = 0H ) , 必须实现所有定义在 INT 15H , AX = 0D042H 规范中的子功能 , 没有可选功能 .

BIOS 负责管理 NVRAM 更新块 , 包括垃圾回收 , 比如移除 NVRAM 中已经不存在的处理器的微代码更新 .

执行 INT 15H, 0D042H 功能时 , BIOS 必须假定调用者完全不知道平台的要求 , BIOS 需要管理所有的芯片组和平台需求信息 . 使用写更新子功能写入更新数据时 , BIOS 必须维护实现相关的数据 ( 比如更新 NVRAM 的校验和 ) .

5.1.2. 调用程序的职责

调用程序加载微代码更新到 BIOS NVRAM 时 :

  • 调用程序需要从实模式程序调用 INT 15H, 0D042H 功能 , 并且在运行在实模式的系统中运行 .
  • 调用程序应该调用存在性测试功能功能 , 校验签名和功能的返回代码 .
  • 调用程序向 BIOS 提供需要的 RAM 缓冲区和栈大小
  • 调用程序应该读取已经存在的任何更新数据 , 以加载合适的更新 ; BIOS 必须拒绝用更老的版本覆盖更新的更新 .
  • 不会有模棱两可的更新 , BIOS 必须防止同一个 CPU 的多个更新同时存在 ; 必须拒绝加载 CPU 已经不存在的更新 .
  • 调用程序应该实现一个验证功能 , 在更新写功能成功完成后执行 : 读取更新 , 校验 BIOS 返回了和写入一致的镜像 .

调用程序需要在调用读写功能时给 BIOS 提供 3 个 64KB 的 RAM , 用 CX , DX 和 SI 指向这些 RAM 块 ; 提供的栈至少为 32 KB .

每个功能返回时都会清除 CF , 保存返回状态到 AH , AL 用于返回额外的平台特定的错误信息 .

必须实现的 4 个功能为 :

  • 功能 00H : 存在性测试
  • 功能 01H : 写微代码更新数据
  • 功能 02H : 微代码更新控制 , 加载更新数据到处理器
  • 功能 03H : 读微代码更新数据