ch39 加了锁,数据安全了。但调度器还有个问题:PIT(8253 定时器)是系统里唯一的一个,只有 CPU0 能收到 IRQ0。AP 没有定时中断,没法触发调度。

这章做两件事:

  1. LAPIC 定时器替代 PIT,每颗核心独立触发调度
  2. current_proc 改成 per-CPU 变量,每颗核各自记录自己在跑哪个进程

全局 current_proc 的灾难

int current_proc = 0;   // 全局变量

// CPU0 把它改成 3(选中进程3)
// CPU1 同时把它改成 5
// CPU0 接着继续,以为自己在跑进程3,实际内存里已经是 5
// → 崩溃

解决方法是每颗核心维护自己的 current_proc,互不干扰:

typedef struct {
    int cpu_id;
    int current_proc;
} cpu_t;

cpu_t cpus[MAX_CPUS];

static inline cpu_t *this_cpu(void) {
    // 读 LAPIC ID → 查表 → 返回本核的 cpu_t
    int lid = lapic_id();
    for (int i = 0; i < ncpus; i++)
        if (cpu_lapic_ids[i] == lid) return &cpus[i];
    return &cpus[0];
}

#define CUR_PROC  (this_cpu()->current_proc)

所有原来用 current_proc 的地方改成 CUR_PROC。CPU0 修改自己的,CPU1 修改自己的,互不干扰。

LAPIC 定时器

PIT 全局唯一,SMP 下每核需要独立的调度时钟。LAPIC 定时器是每颗核心内置的,彼此独立,正好适合这个需求。

使用前需要校准:

void lapic_timer_init(void) {
    // 1. 设为单次模式,填入初始计数 0x10000
    lapic_write(LAPIC_TIMER, 0x10000);

    // 2. 等 10ms(用 PIT 计时)
    uint64_t t0 = pit_get_ticks();
    while (pit_get_ticks() - t0 < 10) __asm__ volatile("pause");

    // 3. 计算 10ms 内 LAPIC 减少了多少 → ticks_per_ms
    uint32_t elapsed = 0x10000 - lapic_read(LAPIC_TIMER_CCR);
    uint32_t ticks_per_ms = elapsed / 10;

    // 4. 设为周期模式,每 10ms 触发一次,向量 0x20
    lapic_write(LAPIC_TIMER, LAPIC_TIMER_PERIODIC | 0x20);
    lapic_write(LAPIC_TIMER_ICR, ticks_per_ms * 10);
}

LAPIC 定时器中断不走 8259 PIC,EOI 要发给 LAPIC 而不是 PIC:

if (irq == 0) {
    lapic_eoi();        // LAPIC 定时器
    sched_tick(regs);
} else {
    pic_send_eoi(irq);  // 其他外设
}

AP 的初始化序列

AP 完成 per-CPU 初始化,启动自己的 LAPIC 定时器,但本章暂不开中断(原因见下):

void ap_main(void) {
    gdt_init(); idt_init(); lapic_init();
    tss_init(...);
    percpu_init(cpu_id());
    lapic_timer_init();   // 硬件就绪,但不开中断
    __sync_fetch_and_add(&ap_online_count, 1);
    for (;;) __asm__ volatile("hlt");
}

为什么不开中断? AP 开中断后会触发 sched_tick,尝试 vmm_switch 切换页表。但 BSP 在 exec 过程中可能正在修改页表,AP 拿到一个未完整建立的页表就会 Page Fault。完整的解决方案在 ch41。

cpu_pin:防止同一进程被两核同时调度

进程结构体里加一个字段:

int cpu_pin;   // -1 = 没有核在跑;>=0 = 正在此核运行

sched_tick 选下一个进程时,跳过 cpu_pin >= 0 的进程,防止两核同时跑同一个进程:

if (procs[n].state == PROC_READY && procs[n].cpu_pin < 0)
    { next = n; break; }

验收

[acpi] MADT found, 2 CPU(s)
[smp] booting AP lapic=1
[cpu1] online
[smp] all APs online, total CPUs=2
/bin/sh: can't access tty; job control turned off
/ #

两颗 CPU 都上线,各自有 LAPIC 定时器驱动调度,shell 正常启动。