Linux--寫時複製(Copy-On-Write,COW)技術簡述

心恩惠动發表於2024-06-28

1、簡介

 寫時複製(Copy-on-write,COW),有時也稱為隱式共享(implicit sharing)。COW 是一種記憶體管理技術,將複製操作推遲到第一次寫入時進行:在建立一個新副本時,不會立即複製資源,而是共享原始副本的資源;當修改時再執行復制操作

 透過這種方式共享資源,可以大幅減少記憶體消耗和複製開銷,同時實現高效的資源複製

優點:

  1. 記憶體利用率高:

    • 在初始化時,COW 技術可以讓多個程序共享同一份記憶體頁面,減少記憶體佔用。
    • 只有在某個程序需要修改頁面時,才會複製出新的頁面,從而提高記憶體利用率。
  2. 效能提升:

    • 在建立新程序時,COW 技術可以快速完成頁面複製,避免了全量複製的開銷。
    • 這對於需要頻繁建立子程序的場景(如 Redis 的資料持久化)非常有利。
  3. 資料一致性:

    • COW 技術可以確保每個程序都擁有一致的資料檢視,避免了資料競爭問題。
    • 這對於多程序共享資料的場景很有幫助。

缺點:

  1. 記憶體碎片問題:

    • 頻繁的頁面複製可能會導致記憶體碎片,降低記憶體使用效率。
    • 這需要作業系統提供相應的記憶體管理策略來緩解。
  2. 不適用於高併發修改場景:

    • 如果多個程序同時修改同一個頁面,COW 技術就無法提供資料一致性保證。
    • 這種場景下需要採用其他的併發控制機制。

2、背景

在傳統的記憶體管理機制中,當透過 fork() 來建立一個子程序時,作業系統需要將父程序虛擬記憶體空間中的大部分內容全部複製到子程序中(主要是資料段、堆、棧;程式碼段共享)。這個操作不僅非常耗時,而且會浪費大量實體記憶體。特別是如果程式在程序複製後立刻使用 exec 載入新程式,那麼負面效應會更嚴重,相當於之前進行的複製操作是完全多餘的。

因此引入了寫時複製技術。核心不會複製程序的整個地址空間,而是隻複製其頁表,fork 之後的父子程序的地址空間指向同樣的實體記憶體頁。當一個程序試圖寫入共享區域的某個頁面,那麼就會為這個程序建立該頁面的一個新副本。

3、工作原理

  1. 當父程序呼叫fork()建立子程序時,核心會將父程序的所有記憶體頁都標記為只讀(即共享頁面),並增加每個頁面的引用計數。在這個過程中,父子程序共享同一份記憶體頁面,可以大幅減少記憶體佔用。
  2. 一旦其中一個程序(父程序或子程序)嘗試寫入某個記憶體頁,就會觸發一個保護故障(缺頁異常),此時會陷入核心,核心將攔截這個寫入操作,檢查該頁面的引用數:
    • 如果引用數大於 1,則會建立該頁面的副本,並將引用數減 1,同時恢復這個頁面的可寫許可權,然後重新執行這個寫操作
    • 如果頁面的引用數只有 1,也就是說該頁面只被當前程序引用,那麼核心就可以跳過分配新頁面的步驟,直接修改該頁面,將其標記為可寫

這種分配過程對於程序來說是透明的,即程序無需關心記憶體頁面的引用計數和複製過程,能夠確保一個程序的記憶體更改在另一程序中不可見。

在一般情況下,當子程序透過寫時複製機制建立了自己的記憶體頁面副本後,這個副本會一直與父程序的頁面保持不一致,直到該子程序退出或被殺死

如果需要讓子程序的頁面修改內容"回寫"到父程序的頁面中,可以使用以下系統呼叫:

  1. msync() 系統呼叫:

    • msync() 可以用來同步記憶體對映檔案的內容。
    • 子程序可以在修改頁面後,呼叫 msync() 將修改的內容寫回到記憶體對映的檔案中。
    • 之後父程序就可以從對映檔案中讀取到子程序的修改內容。
  2. madvise() 系統呼叫:

    • madvise() 可以用來提供記憶體使用的建議資訊給作業系統。
    • 子程序可以在修改頁面後,呼叫 madvise() 並指定 MADV_DONTNEED 選項,告知作業系統可以丟棄該頁面的內容。
    • 這樣作業系統就會將子程序修改過的頁面內容寫回到父程序的頁表指向的原始頁面中。

不過這種做法與 COW 的資料隔離特性相反,需要根據實際需求來權衡使用。

4、應用

4.1 Redis
  1. 資料持久化:

    • Redis 支援 RDB 和 AOF 兩種資料持久化方式。
    • 在執行 SAVEBGSAVE 命令時,Redis 會 fork 出一個子程序來執行持久化操作。
    • 這時 Redis 主程序和子程序會共享同一份記憶體頁面,直到子程序需要寫入資料時才會複製頁面(Copy-On-Write)。
  2. 資料複製(主從複製):

    • Redis 支援主從複製,從伺服器會 fork 出一個子程序來同步主伺服器的資料。
    • 在同步過程中,主從伺服器也會共享記憶體頁面,直到從伺服器需要寫入資料時才會複製頁面。
  3. 叢集伸縮:

    • Redis 叢集在增加或刪除節點時,也會用到 COW 技術來減少記憶體開銷。
    • 當增加節點時,新節點會 fork 出一個子程序來複制其他節點的資料,在複製過程中利用 COW 技術。

4.2 檔案複製

當需要複製一個大檔案時,作業系統也會使用COW技術,複製操作會建立一個新的檔案控制代碼,並將原檔案的資料頁面對映給新檔案,而不是直接複製整個檔案內容。只有當其中一個檔案被修改時,作業系統才會為其建立一個副本。

4.3 快照和克隆

在檔案系統中,COW技術可用於快照和克隆功能,當建立快照或克隆時,作業系統僅會複製後設資料,而不會複製整個檔案系統,只有當某個檔案被修改時,才會為該檔案建立一個新的資料塊。

相關文章