Week 1:Virtual Memory / Page Table(高密度版)
你要建立的模型
VM 不是“把内存变大”的抽象,而是三件事的统一接口:
- 隔离:地址空间隔离 + 权限位(U/S, R/W/X)。
- 稀疏映射:虚拟地址连续,物理地址可离散。
- 按需承诺:lazy allocation / COW / swap,把峰值需求转成时间换空间。
性能问题里,VM 的核心是:地址翻译路径本身就是一条多级缓存链。
地址翻译快照(x86_64 / RISC-V 思维都适用)
访存时的关键路径可抽象成:
VA -> VPN + offset- TLB lookup(并行于 cache tag lookup,具体取决于微架构)
- TLB miss -> page walk(多级页表访问)
- 拿到 PTE 后验证权限/状态位
PA = PPN + offset,进入 data cache hierarchy
重要点:
- TLB miss 不等于 page fault。
前者是“翻译缓存没命中”,后者是“PTE 不可用或权限不满足,需要内核介入”。 - page walk 本身会吃 cache;页表不在 cache 时,miss 代价级联放大。
代价层级(数量级直觉)
- L1 hit:~几 cycles
- L2/L3 hit:~十到几十 cycles
- DRAM:~百 cycles
- TLB miss + page walk:常见几十到上百 cycles(取决于层级命中)
- minor page fault(无需磁盘):通常微秒级
- major page fault(涉及磁盘):毫秒级起步
结论:把 TLB miss 当成“小 cache miss”会低估问题;把 page fault 当成“慢一点的 miss”是灾难级误判。
页表结构与工程含义
多级页表不是“教科书复杂化”,是对稀疏地址空间的空间优化:
- 大地址空间下,单级页表不可接受(常驻开销过大)。
- 多级 + 按需分配,把页表空间开销和实际映射规模绑定。
对性能敏感系统的含义:
- 热 working set 如果跨太多 page,会推高 TLB pressure。
- 大页(huge page)可降低 TLB miss,但会影响内存碎片和分配灵活性。
- NUMA 下 page placement 失误会把局部 miss 变成远端访问惩罚。
常见机制(你要会“推演代价”)
Copy-on-Write(COW)
- fork 后父子共享只读页,写时 fault 再复制。
- 好处:快 fork、低初始内存。
- 代价:写热点阶段 fault 风暴,且 TLB shootdown / cache 扰动显著。
Lazy allocation
- 只承诺虚拟范围,不立即分配物理页。
- 好处:启动快、峰值内存可控。
- 风险:首次触达延迟抖动,RT/低延迟路径要显式预热。
mmap vs read
mmap省拷贝与 syscall 次数,但 fault 行为与回收策略更复杂。- 对随机访问大文件通常更友好;对严格尾延迟场景需评估 fault 抖动。
观测与定位(不靠猜)
你至少应能回答三类问题:
- 是不是 TLB 压力:dTLB/iTLB miss 是否与吞吐下降相关?
- 是不是 fault 抖动:minor/major fault 峰值是否和 p99 对齐?
- 是不是布局问题:对象跨页、cache line 对齐、访问步长是否恶化局部性?
工具侧(Linux 语义)常见入口:perf stat, perf record, /proc/<pid>/smaps, page-fault counters。
设计准则(面向低延迟)
- 把热路径工作集压到更少页内,减少 TLB footprint。
- 大页只给稳定大块热数据,不要全局盲开。
- 启动阶段做显式 pre-touch,避免交易时段首次 fault。
- 数据结构优化优先考虑访问模式,不只是“省内存字节数”。
高发误区
- “CPU 利用率不高,所以不是内存问题。”
-> 错。内存/翻译停顿会让流水线空转。 - “没有 major fault,就不可能有延迟尖刺。”
-> 错。minor fault + shootdown 一样能打爆 p99。 - “TLB miss 一定比 cache miss小很多。”
-> 不稳定,取决于 page walk 命中链路。