从零写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 能响应。 ...

June 4, 2026 · 2 min · 大飞

从零写OS(七):虚拟内存,给每个程序一个假的地址空间

上一章内核能分配物理页了,但用的全是物理地址。 物理地址有个问题:全局唯一,谁都能访问。进程 A 如果知道进程 B 的物理地址,直接就能读写它的数据。这不行。 解决方案是虚拟内存:每个程序看到的地址都是"假的",CPU 访问时由硬件自动翻译成真实的物理地址。程序互相隔离,谁也看不见谁。 地址翻译的硬件机制:四级页表 x86-64 的地址翻译靠 MMU(Memory Management Unit,内存管理单元,CPU 内部硬件,负责把虚拟地址翻译成物理地址)完成,翻译规则写在页表里,页表的根地址放在 CR3 寄存器里。 一个 64 位虚拟地址被这样拆开: 63 48 47 39 38 30 29 21 20 12 11 0 [ 符号扩展 | PML4_IDX | PDPT_IDX | PD_IDX | PT_IDX | 页内偏移 ] 9 bit 9 bit 9 bit 9 bit 12 bit 翻译过程是四级查表: CR3 → PML4[PML4_IDX] → PDPT[PDPT_IDX] → PD[PD_IDX] → PT[PT_IDX] → 物理页帧 每级页表是一个 512 项的数组,每项 8 字节,刚好占满一个 4KB 页。每项的低 12 位是 flags,高位是下一级页表(或最终物理页)的地址。 ...

May 6, 2026 · 2 min · 大飞
京ICP备14031575号-3