x86多核启动代码实现
前言
对于多核CPU,开机上电后,最开始其实只有一个cpu核心会启动,称为bootstrap processor (BSP) ,而其他的核心则称为application processors (APs)。BSP的启动无需操心,而启动AP则需要我们自己代码实现,学习时发现少有相关的资料,也踩了一些坑,因此记录个简单流程备忘。
流程
原理可以见引用1或者intel开发手册vol3 8.4节,已经有了非常详细的解答。
流程如下:
- 首先BSP要进入保护模式或者长模式,启动AP需要访问0xFEE00300这个地址(如果开启了分页,还需要确保已经映射到了该地址),因为实模式下是无法访问到这个地址的。
- 发送相关指令,激活AP
2.1 发送 INIT IPI指令,其实就是往内存0xFEE00300这个地址写入000C4500H
2.2 sleep 10毫秒
2.3 发送IPI指令,即往内存0xFEE00300写入000C46**XXH,XX是AP的启动地址除以4096的值,如AP启动代码在内存0x8000的位置,XX的之则为08。从这里就可以看出,AP的启动地址是需要4K对齐的,否则无法正确计算出正确的AP启动地址。同时要注意AP启动代码在内存中的位置不能大于1MB,因为AP刚启动也是处于实模式,后续进入保护模式, 才能访问大于1MB的内存地址。
2.4 sleep 200毫秒
2.5 再次发送IPI指令
写成汇编就是如下十几行代码,完整代码可以见https://github.com/basic60/ARCUS
APIC_ICR_LOW equ 0xFEE00300
start_multi_core:
; 向所有其他 AP 发送 INIT
mov eax, 000C4500H
mov esi, APIC_ICR_LOW
mov [esi], eax
push rdi
mov rdi, 10
call sleep
; 向所有其他 AP 发送 SIPI
mov eax, 0x000C4608
mov [esi], eax
mov rdi, 200
call sleep
pop rdi
mov [esi], eax
ret
可以看见AP启动并不复杂,但这仅仅是万里长征第一步,后续完成CPU多核同步、任务调度等才能真正发挥出SMP系统的价值。
其他
sleep实现
简单实现可以通过一段很大的循环,或者通过时钟中断实现。
判断当前CPU是否是BSP
可以通过读取IA32_APIC_BASE
这个MSR寄存器,看它第9位是否1来判断
获取当前的cpuid
可以通过cpuid指令来获取,代码如下:
get_cpu_id:
mov rax, 0x1
cpuid
and ebx, 0xff000000
shr ebx, 24
mov eax, ebx
ret