需求分頁(demand paging)
想像一位廚師,原則上他大可以在開始烹飪前,把儲藏室裡的每一樣食材都鋪到流理台上。那既慢又浪費——大多數菜餚只用到幾樣食材。聰明的廚師會等到食譜第一次要用某樣食材的那一刻,才從儲藏室去拿。需求分頁對執行中的程式採取同樣的想法:在程式真正觸碰某一頁之前,不要把那一頁載入 RAM。依需求載入,絕不預先載入。
具體來說,程式的位址空間被切成固定大小的頁。採用需求分頁時,程式啟動之初,它幾乎沒有任何一頁在 RAM 裡。每個分頁表項目都帶有一個有效/無效位元:有效代表該頁常駐於一個真實的頁框,無效代表它此刻不在記憶體裡。當程式參考一頁,而它的位元是無效的,硬體就陷入作業系統——這就是一次分頁錯誤(缺頁)。作業系統在後備儲存上找到該頁,把它載入一個空閒頁框(或趕走一頁以騰出空間),把位元翻為有效,並重新啟動造成錯誤的那道指令。從程式的角度看沒發生什麼異常;它只是這次花了比平常久的時間。隨著程式執行,它真正用到的頁陸續滴入 RAM,而它從未碰過的頁則根本不會被載入。
為什麼重要:需求分頁正是讓虛擬記憶體既實用又便宜的關鍵。它避免把 RAM 和磁碟讀取時間浪費在程式也許永遠用不到的程式碼與資料上(想想那些很少執行的錯誤處理常式)。它讓啟動變快,因為程式只要載入最初幾頁就能開始執行。它也提高了能共用 RAM 的程式數量。它在實務上行得通,全靠區域性(locality of reference):程式傾向於在一段時間內只使用一小群、緩慢變化的頁,於是一次錯誤的成本可以攤平到許多次便宜的記憶體內存取上。沒有區域性,需求分頁就會不斷發生錯誤而慢如爬行。
一個有 200 頁的文件編輯器啟動時,只載入存放其啟動程式碼的那十幾頁。你整場都沒有開啟列印預覽功能,所以它的那些頁根本沒有被分頁進來——在你整個工作階段中它們佔用零 RAM。
頁只在第一次被參考時才到來;沒被碰過的頁永遠不會載入。
需求分頁代表頁只在發生錯誤時才載入,但這不代表每次錯誤都很便宜——一次必須從磁碟讀取的錯誤,比一次記憶體內存取慢上數十萬倍,因此整套機制仰賴錯誤本身很罕見。