- 1. local APIC ID寄存器
- 2. APIC ID在multi-threading处理器下
- 2.1. 检测处理器是否支持multi-threading
- 2.2. multi-threading下的APIC ID
- 2.3. xAPIC ID
- 2.3.1. SMT
- 2.3.2. initial xAPIC ID值的查询
- 2.4. x2APIC ID
- 2.4.1. 测试是否支持CPUID 0B leaf
- 2.4.2. x2APIC ID值的查询
- 2.4.3. 枚举x2APIC ID的level数
- 2.5. Intel Hyper-threading技术的处理器
- 2.6. 支持multi-core和Hyper-threading技术的处理器
- 2.7. 在multi-core和Hyper-threading技术下的x2APIC ID
- 3. multi-threading技术的使用
- 3.1. 收集处理器的Package/Core/SMT ID
- 3.2. 提取Package/Core/SMT ID值
- 3.2.1. SMT_MASK_WIDTH与CORE_MASK_WIDTH
- 3.2.2. SMT_SELECT_MASK与CORE_SELECT_MASK
- 3.2.3. 32位x2APIC ID的排列规则
- 3.3. 分解提取3-level结构的x2APIC ID
- 3.3.1. 枚举MASK_WIDTH值
- 3.3.2. 计算SMT_SELECT_MASK值
- 3.3.3. 得到SMT_ID值
- 3.3.4. 计算CORE_SELECT_MASK值
- 3.3.5. 得到Core_ID值
- 3.3.6. 计算PACKAGE_SELECT_MASK值
- 3.3.7. 得到Package_ID值
- 3.4. 分解提取8位的xAPIC ID
- 4. multi-threading处理器编程
- 4.1. 枚举所有处理器的APIC ID
- 4.2. BSP与AP处理器
- 4.3. 枚举所有processor
- 4.4. 提供start-up代码
无论是在Intel还是AMD平台上,local APIC ID都非常重要。在处理器power-up或reset后,处理器和bus硬件上赋予每个local APIC唯一的initial APIC ID值,这个唯一的APIC ID值基于system bus上的拓扑结构构建,可以使用下面的方法查询这个值。
① 在处理器支持CPUID 0B leaf的情况下,使用
② 不支持CPUID 0B leaf时,使用
然后,在处理器power-up或者reset期间,这个initial值被写入到local APIC ID寄存器里(在支持CPUID 0B leaf但不支持x2APIC模式时,只保存32位APIC ID值的低8位)。
APIC ID实际上也是每个logical processor在system bus(或者Pentium和P6处理器使用的APIC bus)上的唯一编号。这个编号可以被使用在处理器间进行通信,系统软件可以使用这个编号动态调度任务给某些处理器执行。
local APIC ID寄存器保存着APIC ID值,地址在偏移量20H位置上,如下所示。
在P6和Pentium处理器使用的APIC版本里,APIC ID值是4位。Pentium 4以后的处理器上的xAPIC版本里APIC ID值是8位,x2APIC版本上使用32位的APIC ID值(实际上,在支持CPUID 0B leaf的处理器上APIC ID都使用32位的x2APIC ID值!!!)。
在本节后续的内容主要围绕着8位的xAPIC ID和32位的x2APIC ID值进行探讨。
Intel的处理器实现了两种技术:
- 从后期的Pentium 4 处理器开始加入的Hyper-Threading(超线程)技术, 使用逻辑core技术,两个logical processor共用一个physical package(物理处理器)。
- 从Core duo处理器开始加入的multi-core(多处理器核心)技术, 使用的是物理core技术,一个physical package上有两个物理core单元。
multi-threading技术不单指Hyper-Threading(超线程),也包括了multi-core(多处理器核心)技术。
- 现在Intel的处理器上的multi-threading实现了Hyper-Threading与multi-core相结合。
- 而AMD的multi-threading技术以multi-core形式实现。
两种技术都有的话, 在有cluster的MP系统中, 会有多个cluster, 每个cluster会有多个physical package(物理处理器), 一个physical package会有多个物理processor core单元(2个, multi-core技术), 每个物理core单元内有多个logical processor(逻辑, Hyper-Threading技术, 2个), logical processor就是指SMT单元, 用来执行单元的线程, 拥有自己的资源(处理器的stat信息)和自己的local APIC等.
对于处理器是否支持multi-threading技术,软件可以使用
代码清单18-9(lib\cpuid.asm):
1 2 3 4 5 6 7 8 9 10 | ;----------------------------------------------------- ; support_multi_threading():查询是否支持多线程技术 ;----------------------------------------------------- support_multi_threading: mov eax,1 cpuid bt edx,28 ; HTT 位 setc al movzx eax,al ret |
上面这个support_multi_threading()函数用来检测处理器是否支持multi-threading技术,可以使用在Intel和AMD平台上。然而,这里无法得知是支持Hyper-threading还是multi-core技术。
在multi-threading技术下,initial APIC ID通常分成3-level或4-level的拓扑结构,因此一个完整的APIC ID将分为3或4个sub-field(子域),某些处理器的32位x2APIC ID还可以超过4-level,这些具体的sub-field信息需要通过CPUID的相关leaf进行查询。
当MP系统里包含cluster时,8位的initial APIC ID被分成4-level结构,最高层为Cluster ID,下面是一个使用cluster bridge连接各个cluster的示意图,每个cluster内有数个physical package。
在没有Cluster的MP系统里,initial xAPIC ID被分成3-level结构。
如上所示,8位的initital xAPIC ID结构的3个子域分别如下。
① Package ID:APIC ID最上层是Package ID,在一个MP(multi-processor)系统里,system bus会有多个physical package,每个physical package就是一个物理处理器。APIC ID里的package ID就是用来区分cluster内(!!!)(假如system bus上有cluster)或者system bus上的物理处理器的。
② Core ID:Package ID下一层是Core ID,用来区分physical package内的processor core,在一个单核的处理器上,Core ID的值为0。
③ SMT ID:Core ID下一层是SMT ID,每个SMT ID代表一个logical processor,SMT ID用来区分processor core内的logical processor(!!!)。
这些Package ID、Core ID及SMT ID子域的宽度依赖于处理器的实现,每个处理器在power-up时,bus硬件会赋一个initial APIC ID给local APIC,这个initial APIC ID值可以从CPUID指令里查询得到。软件可以使用CPUID的01 leaf和04 leaf来查询和枚举initial APIC ID的子域。然而值得注意的是,这个initial APIC ID值可能与通过local APIC ID寄存器查出来的ID值不一样。在某些处理器(!!!)上,local APIC ID寄存器是可写的,BIOS或OS可能会写给local APIC ID寄存器不一样的ID值(当然,改写的可能性极少)。
SMT(Simultaneous Multi-Threading)直译为同步的多线程,就是指Intel的Hyper-Threading技术,在processor core内实现两个共享物理core执行单元的线程。这些线程单元拥有自已的处理器资源(处理器的state信息!!!),包括有自己的local APIC。logical processor就是指SMT单元。
显然,system bus 硬件产生的8位的initial xAPIC ID会被保存到local APIC ID寄存器里,软件可以使用CPUID.01:EBX[31:24]查询得到initial xAPIC ID值。
代码清单18-10(lib\apic.asm):
1 2 3 4 5 6 7 8 9 | ;------------------------------------- ; get_apic_id():得到 initial apic id ;------------------------------------- get_apic_id: mov eax,1 cpuid mov eax,ebx shr eax,24 ret |
这个get_apic_id()函数用来获得当前运行代码的处理器的8位的initial xAPIC ID值,在CPUID指令的01 leaf返回的EBX寄存器[31:24]位里。
思考一下,这个initial xAPIC ID值,只会对当前运行的线程(也就是当前logical processor!!!)执行后所返回的。
在处理器支持CPUID 0B leaf或者支持x2APIC模式的情况下,软件可以查询到一个32位的x2APIC ID值,结构如下。
x2APIC ID值可能会超过4个子域,依赖于处理器的实现,有多少个子域及每个子域的宽度必须使用CPUID指令的0B leaf进行查询和枚举。然而在没有cluster的情况下(cluster为0),处理器还是使用3-level的结构,也就是32位的x2APIC ID依然使用了3-level结构。
值得注意的是,CPUID指令的0B leaf并不依赖于local APIC的x2APIC模式。
不支持x2APIC模式的处理器,并不代表不支持CPUID 0B leaf,因此,在不支持x2APIC模式的处理器上,如果支持CPUID的0B leaf,软件依然可以使用CPUID 0B leaf来查询32位的x2APIC ID值。
CPUID 0B leaf允许处理器查询超过8位的xAPIC ID值(在xAPIC模式下),使用CPUID 01 leaf查询得到的8位xAPIC ID值(由CPUID.01:EBX[31:24]得到)等于使用CPUID 0B leaf查询得到的32位x2APIC ID值的低8位(即CPUID.01:EBX[31:24]=CPUID.0B:EDX[7:0])。
只有在处理器支持x2APIC模式情况下,才可能使用完整的32位x2APIC ID值,操纵超过256个logical处理器。
CPUID指令的0B leaf用来查询和枚举处理器扩展的拓扑结构,在近期的处理器上才实现了0B leaf。
代码清单18-11(lib\apic.asm):
1 2 3 4 5 | ; 测试是否支持 leaf 11 mov eax,0 cpuid cmp eax,11 jb extrac_x2apic_id_done ; 不支持 0B leaf |
上面这段代码用来测试是否支持0B leaf,关于测试CPUID指令支持的最大leaf(叶号),请参考4.3节所述。
当处理器支持CPUID 0B leaf时,软件就可以使用CPUID 0B leaf来查询x2APIC ID值,代码如下所示。
代码清单18-12(lib\apic.asm):
1 2 3 4 5 6 7 8 | ;--------------------------------------- ; get_x2apic_id():得到 x2APIC ID ;--------------------------------------- get_x2apic_id: mov eax,11 cpuid mov eax,edx ; 返回 x2APIC ID ret |
上面的get_x2apic_id()函数用来查询x2APIC ID值,CPUID.0B:EDX[31:0]返回的是当前运行线程(当前运行的logical processor!!!)下的32位x2APIC ID值。
前面提及,x2APIC ID的子域(level数)可以使用CPUID 0B leaf来枚举。EAX寄存器输入main-leaf号(即0BH),ECX寄存器输入sub-leaf号枚举,最终的结果值表示在physical package内有多少level。
在第1次发起CPUID 0B leaf查询时,使用EAX=0BH(main-leaf),ECX=0(subleaf),每次递增ECX,直到返回的ECX[15:8]=0为止,它的算法描述如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #define SMT 1 #define CORE 2 #define INVALID 0 int get_x2apic_id_level() { sub_leaf=0; /* sub-leaf 号 */ do { eax=0Bh; /* EAX=main-leaf */ ecx=sub_leaf; /* ECX=sub-leaf */ cpuid(); /* 执行 CPUID 指令枚举 */ sub_leaf++; /* 递增 sub-leaf */ } while (ecx.level_type != INVALID); /* 返回的 ECX[15:8] 值不等于0时,重复迭代*/ return ecx.level; /* 返回的 ECX[7:0] 值就是 level 数 */ } |