Skip to content

导引:一块物理页的两本账

一块物理内存页,在内核里有两种完全不同的「被谁占着」要记:有几个页表项(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 + 看不变式验证两本账各自正确、合起来等价于旧的单账语义。

这章咱们要点亮什么

  1. 一块物理页有两个独立的「占着」维度:映射维度(几个 PTE 指向它)和所有权维度(谁拥有它、能不能释放)。这两个维度的「到 0」事件是不同的,混成一本账会互相误伤。
  2. 释放该由「所有权」决定,不该由「映射」决定:映射到 0 只说明「没人映射了」,可页可能还被缓存拥有(下次还能命中);只有所有权到 0 才真释放。旧的 mapcount_dec_and_test 把这两件事绑在一起,逼得缓存用幻影 +1 兜底。
  3. pte_count_dec_and_test 内部联动两本账:拆映射时,pte_count 减;pte_count 到 0 时,连带减一个所有权 refcount;refcount 到 0 才真释放。对 7 个拆映射的调用点来说,接口没变(还是 pte_count_dec_and_test 返 true=已释放),但语义内部化了。
  4. PhysRef<Tag> 是类型化的所有权句柄:页缓存用 CachePhysRef、匿名页用 AnonPhysRef、页表用 PageTablePhysRef——类型在编译期标记「这页归谁」,RAII 析构时减对应的 refcount。幻影 +1 被「缓存领一个 CachePhysRef」替代,语义清晰、不会忘配对。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04