ch39 加了锁,数据安全了。但调度器还有个问题:PIT(8253 定时器)是系统里唯一的一个,只有 CPU0 能收到 IRQ0。AP 没有定时中断,没法触发调度。
这章做两件事:
- 用 LAPIC 定时器替代 PIT,每颗核心独立触发调度
- 把
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 正常启动。