iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
自我挑戰組

從 Page Fault 到 OOM:Linux 記憶體管理 30 天拆解系列 第 9

Copy-on-write:fork() 為什麼可以很快

  • 分享至 

  • xImage
  •  

在 Unix-like 系統裡,fork() 會建立一個幾乎和 parent process 相同的 child process。直覺上,kernel 好像必須複製 parent 的整個記憶體;如果 process 已經用了數 GB,這個動作理應非常慢。但實際上,fork() 通常只需要複製記憶體管理的中繼資料和 page table,而不會立刻複製每一個 physical page。背後的關鍵就是 copy-on-write,簡稱 COW。

呼叫 fork() 之後,parent 和 child 的虛擬位址空間彼此獨立,但一開始可以讓兩邊的 page table 指向同一批 physical pages。kernel 會把原本可寫的映射暫時標成唯讀,並記錄這些 page 受到 COW 保護。只要雙方都只有讀取,共用同一份內容就是安全的,也不必浪費時間和 RAM 製作副本。

真正的複製會延後到其中一方嘗試寫入。CPU 發現 page table entry 不允許寫入,便觸發 page fault。kernel 判斷這不是非法存取,而是 COW fault,接著配置新的 physical page、複製原 page 的內容、更新寫入方的 page table,再重新執行剛才的指令。完成之後,修改只會出現在寫入方的副本,另一個 process 仍然看到原內容。

這和上一篇的 demand paging 有相似精神:把可能不需要的工作延後,直到真的有需求。fork() 之後常會立刻呼叫 exec(),用另一個程式映像取代 child 原本的 address space。若一開始就完整複製 parent 的所有 page,這些副本馬上又會被丟掉;COW 可以避開大部分無用功。

COW 並不表示 fork() 完全沒有成本。kernel 仍要建立 child 的 process 資料結構、複製 VMA 資訊和 page table,還要調整 page 的引用計數。process 的 address space 越大,page table 本身的處理成本通常也越高。若 parent 或 child 隨後大量寫入,還會產生許多 COW fault、記憶體配置和 page copy,延遲與 RSS 都可能迅速增加。

多執行緒程式在 fork() 後也要特別小心。child 會保留呼叫 fork() 的 thread,但 process 內的 mutex 可能原本由其他 thread 持有。因此在 fork()exec() 之間,通常僅能呼叫 async-signal-safe 的函式。這是同步狀態的問題,不是 COW 自己能解決的問題。

觀察 COW 時,可以寫一個程式先配置並寫入一大片 anonymous memory,再呼叫 fork()。如果 parent 和 child 都不進行寫入,兩邊的 PSS 會反映共享 page,整體實體記憶體不會立刻翻倍;讓 child 逐頁修改後,private dirty memory 和總 RSS 才會逐步上升。過程中增加的 minor page faults,也常是 COW fault 留下的跡象,因為通常不需要磁碟 I/O。

Copy-on-write 讓 fork() 的成本更接近「先複製映射關係,之後按需複製內容」,而不是「立刻複製整個 process」。下一篇會往更底層走,看看 kernel 用 buddy allocator 管理 physical pages,理解 COW fault 需要新 page 時,這些 page 從哪裡來。


上一篇
Demand paging:用到才真的分配
下一篇
Buddy allocator:kernel 怎麼管理 physical pages
系列文
從 Page Fault 到 OOM:Linux 記憶體管理 30 天拆解11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言