正常
导引:一块物理页的两本账
一块物理内存页,在内核里有两种完全不同的「被谁占着」要记:有几个页表项(PTE)映射了它(映射维度),以及有谁「拥有」它(所有权维度)。咱们以前把这两本账混成一本——一个
mapcount,既数映射、又决定释放。这一章把它拆成两本:pte_count(只数映射)+refcount(决定释放),并用一个类型化的PhysRef<Tag>把「这页归谁」管起来。punchline 是个根因故事:混账的时候,页缓存为了「自己的页别在拆映射时被释放」,在
pte_count上偷偷+1兜底(幻影引用)。这个+1在单核、顺序好的时候没事,可一旦 GCC 自举跑真编译、跨进程共享缓存页、SMP 并发拆映射,这个兜底就盖不住所有顺序——偶发 double-free 风格的页腐蚀,ld/cc1莫名崩。拆成两本账后,「缓存拥有」走refcount(类型化、不会被映射拆减误伤),「映射计数」走pte_count(纯计数),兜底的+1就不需要了——bug 从类型层没了。验证:这是内部重写、无新用户可见能力,靠
test_pmm_pte_count+ 看不变式验证两本账各自正确、合起来等价于旧的单账语义。
这章咱们要点亮什么
- 一块物理页有两个独立的「占着」维度:映射维度(几个 PTE 指向它)和所有权维度(谁拥有它、能不能释放)。这两个维度的「到 0」事件是不同的,混成一本账会互相误伤。
- 释放该由「所有权」决定,不该由「映射」决定:映射到 0 只说明「没人映射了」,可页可能还被缓存拥有(下次还能命中);只有所有权到 0 才真释放。旧的
mapcount_dec_and_test把这两件事绑在一起,逼得缓存用幻影+1兜底。 pte_count_dec_and_test内部联动两本账:拆映射时,pte_count减;pte_count到 0 时,连带减一个所有权refcount;refcount到 0 才真释放。对 7 个拆映射的调用点来说,接口没变(还是pte_count_dec_and_test返 true=已释放),但语义内部化了。PhysRef<Tag>是类型化的所有权句柄:页缓存用CachePhysRef、匿名页用AnonPhysRef、页表用PageTablePhysRef——类型在编译期标记「这页归谁」,RAII 析构时减对应的refcount。幻影+1被「缓存领一个CachePhysRef」替代,语义清晰、不会忘配对。