x86多核启动代码实现

发布时间 2023-10-21 02:02:31作者: basic60

前言

对于多核CPU,开机上电后,最开始其实只有一个cpu会启动,称为bootstrap processor (BSP) ,而其他的核心则称为application processors (APs)。BSP的启动无需操心,而启动AP则需要我们自己代码实现,学习时发现少有相关的资料,也踩了一些坑,因此记录个简单流程备忘。

流程

原理可以见引用1或者intel开发手册vol3 8.4节,已经有了非常详细的解答。
流程如下:

  1. 首先BSP要进入保护模式或者长模式,启动AP需要访问0xFEE00300这个地址(如果开启了分页,还需要确保已经映射到了该地址),因为实模式下是无法访问到这个地址的。
  2. 发送相关指令,激活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来判断
IA32_APIC_BASE

获取当前的cpuid

可以通过cpuid指令来获取,代码如下:

get_cpu_id:
    mov rax, 0x1
    cpuid
    and ebx, 0xff000000
    shr ebx, 24
    mov eax, ebx
    ret

引用

  1. x86 汇编并没有多线程之类的并行指令,那操作系统的多线程是如何实现的?
  2. Intel® 64 and IA-32 Architectures Developer's Manual: Vol. 3A