影子分頁表(shadow page table)
在 CPU 還無法用硬體把客體實體位址轉譯成主機實體位址(巢狀分頁)之前,超管理器面對一個難題:客體管著自己那張「客體虛擬→客體實體」的分頁表,但真正的記憶體單元只能做一次轉譯,而且最終必須落在一個真實的主機實體位址上。影子分頁表就是那個聰明的軟體答案。超管理器偷偷建立並維護它「自己」的一張分頁表——也就是「影子」——這張表把客體虛擬位址直接映射到主機實體位址,把兩層壓成真硬體實際使用的那一張表。
讓它變難的那個關卡在這裡。客體不斷編輯自己的分頁表,渾然不知影子的存在,所以影子必須被保持同步。訣竅是把客體那張真正的分頁表對硬體標成唯讀。當客體試圖修改它時,這次寫入會陷入(一次 VM-exit);超管理器醒來、看出客體改了哪個映射、把客體想要的客體實體位址與它自己那張「客體實體→主機實體」的圖組合起來,重算出對應的主機實體項目、更新影子、再繼續執行。硬體接著便用這張最新的影子來轉譯,很快——直到下一次客體編輯分頁表又陷入為止。
它為何重要、又為何退場:影子分頁有效,且在沒有 EPT/NPT 的硬體上是「唯一」能虛擬化記憶體的方法,但它很折磨人。客體每次上下文切換都可能需要一張全新的或重建的影子,而分頁表密集的工作負載(衍生行程、開機)會產生一陣陣「陷入—模擬—重新同步」的退出風暴,主宰了整體成本。正是這份折磨催生了 Intel EPT 與 AMD NPT——那種讓客體自由編輯自己的表、毫無陷阱的硬體巢狀分頁。如今影子分頁主要作為較舊或受限硬體上的後備手段而留存,也作為「為何巢狀分頁是這麼大的解脫」最清楚的理解途徑。
客體把客體虛擬位址 0x4000 映射到客體實體位址 0x9000。超管理器知道客體實體位址 0x9000 真正住在主機實體位址 0x3A2000,所以它的影子表把 0x4000 直接映射到 0x3A2000。當客體稍後重新映射 0x4000 時,這次對它分頁表的寫入會陷入,超管理器便修補影子使之相符。
影子把「客體虛擬→主機實體」壓成一張表;客體的編輯會陷入,好讓它保持同步。
影子分頁是硬體巢狀分頁(EPT/NPT)的軟體前身,而非它的互補品——你通常只用其中一種。它「每改一次分頁表就陷入」的成本,正是巢狀分頁所消除的。