一般而言,UEFI 的主要作用是检测和初始化设备,加载操作系统的引导程序,然后将控制权交给操作系统,整个过程不需要大量的运算,在单个 CPU 核上运行单线程程序已经可以满足需求,因此,EDK2 没有提供多线程机制。
如果需要在 UEFI 应用中支持多任务,则可以使用 EFI_MP_SERVICES_PROTOCOL启动从 CPU,也可以自己实现一个多线程库,在每个CPU上运行并发任务。
多处理器服务
在多处理器系统中,系统初始化时,总是由一个处理器执行这些初始化指令,这个 CPU 称为 BSP ( Boot-Strap Processor )。系统中 BSP 之外的 CPU 称为 AP ( Application Processor )。
EFI_MP_SERVICES_PROTOCOL 功能及用法
PI (Platform Initalization) 标准中定义了 EFI_MP_SERVICES_PROTOCOL (简称 MP 服务),这个 Protocol 用于在多处理器系统中管理处理器,包括查询处理器信息、启动或停止AP、设定 BSP,

除了 WhoAmI 服务外,EFI_MP_SERVICES_PROTOCOL 中的其他服务都只能在 BSP 上调用,在 AP上调用这些服务时会返回EFI_DEVICE_ERROR错误。
1.查询服务
EFI_MP_SERVICES_PROTOCOL提供的查询服务有三个:
GetNumberOfProcessors // 用于查询系统中的逻辑处理器个数,以及启用的逻辑处理器
GetProcessorInfo // 用于获取指定处理器的相关信息
WhoAmI // 用于获取当前处理器的编号
GetNumberOfProcessors 服务的函数原型:

其功能是查询指定逻辑处理器有关多处理器的信息。此服务仅返回多处理器相关信息(如处理器编号、处理器状态等),不提供平台相关的处理器信息(如Cache 大小、处理器频率等)。
查询时通过处理器编号(参数ProcessorNumber)指定待查询的处理器。处理器编号从0开始。系统启动后,UEFI 把系统中的处理器从 0 开始编号,ProcessorNumber 就是这个编号 EFI_MP_SERVICES_PROTOCOL 中其他服务中的 ProcessorNumber 意义与之相同。如果指定的处理器编号超出系统中存在的处理器个数,则返回EFI_NOT_FOUND。

GetProcessorInfo服务返回的处理器信息存放在EFI_PROCESSOR_INFORMATION结构体内:

其中,ProcessorId是由系统硬件决定的地址。对 IA32 和 x64 处理器来说,它与本地 APIC ID 地址相同,低 8 位有效。对安腾处理器来说,低 16 位地址有效。要注意 ProcessorId 与 ProcessorNumber (处理器编号)不同。
StatusFlag 是处理器状态:
第 0 位是 BSP 位(PROCESSOR_AS_BSP_BIT),为 0 时说明该处理器是 AP;为 1 时说明该处理器是 BSP;
第 1 位是 EnabIed 位(PROCESSOR_ENABLED_BIT),0 表示该处理器被禁用;1表示该处理器被启用;
第 2 位是 Health 位(PROCESSOR_HEALTH_STATUS_BIT),0表示该处理器出现故障;1表示该处理器正常。
3~31位必须是 0。
处理器信息还包含一个域 Location,它是 EFI_CPU_PHYSICAL_LOCATION 类型的变量,表示处理器的物理地址,用于给每一个 CPU 定位。在一个计算机系统中,可能有多颗处理器(一颗处理器称为一个 Package),每颗处理器可能有多个物理核(物理核称为 Core),每个物理核又可能有多个逻辑核(逻辑核称为Thread)。所有的编号都从0开始。
EFI_CPU_PHYSICAL_LOCATION 结构体:

对于一个逻辑处理器,有三种地址:
1)ProcessNumber,作为 MPService 服务的参数,假设系统中有 n 个处理器,ProcessNumber在 0~n-1 之间,称为处理器编号;
2)Location是由 Package、Core、Thread 组成的三元组,可给一个逻辑处理器定位;
3)Processorld,由系统硬件决定的地址。
WhoAmI 用于获得当前处理器的编号 (ProcessorNumber)。

2.管理处理器
EFI_MP_SERVICES_PROTOCOL 中用于管理处理器的服务有以下 4 个:
StartupThisAP服务用在指定的AP上执行传入的函数;StartupAIlAPS服务用于在所有AP上执行传入的函数;‘SwitchBSP服务用于选定某个AP作为BSP;EnableDisableAP服务用于启用或禁用某个AP。
(1)StartupThisAP 服务
StartupThisAP 服务只能在 BSP上执行,用于在指定的 AP(此 AP 必须没有被禁用)上执行调用者提供的函数。调用者可以指定Timeout时间。
如果逾期时 AP 上的任务还未完成,那么 AP 上的任务将被终止;
如果 AP 未处于 IDLE 状态,那么该服务会返回 EFI_NOT_READY;
如果 AP 未处于 Enable 状态,那么该服务会返回EFI_INVALID_PARAMETER;

此服务有阻塞和异步两种模式。参数 WaitEvent 为NULL时,该服务使用阻塞模式;否则,使用异步模式。
阻塞模式下,该服务向 AP 发出指令后,等待 AP 把调用者提供的函数执行完毕,然后该服务成功返回。若 AP 完成任务前已逾期,则逾期时服务返回 EFI_TIMEOUT。
异步模式下,该服务向 AP 发出指令后立即返回。AP 执行完任务后触发该服务提供的事件 WaitEvent,并将Finished标志设为TRUE;若 AP 完成任务前已逾期,则逾期时终止 AP上的任务,触发事件 WaitEvent,并将Finished标志设为FALSE。
系统事件 EFI_EVENT_GROUP_READY_TO_BOOT 被触发后,该服务仅提供阻塞模式。
(2)StartupAIlAPS
StartupAllAPs 用于在所有的 AP上执行指定的函数。如果有任何 AP 未处于 IDLE 状态,则该函数返回EFI_NOT_READY。如果参数SingleThread为TRUE,则 AP 会依次执行Procedure函数,前一 AP 从 Procedure 返回后下一 AP 才会执行Procedure。如果参数SingleThread为FALSE,则所有的 AP 同时执行 Procedure,开发者负责 Procedure 的线程安全。
StartupAIlAPS 服务有阻塞和异步两种模式。参数 WaitEvent为NULL时,该服务使用阻塞模式;否则,使用异步模式。
阻塞模式下,所有 AP 从 Procedure 返回或超时后,StartupAllAPs 返回。FailedCpuList 列表返回未成功执行 Procedure 的 CPU 的编号,系统为 FailedCpuList 分配内存,调用者负责释放该内存。
异步模式下,所有 AP 从 Procedure 返回或超时后,WaitEvent 事件触发,系统同样会设置 FailedCpuList 列表。

(3)SwitchBSP服务
SwitchBSP 用于将指定的 AP 切换为 BSP。成功切换后,该服务返回EFI_SUCCESS,新的 BSP 将无缝接管原 BSP 的工作。当系统事件EFI_EVENT_GROUP_READY_TO_BOOT触发后,该服务不再有效。

(4)EnableDisableAP 服务
EnableDisableAP 服务用于启动或禁用指定的 AP。

启动 AP 的过程
1.通过发送 IPIs 消息启动 AP
每个处理器 Core 上都有一个 APIC (Advanced Programmable Interrupt Controller) 芯片,称为本地 APIC。它接收来自本地或外部的中断信号,发送给 Core 上的处理器处理这个中断。
这些中断信号包括:来自处理器 Core 中断引脚的信号、来自 I/O APIC 的中断、来自性能监视寄存器的中断、本地 APIC 时钟中断、本地 APIC 错误中断、温度传感器中断、来自其他处理器的中断 (Inter-Processor Interrupts,简称 IPIS)。
AP 的启动是通过在 BSP 上向 AP 发送特定的 IPIs 完成的,而发送 IPIs 是通过写本地 APIC 的 ICR(Interrupt Command Register)完成的。
多处理器系统启动时,首先根据 MP 初始化协议选择一个处理器为 BSP,其他处理器作为 AP。BSP 执行初始化代码,初始化系统的 APIC 环境,建立系统的全局数据结构,初始化 AP(向 AP 发送 INIT-SIPI-SIPI IPIs)。等 BSP 和 AP 初始化完成后,BSP 会执行 OS Loader 加载操作系统。
AP 重置(收到 INIT IPI 中断信号)或加电后,首先简单地进行自我配置,然后等待来自 BSP 的启动信号(来自 BSP 的 IPI),这个启动信号称为 SIPI。AP 收到 SIPI 后,会从实模式 0x000XX000 地址处开始执行。XX是 SIPI 消息中的 8bit 的地址向量。
(1)寄存器 ICR 及 IPI 类型
发送 IPI 是通过写寄存器 ICR 完成的,ICR是 64 位寄存器,可分为两个 32 位寄存器。其中低 32 位的寄存器偏移为0x300,高32位的寄存器偏移为0x310。

可用 IPI 类型(DeliveryMode)共有7种:
0:向目的处理器发送 Vector 指定的中断。
1:同 000,但仅向目标处理器中优先级最低的处理器发送。
2:向目标处理器发送 SMI 中断,Vector 必须是 0。
4:向目标处理器发送 NMI中断,Vector被忽略。
5:向目标处理器发送 INIT 中断(称为 INITIPI),目标处理器收到 INITIPI 后进行初始化,然后等待 SIPI。
6:向目标处理器发送 SIPI(startup) 中断,目标处理器收到 SIPI 后执行 Vector 指定的代码。
7:向目标处理器发送信号,目标处理器收到信号后向外部 8259A 控制器发出请求从而获得 Vector。

(2)向目标处理器发送 IPI
发送 IPI 是通过写寄存器 ICR 实现的:
a.写 ICR 高 32 位的寄存器XAPIC_ICR_HIGH_OFFSET,该寄存器包含了目的处理器的 APCI 地址;
b.写 ICR 低 32 位的寄存器XAPIC_ICR_LOW_OFFSET,写入该寄存器后 IPI 被发送,目的 CPU 收到消息后,发送方的XAPIC_ICR_LOW_OFFSET 的 DeliveryStatus 会设置为 0;
c.检查XAPIC_ICR_LOW_OFFSET的 DeliveryStatus,直到该值变为 0。
Sendlpi函数实现了这个流程:


(3)启动 AP 的 IPI 序列
启动 AP 是通过向 AP 发送 INIT-SIPI-SIPI 列完成的,SendInitlpi 函数用于发送 INIT IPI 消息。在 SendInitlpi 函数中,首先构造 INIT IPI 消息,然后调用 SendIpi 函数发送该消息。

SendInitSipiSipi 函数用于发送 INIT-SIPI-SIPI 序列,发送过程分为三步:发送 INIT IPI 消息,发送 SIPI 消息,再次发送 SIPI 消息。在 SIPI 消息中包含了 AP 启动向量 StartupRoutine。目标处理器接收 IPI 序列后 SendInitSipiSipi 函数返回。目标 AP 接收到 INIT-SIPI-SIPI IPI 序列后,从实模式物理地址 StartupRoutine 处开始执行代码。StartupRoutine 必须在 1MB 地址之内,并且必须按 4KB 对齐。

2.AP 启动向量
使用函数 SendInitSipiSipi(TargetCpu, mStartup Vector)启动一个 AP。AP 收到启动消息后会执行 mStartupVector。
启动向量 mStartupVector是一段代码的基址,在程序中它定义为 EFI_PHYSICAL_ADDRESS 类型的变量。地址 mStartupVector 必须在1MB地址之内,并且必须按4KB对齐。
(1)BSP 首先要为启动向量mStartupVector分配内存,AllocateStartupVector 函数在物理地址为0x02000~0x7F000的内存中找到可供使用的内存并分配给mStartupVector 使用。
(2)BSP 使用PrepareAPStartupVector 函数用于初始化启动向量mStartupVector。该函数首先取得启动代码的地址、大小等信息,这些信息包含在 MP_ASSEMBLY_ADDRESS_MAP类型的变量 AddressMap中。然后根据启动代码的大小调用AllocateStartupVector(…)为启动向量mStartupVector分配内存。接着将启动代码复制到mStartupVector指向的内存,并设置启动向量中的相关跳转地址,为 AP分配 GDT(全局描述符表) 和 IDT(中断描述符表) 并存入中断向量。
(3)AsmGetAddressMap 取得启动代码地址和大小。
多线程
UEFI Spec 没有提供多线程机制,如果在开发 UEFI 应用的过程中需要多线程,那么需要用户自己实现多线程库。
编写多线程库,线程的两个核心内容:
(1)线程切换时机
切换分两种,一种是主动切换,另一种是被动切换。当某个线程需要等待某个事件的发生,如网络应用等待远程服务器响应,可以主动切换到其他线程。当线程时间片用完时,需要切换到其他线程,这是被动切换。
(2)线程切换机制
要理解线程切换机制,首先要清楚每个线程执行过程中都拥有哪些资源。每个线程拥有独立的栈,还拥有执行上下文即寄存器环境。将旧的线程切换到新的线程时,需要保存旧线程的寄存器上下文,加载新线程的寄存器上下文,在切换寄存器上下文的同时发生了栈的切换。寄存器切换使用 SetJump/LongJump 函数。
1.生成线程:分配线程所需的资源,包括线程结构体及栈,以及初始化栈及线程寄存器上下文。
2.调度线程:线程的被动调度在定时器回调函数中完成。因此,在创建线程之前要先初始化Timer。
3.等待线程结束:在主线程退出之前一定要确保其他线程已经结束,并关闭定时器事件。因为主线程结束意味着程序结束,若不关闭定时器时间,会造成系统崩溃。
《UEFI原理与编程》。。。。有些东西,等用到的时候再学吧,这里大概了解一下。。。



















