编者按 : 鉴于笔者水平有限,行文若有不当之处,还望诸位海涵并不吝指正。
目录
是为序
笔者在前面讲到了 Intel Xeon 系列多核CPU的 Core 和 Uncore 架构。
那,这便引出了一个问题。什么问题呢?
这个问题便是:多核CPU里面的 Core 都是一模一样的吗?
可能大部分同学的回答会是:应该是一模一样,的,吧...
但,实则非也。
笔者认为:多核CPU里面的物理Core应该是一样的,但它们的身份或者担任的角色却不一样。
那,要不咱细说?
好,笔者今天就与大家一起唠唠多核CPU里面的Core们,看看这些小家伙们在功能上究竟有啥不一样的。
AP和BP
对于多核CPU来说,CPU里的每个Core都有唯一一个 Local APIC ID 来标识该物理Core。APIC(Advanced Programmable Interrupt Controller) ,即高级可编程中断控制器。当系统或外部设备产生中断时,便可以将中断信息传送给特定 Local APIC ID 的 Core 来处理。
UEFI 的实现 EDK II 中对一个逻辑CPU(也就是Core) 进行了如下抽象。对于 IA32 和 X64 处理器架构来说,ProcessorId 就是上面的 Local APIC ID,而且只有低8位有效,符合x84架构CPU的兼容性。
/// Structure that describes information about a logical CPU.
typedef struct {
/// The unique processor ID determined by system hardware. For IA32 and X64,
/// the processor ID is the same as the Local APIC ID. Only the lower 8 bits
/// are used, and higher bits are reserved. U
INT64 ProcessorId;
//...
} EFI_PROCESSOR_INFORMATION;
关于一些 Local APIC ID 的细节,诸位可以参考这篇文章:计算机中断体系一:历史和原理 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/26464793
关于 APIC ID,这里有几点需要注意下:
- 不能假设 APIC ID 0 存在。
- 不能假设 APIC ID 是连续编号的。
接下来,笔者以下图作为例子进行说明,CPU 0 (Socket 0) 是一个标准的多核CPU。其中,APIC ID 从0开始,依次连续编号,而 CPU 1 里则没有 APIC ID 0,而是从1开始编号,并且APIC ID 也并不连续。
那,除了中断处理相关,CPU Core 的 APIC ID 号还有其它什么用处吗?
有!
先说用处:BIOS/UEFI 可通过 APIC ID,从AP中选择一个Core(BSP),来执行BIOS/UEFI。
多核CPU里面的Core,可分为两类:
- AP: Application Processor,应用处理器。
- BP: BootStrap Processor,也叫做BSP,启动处理器,用来启动BIOS/UEFI。
还是上面的 EFI_PROCESSOR_INFORMATION, 其中的 StatusFlag 成员标识了该 Core 是 AP 还是 BP,以及该 Core 是开启状态,还是关闭状态。
/// Structure that describes information about a logical CPU.
typedef struct {
UINT64 ProcessorId;
/// Flags indicating if the processor is BSP or AP, if the processor is enabled
/// or disabled, and if the processor is healthy.
/// BSP ENABLED HEALTH Description
/// === ======= ====== ===================================================
/// 0 0 0 Unhealthy Disabled AP.
/// 0 0 1 Healthy Disabled AP.
/// 0 1 0 Unhealthy Enabled AP.
/// 0 1 1 Healthy Enabled AP.
/// 1 0 0 Invalid. The BSP can never be in the disabled state.
/// 1 0 1 Invalid. The BSP can never be in the disabled state.
/// 1 1 0 Unhealthy Enabled BSP.
/// 1 1 1 Healthy Enabled BSP.
UINT32 StatusFlag;
//...
} EFI_PROCESSOR_INFORMATION;
下面给出一个基于 Intel Core Gen4 CPU 的系统概览图,方便读者理解AP和BSP的区别。
再者,BP在多CPU的情况下,又可分为两种:
- NBSP : Node BSP,每个CPU里面的BP。
- SBSP: System BSP,系统BSP是BIOS/UEFI从每个NBSP里面挑选出来的BP。
最终,BIOS/UEFI的大部分代码就是由这个SBSP来执行的。
根据前面 APIC ID 的两个特点和SBSP的挑选原则,可知SBSP的 APIC ID 并非一定为0 ,但大概率是0。
有同学要问了,上面的“大部分代码”是怎么回事?这里,笔者其实也了解不深,之所以这么写,主要是考虑到行文的准确性。
老狼提到:多CPU的时候,它们(BSP)都会执行BIOS,再由BIOS来选择最终谁胜出。
对此,笔者是这样理解的,在BIOS/UEFI选择SBSP的时候,会让每个BSP执行一段BIOS/UEFI代码,然后根据执行结果来选出SBSP。所以才说,SBSP执行的是大部分的BIOS/UEFI代码。
那BIOS/UEFI挑选SBSP依据的原则又是什么呢?肯定有很多种,但笔者在这里只说一般情况。一般来说:
BIOS/UEFI选择 Socket 0 上 APIC ID 最小的那个Core,作为SBSP。
这里的Socket 0 指的是主板 (Mother Board, MB) 上的第一个CPU插槽(针对多CPU)。另外,当BIOS/UEFI检测到当前SBSP不能正常工作时,便会重新选择(SwitchBSP)一个BP,将其作为新的SBSP来运行BIOS/UEFI。
好,简单总结一下上面的内容:
- 多核CPU里面的 Core 可分为 AP 和 BP 两类。
- 在多核多CPU系统中,由BIOS/UEFI从多个BP中选择一个BP,作为SBSP,来执行后续的大部分BIOS/UEFI代码。
- CPU里面,Core 的 APIC ID 并不保证一定从0开始,也不保证连续编号。
- BIOS/UEFI可以切换其运行的SBSP。
下面笔者以一张图对上述内容进行总结。
很多人误以为 BIOS/UEFI一定是在CPU 0 的 APIC ID 0 上执行,笔者只能说在大部分情况下,确实如此,但并不总是这样,比如当某个CPU的 Core 0 被BIOS/UEFI检测到损坏或者不从0开始编号时,BIOS/UEFI 便不会在 APIC ID 0上执行。
到这里,其实笔者已经可以结束本文了。
但部分好奇心比较重,或者 "求甚解"的读者可能会提出一个让笔者措手不及的问题:既然你说UEFI可以废立SBSP,那具体是咋实现的?
哼哼,还好当初笔者也问了自己这样一个问题。
所以,这个问题可难不倒俺。
那,接下来笔者将与诸位,细细道来~
EFI_MP_SERVICES_PROTOCOL
针对多核处理器(Multiple Processor),UEFI提供了一个Protocol——EFI_MP_SERVICES_PROTOCOL。
This Protocol provides many services for Multiple Processors. These services can be used for parallel execution in reduing UEFI boot time.
该Protocol提供了一系列函数。UEFI可以通过这些函数实现SBSP的选择、启动关闭AP、切换BSP、并行计算以减少UEFI启动时间等等。
非BIOS工程师的读者,可能对这里的Protocol有点懵。注意,这里的Protocol和计算机网络的Protocol不一样。这里笔者解释一下自己对UEFI Protocol的理解。UEFI中的Protocol其实类似于面向对象编程中的接口,都向外部使用者提供了一系列可供调用的函数接口,即API(Application Programmable Interface)。面向对象采用类来实现接口,而UEFI采用Protocol来实现接口。UEFI Protocol 的实现基于C语言,通过struct的函数指针实现。
具体来说,我们来看 EDK II 下面的 MdePkg\Include\Protocol\MpService.h。这个 MpService.h 中声明了一个结构体:_EFI_MP_SERVICES_PROTOCOL,具体实现如下:
// EFI_MP_SERVICES_PROTOCOL declaration
struct _EFI_MP_SERVICES_PROTOCOL {
EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS GetNumberOfProcessors; // 获取系统中包含Core的数量
EFI_MP_SERVICES_GET_PROCESSOR_INFO GetProcessorInfo; // 获取特定Core的信息
EFI_MP_SERVICES_STARTUP_ALL_APS StartupAllAPs; // 启动所有的AP
EFI_MP_SERVICES_STARTUP_THIS_AP StartupThisAP; // 启动特定AP
EFI_MP_SERVICES_SWITCH_BSP SwitchBSP; // 切换BSP
EFI_MP_SERVICES_ENABLEDISABLEAP EnableDisableAP; // 开启关闭AP
EFI_MP_SERVICES_WHOAMI WhoAmI; // 查询当前UEFI运行Core的信息 };
相信读者已经注意到了,我们可以通过调用该Protocol中的成员——SwitchBSP, 来实现BSP的切换。SwitchBSP 是一个 EFI_MP_SERVICES_SWITCH_BSP 类型的函数指针。那,让我们来看看这个函数指针,其声明如下,共有三个参数。
// This service switches the requested AP to be the BSP from that point onward. // This service changes the BSP for all purposes. This call can only be performed // by the current BSP.
typedef EFI_STATUS (EFIAPI *EFI_MP_SERVICES_SWITCH_BSP)(
IN EFI_MP_SERVICES_PROTOCOL *This, // 相当于类的 This 指针
IN UINTN ProcessorNumber, // 即将被指定成为BSP的AP
IN BOOLEAN EnableOldBSP // TRUE: 旧的BSP重新变成AP,并运行;FALSE: 关闭该Core
);
UEFI 通过调用 SwitchBSP 函数,就可以切换 SBSP 啦。
各位同学,你们现在明白了吗?
结语
本文简单介绍了多核CPU的AP和BP,UEFI运行在哪个BP上,以及UEFI如何切换BP。其实,关于这方面的相关内容还有很多,本文只讲述了其中很小的一部分,其它的内容包括但不限于:
- Core相关的知识,比如 Package、Die、Thread等。
- UEFI基本是单线程执行的,也就是说在UEFI启动过程中,基本上是BP太忙,AP太闲。故而如何更好地实现AP的并行化执行,值得研究。
- Local APIC ID 的相关内容。
好,本文的分享就到此次结束啦,各位同学,觉得本文还不错的话,记得点个赞哦~
【往期好文】