从零写OS(四十一):TLB Shootdown —— 多核下的页表一致性
ch40 里 AP 还没开中断,原因是页表修改没有保护。这章解决这个问题:实现 TLB Shootdown,让多核下的页表修改安全可见。 问题:每颗 CPU 有自己的 TLB TLB(Translation Lookaside Buffer)是每颗核心内置的页表缓存,把虚拟地址→物理地址的映射缓存起来,避免每次都走四级页表。 问题在于,当一颗核心修改了页表(比如 fork 的 CoW、exec 建立新地址空间),其他核心的 TLB 里可能还缓存着旧的映射: CPU0 修改页表:VA 0x1000 → 新物理页 0xABCD000 CPU1 的 TLB: VA 0x1000 → 旧物理页 0x1234000 ← 未失效! CPU1 访问 VA 0x1000 → 访问了错误的物理页 → 数据错误 解决方法:修改页表后,给所有其他核心发一个 IPI,让它们执行 invlpg 使对应 TLB 条目失效。这个过程叫 TLB Shootdown。 实现 全局协调变量 volatile uint64_t tlb_shootdown_addr = 0; volatile int tlb_ack_count = 0; 发起方(修改页表的核心) void tlb_shootdown(uint64_t vaddr) { int online = ap_online_count; if (online <= 0) { // 单核路径,直接本地 invlpg __asm__ volatile("invlpg (%0)" :: "r"(vaddr) : "memory"); return; } tlb_shootdown_addr = vaddr; __sync_synchronize(); // 写屏障:确保其他核能看到 addr tlb_ack_count = 0; __sync_synchronize(); lapic_send_ipi_others(TLB_SHOOTDOWN_VECTOR); // 广播 IPI while (tlb_ack_count < online) // 等所有 AP 应答 __asm__ volatile("pause"); __asm__ volatile("invlpg (%0)" :: "r"(vaddr) : "memory"); // 本核也刷新 } ap_online_count 只有在 AP 真正开中断后才增加,确保发 IPI 时 AP 能响应。 ...