到目前为止,内核一直跑在单核上。这一章迈出多核的第一步:让所有 CPU 核心都进入内核。

验证结果:

[acpi] MADT found, 2 CPU(s)
[smp] booting AP lapic=1
[cpu0] online
[cpu1] online
[smp] all APs online, total CPUs=2
/ #

x86 多核启动的规则

x86 多核启动有一套固定规则。系统上电后只有一颗 CPU 跑起来,叫做 BSP(Bootstrap Processor)。其余的核心叫 AP(Application Processor),处于等待状态,需要 BSP 主动唤醒。

唤醒的方式是通过 LAPIC(Local APIC) 发送 IPI(Inter-Processor Interrupt)。每颗核心内置一个 LAPIC,是核间通信的硬件基础。

第一步:找到所有 CPU

CPU 的信息藏在 ACPI MADT 表里。MADT(Multiple APIC Description Table)是固件写好放在内存里的一张表,描述了系统上有多少颗 CPU 以及每颗 CPU 的 LAPIC ID。

内核启动时扫描 MADT,把所有有效 CPU 的 LAPIC ID 记下来:

void acpi_parse_madt(void) {
    madt_t *madt = acpi_find_table("APIC");
    // 遍历所有条目,找 type=0 的 Processor Local APIC 条目
    // 记录 lapic_id → cpu_lapic_ids[],ncpus++
}

第二步:发送 INIT + STARTUP IPI

唤醒 AP 的序列是固定的(来自 Intel SDM):

  1. 发送 INIT IPI:复位 AP
  2. 等待 10ms
  3. 发送两次 STARTUP IPI:告诉 AP 从哪个物理地址开始执行

STARTUP IPI 的向量字段(8 位)是 AP 起始物理地址 » 12。例如,让 AP 从 0x8000 开始,向量就是 0x08。这个起始地址必须在低 1MB(0x000xx000),这是 CPU 的硬件限制。

第三步:蹦床代码

AP 收到 STARTUP IPI 后,从实模式开始执行。内核需要在低地址(0x8000)放一段汇编代码,把 AP 从实模式引导到 64 位长模式:

[实模式 0x8000]
  → 加载临时 GDT,进入保护模式
  → 开启 PAE + IA32e,加载 kernel_pml4,开启分页
  → 跳入 64 位代码
  → 调用 ap_main()

页表直接复用 BSP 的 kernel_pml4,所以 AP 进入 64 位后就和 BSP 共享内核地址空间。

第四步:ap_main

每颗 AP 进入 C 代码后,执行自己的初始化:

void ap_main(void) {
    gdt_init();     // 加载内核 GDT
    idt_init();     // 加载 IDT
    lapic_init();   // 初始化自己的 LAPIC
    tss_init(...);  // 设置自己的内核栈

    int id = cpu_id();   // 通过 LAPIC ID 查表得到逻辑编号
    kprintf("[cpu%d] online\n", id);

    __sync_fetch_and_add(&ap_online_count, 1);
    for (;;) __asm__ volatile("hlt");   // 暂时空转
}

注意 __sync_fetch_and_add:这是原子加操作,保证多颗 AP 同时上线时 ap_online_count 不会出现竞争。

per-CPU 数据结构

多核的一个核心问题是:哪些数据是全局共享的,哪些数据每颗核心要有自己的副本?

最基础的是 current_proc——当前核心在运行哪个进程。如果还是全局变量,两颗核心会互相踩:

// 危险:全局 current_proc,两核同时写
int current_proc = 0;

// 正确:每核独立
typedef struct { int cpu_id; int current_proc; } cpu_t;
cpu_t cpus[MAX_CPUS];

cpu_id() 通过读 LAPIC ID 确定当前是哪颗核,再查 cpu_lapic_ids[] 数组返回逻辑编号。

这一章只是开始

这一章 AP 上线后就挂在 hlt 里空转,还没参与调度。后续章节会逐步:

  • ch39:加锁保护共享数据结构
  • ch40:per-CPU 调度器,让 AP 真正跑进程
  • ch41:TLB shootdown,保证多核下页表一致性