虛擬記憶體與需求分頁

寫入時複製(copy-on-write)

想像兩位室友都想要同一張長長購物清單的個人副本。如果兩人都不打算更改它,馬上影印一份是浪費——他們大可共用那一張、各自讀取。於是他們共用,並且只在其中一人真的想塗改時,才做出私人副本。在有人寫之前,一張共用清單同時服務兩人;複製是延遲的,發生在第一次寫入時。寫入時複製把這個把戲一模一樣地套用到記憶體的頁上。

具體來說,當一個行程 fork 出一個子行程時,子行程會得到父行程位址空間的一份副本。急切地複製每一頁既慢又常常徒勞,因為子行程往往隨即就呼叫 exec、把自己的記憶體換掉。取而代之,寫入時複製讓父行程與子行程共用同樣的實體頁框,在兩者的分頁表裡全都標記為唯讀。只要雙方都只讀取,它們就愉快地共用同一份副本。當任一方試圖寫入某個共用頁的那一刻,硬體就陷入(一次保護錯誤);作業系統接著只為這一頁、為那個要寫的一方做出一份私人副本,把那一方的分頁表項目指向新頁框並恢復寫入權限,再讓寫入進行。只有真正被修改的那些頁才會被複製;所有沒被碰過的都維持共用。

為什麼重要:寫入時複製讓 fork 既便宜又快速,這很要緊,因為「先 fork 再 exec」是類 Unix 系統啟動新程式的標準做法。沒有它,每一次 fork 都得複製父行程的整個記憶體,卻只是為了在一瞬間之後就丟掉。同樣的想法也驅動了高效的快照、虛擬機器的記憶體共享,以及寫入時複製檔案系統。誠實的微妙之處是:寫入時複製只是延後成本、並未消除它——一個父子雙方都大量寫入的工作負載會觸發許多次複製,最終也許不比一開始就複製便宜,還外加每次錯誤的陷入開銷。

一個使用 500 MB 的行程 fork。作業系統不複製那 500 MB,而是把所有頁標記為唯讀且共用。子行程隨即呼叫 exec、把自己的記憶體整個換掉——於是那 500 MB 幾乎沒有一個位元組真的被複製過。只有子行程在 exec 之前寫過的那少數幾頁才需要複製。

頁在被寫入之前一直共用;只有被修改的頁才會被複製。

寫入時複製是延後、而非消除複製的成本。若父子雙方都對大多數頁寫入,你終究要付出複製代價,外加每個首次寫入的陷入開銷——因此它主要在常見的「fork 再 exec」或大量唯讀共享模式下才划算。

又称
CoWCOW寫時複製