RFC-0226:Zircon Pager 寫回

RFC-0226:Zircon Pager 寫入
狀態已接受
區域
  • 核心
說明

對支援分頁式 VMO 的追蹤及寫入修改項目的核心支援

問題
變更
作者
審查人員
提交日期 (年/月)2023-04-13
審查日期 (年/月)2023-09-19

摘要

本文件說明對分頁支援記憶體的 Zircon 核心支援。這些記憶體可修改,然後再寫回 (同步) 至呼叫器來源 (例如儲存磁碟)。

提振精神

Zircon 支援建立由使用者空間分頁器服務支援的 VMO (虛擬記憶體物件),通常是由檔案系統託管。個別檔案在記憶體中會顯示為 VMO。當使用者存取 VMO 頁面時,頁面會隨需求變造,而使用者呼叫器會從磁碟中讀取頁面內容。

Zircon Pager API 原本僅支援唯讀檔案;之前已在記憶體中清理為需要寫回磁碟的 VMO 頁面並沒有機制。這項設計足以代管不可變更的檔案系統,例如 blobfs,後者提供 Fuchsia 上的所有執行檔和其他唯讀檔案。不過,一般用途檔案系統需要使用寫入支援,用戶端可修改檔案內容,並且需要同步回磁碟。

在不支援寫回的情況下,minfsfxfs 等可變動檔案系統無法利用需求分頁。如要解決這個問題,可變動檔案系統必須使用匿名 (非分頁式) VMO 快取記憶體中的檔案,並管理這些 VMO 的內容。這些 VMO 可能必須完整保留在記憶體中,即使其中某些頁面很少或從未存取。將這些匿名 VMO 切換為對翻頁的 VM,讓頁面能在需要時出錯及移除,讓可變動的檔案系統能更有效地利用記憶體。寫入支援也可讓可變動檔案系統的用戶端直接在 VMO 上執行讀取及寫入作業,而不是依賴資料移轉的管道,而這會受到管道限制的限制,速度可能會極慢。

視情境而定,在本文件的其餘部分會交替使用使用者分頁器和檔案系統的字詞。

相關人員

講師:

  • cpu@google.com

審查者:

  • adanis@google.com、csuter@google.com

顧問:

  • brettw@google.com, cdrllrd@google.com, godtamit@google.com, maniscalco@google.com, travisg@google.com

社群媒體化:

這個 RFC 與本機儲存空間團隊進行了設計審查。

相關規定

此提案的設計著重於下列目標:

  1. 強化 Zircon,以便支援對分頁式 VMO 的寫入,以便建構高效能的可變動檔案系統 (使用 Zircon 串流)。
  2. 支援透過 VM 對應讀取及寫入檔案 (例如 mmap 的檔案)。
  3. 提供使用者呼叫器盡可能清除骯髒頁面,降低因意外關閉而造成資料遺失的風險。
  4. 在日後的疊代中,允許核心 (透過使用者呼叫器) 清除骯髒頁面,以回應系統記憶體壓力。

還有一些非目標:

  1. 除了使用者呼叫器啟動的清除之外,核心不保證能確切清理骯髒網頁的速率。也就是說,未來演進可能會增加功能,能夠限制尚未處理的骯髒資料數量,以及讓核心依據該數量發出寫回要求。
  2. 避免因違反核心/pager 通訊協定而造成資料遺失,並非目標。如果使用者呼叫器無法查詢骯髒頁面,並在關閉 VMO 控制代碼之前 (或終止) 前將其寫回,則資料可能會遺失。

設計

總覽

提議的設計旨在支援兩種即時用途:

  • 檔案系統用戶端可以透過串流 (會包裝檔案 VMO 的核心物件) 存取檔案。由於串流系統呼叫會在內部納入 VMO 讀取/寫入核心處理常式,因此大致上可以視為與 zx_vmo_read()zx_vmo_write() 類似。
  • 檔案系統用戶端也可以對檔案進行 mmap 處理,該檔案大致會轉譯 (使用 zx_vmar_map) 將檔案 VMO 對應至用戶端程序的位址空間。

為簡單起見,本文件的其餘部分將透過系統呼叫 (zx_vmo_read/write) 或 VM 對應 (zx_vmar_map) 談論與檔案 VMO 的直接互動。

以下列舉幾個範例,說明與寫入相關的互動可能的呈現方式。

示例 1

  1. 檔案系統用戶端在指定範圍的檔案 VMO 上執行 zx_vmo_read()
  2. 由於 VMO 受到分頁回應支援,因此核心會為相關聯的使用者呼叫器產生讀取要求。
  3. 由檔案系統代管的使用者呼叫器會完成這項要求。它會提供從磁碟讀取內容的網頁。
  4. 檔案系統用戶端針對相同範圍的 VMO 執行 zx_vmo_write()。VMO 頁面先前已在步驟 3 中填入,因此只要直接寫入即可。所做修改只會出現在記憶體中,但有時需要回到磁碟上。
  5. 使用者呼叫器會查詢 VMO 中已清除 / 修改的範圍,這項作業可透過定期背景清除檔案系統執行,或回應檔案系統用戶端要求的清除作業來完成。
  6. 使用者呼叫器會將查詢的骯髒範圍寫回磁碟。此時,修改的檔案內容已成功儲存至磁碟。

示例 2

  1. 檔案系統用戶端會使用 zx_vmar_map() 對應檔案 VMO。對應關係從位址 addr 開始。用戶端會從 addr 開始讀取範圍。
  2. 與範例 1 相同。
  3. 與範例 1 相同。
  4. 檔案系統用戶端現在會從 addr 開始寫入相同範圍。基礎頁面已填入資料,因此記憶體中的內容已經過修改。修改內容在某個時間點需要還原到磁碟上。
  5. 與範例 1 相同。
  6. 與範例 1 相同。

這裡的範例會先從 VMO 讀取開始,再執行寫入作業。請注意,上述做法只是依照使用者呼叫器將頁面填入作業分成獨立步驟進行區分。用戶端可以直接寫入不在記憶體中的檔案偏移值;寫入作業只會封鎖,直到使用者呼叫器提供頁面為止。

上述兩個範例假設檔案系統遵循「覆寫」模型進行寫入,其中填入的 (修訂) 頁面可以直接寫入,無須先要求額外的空間。修改後的內容會寫回磁碟上的相同位置,因此不需要分配額外的空間給修改。不過,fxfsminfs 等檔案系統使用的是寫入時複製 (CoW) 模型,在這個模式下,每次修改都需要分配新的空間給磁碟。因此,我們也需要一個機制來保留已承諾的頁面寫入空間;修改步驟 4,以等待該保留項目繼續寫入。

為執行寫入作業,Zircon Pager API 也經過擴充以支援下列項目:

  • 核心區塊會寫入 VMO,使用者呼叫器指示應遵循「複製時寫入」配置,並在使用者呼叫器確認寫入後繼續進行。
  • 核心會追蹤 VMO 中的骯髒頁面,並具備向使用者呼叫器的機制。
  • 使用者分頁器會在 VMO 同步處理骯髒範圍時向核心指示,並在執行完成後告知核心,這樣核心就能據此更新 Dirty 追蹤狀態。
  • 核心也會提供 VMO 調整至使用者呼叫器的相關資訊。
  • 使用者呼叫器可讓您查詢核心代表其追蹤的相關資訊,例如上次修改 VMO 的時間。

建立呼叫頁面和 VMO

呼叫器建立系統呼叫保持不變,即 zx_pager_create() 用於建立將 options 設為 0 的分頁器。

支援呼叫器的 VMO 是使用 zx_pager_create_vmo() 建立,並與呼叫器通訊埠相關聯,以及用於該 VMO 頁面要求封包的金鑰。zx_pager_create_vmo() Syscall 也支援新的 options 旗標 ZX_VMO_TRAP_DIRTY。這表示核心應攔截所有寫入 VMO 的寫入作業,並先要求使用者呼叫器進行寫入確認。這個標記適用於在寫入時複製模式的檔案。稍後可以查看這個標記的詳細資料。

// Create a VMO (returned via |out|) backed by |pager|. Pager requests will be
// queued on |port| and will contain the provided |key| as an identifier.
// |size| will be rounded up to the page boundary.
//
// |options| must be 0 or a combination of the following flags:
// ZX_VMO_RESIZABLE - if the VMO can be resized.
// ZX_VMO_TRAP_DIRTY - if writes to clean pages in the VMO should be trapped by the
// kernel and forwarded to the pager service for acknowledgement before proceeding
// with the write.
zx_status_t zx_pager_create_vmo(zx_handle_t pager,
                                uint32_t options,
                                zx_handle_t port,
                                uint64_t key,
                                uint64_t size,
                                zx_handle_t* out);

根據預設,所有對分頁支援的 VMO 都會視為可變動;這也適用於實作唯讀檔案系統,而不需要額外付費。用於修改頁面的程式碼路徑目前不可用於唯讀 VMO。但如果 VMO 經過修改 (可能不慎誤用),但使用者呼叫器從未查詢其中有問題的頁面,並嘗試重新撰寫,那麼修改的內容只會保留在記憶體中。日後當核心產生寫入要求時,使用者呼叫器可以把這類 VMO 的寫入要求視為錯誤,或直接忽略。

提供 VMO 頁面

收到呼叫器讀取要求時,使用者分頁式 VMO 中的頁面會隨選填入 zx_pager_supply_pages()。這個系統呼叫已可與唯讀 VMO 搭配使用。

寫回通知頁面狀態

由呼叫器支援的 VMO 可以有三種狀態:DirtyCleanAwaitingClean。這些狀態會在 vm_page_t 中編碼。

三種狀態之間的轉換步驟如下:

  1. 剛使用 zx_pager_supply_pages() 提供的頁面一開始為 Clean
  2. 寫入頁面時,這會轉換為 Dirty。如果 VMO 是透過 ZX_VMO_TRAP_DIRTY 建立,則會先封鎖使用者呼叫器發出的 DIRTY 呼叫器要求,然後發生核心封鎖。稍後可找到此互動的更多詳細資料。
  3. 使用者呼叫器會在稍後透過系統呼叫,查詢 VMO 中的骯髒頁面清單。
  4. 針對核心傳回的每個骯髒頁面,使用者呼叫器會叫用 syscall,告知其開始寫入頁面的核心,而該核心會將狀態變更為 AwaitingClean
  5. 如果網頁在此時間之後寫入,狀態就會切換回 Dirty
  6. 使用者呼叫器讀完頁面後,會再次發出系統呼叫。 如果頁面狀態在這個時候為 AwaitingClean,則會轉換為 Clean
  7. 如果使用者呼叫器在寫回時遇到錯誤,頁面仍會保留在 AwaitingClean 中。骯髒網頁的未來查詢會同時傳回 AwaitingCleanDirty 網頁,因此使用者呼叫器可以嘗試再次寫入網頁。

下列狀態圖表顯示了這些狀態之間的轉換情形。

骯髒狀態轉換圖表

必須將 AwaitingClean 視為獨立狀態追蹤,原因如下:

  • 即使 CleanAwaitingClean 狀態的網頁在寫入時都會轉換為 Dirty,但是使用者呼叫著正在返回頁面時,也必須將其視為與 Clean 頁面不同。Clean 頁面可在記憶體壓力下恢復,但正在撰寫的頁面需要避免被收回。

  • 核心必須知道哪個版本的網頁已寫回,才能在使用者呼叫器完成時正確將其轉換為 Clean。重要的是,區分清除之前的頁面寫入 (系統會安全地將寫入磁碟寫入磁碟) 和之後傳入 (稍後需要寫回) 的頁面寫入。

  • 我們可以避免在寫回開始時,來自使用者呼叫器的 syscall,而當核心網頁向使用者呼叫器查詢惡意頁面時,核心可以直接將頁面標示為 AwaitingClean。但是,使用者呼叫器可能需要一些時間才會在查詢後開始清除頁面,因此會預留較長的視窗時間讓系統再次清空頁面。將寫留時間置於最前面的時段,會使使用者分頁器成功將頁面移至 Clean 狀態。

為了更新骯髒狀態,核心會追蹤透過 VMO、串流寫入系統呼叫和 VM 對應寫入網頁的時間。請注意,此情況適用於透過 VM 對應時發生的任何寫入作業 (無論是由使用者或核心執行)。也就是說,若核心使用 user_copy 做為 zx_channel_read() 等系統呼叫的一部分,也會套用這項設定。

由於指定範圍,因此在像 zx_vmo_write() 這樣的系統呼叫期間推論出骯髒頁面就是相當簡單的做法。另一個存取 VMO 的方式,則是透過程序位址空間中的 VM 對應。為了在受頁面支援的 VMO 中追蹤骯髒狀態,可寫入的對應關係會從相應頁面表格項目中移除寫入權限開始。因此,寫入作業會產生防護錯誤,透過還原寫入權限並將頁面狀態標示為 Dirty 即可解決。

此處提及的 Dirty 狀態是由 vm_page_t 追蹤的狀態,也就是軟體追蹤的骯髒狀態。x86 的硬體頁面表格支援 Dirty 位元追蹤功能,但我們選擇不使用這種追蹤在初次實作時導出網頁的骯髒狀態。對於不支援在頁面表格中支援骯髒位元的舊版 arm64 平台,我們也必須追蹤軟體中的骯髒位元。因此,為確保實作的一致性和簡易性,我們選擇開始時,不使用硬體乾淨 / 乾淨狀態來推斷網頁。仰賴硬體頁面資料表位元也會使頁面資料表重建功能出現問題,未來如果我們依賴硬體位元,也必須將此納入考量。

值得注意的是,只有 Pager 直接支援的 VMO 才符合追蹤髒汙的資格。換句話說,由 Pager 支援的 VMO 副本不會採用骯髒的追蹤機制,且不會顯示任何寫回要求。在本機副本中編寫的頁面會從父項頁面副本建立分支,而本機副本會直接將這些網頁設為不同的頁面。

預留待寫入的空間

這個外掛程式能寫入標記為使用 ZX_VMO_TRAP_DIRTY 建立選項旗標來處理骯髒轉換的 VMO,要求檔案系統進行確認。這項解決方案分為兩個部分:v1 從簡單開始,更著重於正確性,v2 則是在第 1 版上建構以提升效能。v1 提案大多使用同步模型,且檔案系統會保留空間來進行新寫入作業。使用 v2 時,我們會新增另一層來在核心中顯示骯髒的預留項目配額,以及如何套用至 VMO,讓核心可以追蹤預留項目本身。減少核心與檔案系統之間的大多數往返通訊,有助於提升效能。

ZX_VMO_TRAP_DIRTY v1

ZX_VMO_TRAP_DIRTY VMO 建立標記表示核心應處理 VMO 中的任何 Clean->Dirty 頁面轉換 (或 AwaitingClean->Dirty 轉換)。如果頁面上有寫入作業,核心就會產生 ZX_PAGER_VMO_DIRTY 呼叫器要求。如果是透過 VM 對應寫入,要求會橫跨包含錯誤位址的單一頁面。針對串流/VMO 寫入,核心會針對需要寫入範圍的每次連續執行非骯髒頁面傳送要求。

[start, end) 範圍的骯髒要求如下所示。

zx_packet_page_request_t request {
    .command = ZX_PAGER_VMO_DIRTY,
    .flags = 0,
    // |offset| and |length| will be page-aligned.
    .offset = start,
    .length = end - start,
};

ZX_VMO_TRAP_DIRTY 建立旗標適合用於以 CoW 模式編寫的檔案,以及在「覆寫」模式下編寫的稀疏檔案。如未指定旗標,系統會將頁面標示為 Dirty,並可在沒有使用者呼叫器作業的情況下繼續寫入;這是適合以「覆寫」模式編寫的非稀疏檔案使用。

使用者呼叫器使用 zx_pager_op_range() 確認 ZX_PAGER_VMO_DIRTY 要求:

  • ZX_PAGER_OP_DIRTY 會將尚未 Dirty 的網頁狀態設為 Dirty,核心則會繼續執行遭封鎖的寫入作業。
  • ZX_PAGER_OP_FAIL 不會變更網頁目前的狀態,且在 zx_vmo_write() 呼叫原始寫入作業失敗時,會產生 VM 對應的嚴重分頁錯誤例外狀況,如果 zx_stream_write() 僅透過部分寫入成功傳回。
// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |offset| and |length| specify the range, i.e. [|offset|, |offset| + |length|).
//
// |op| can be:
//
// ZX_PAGER_OP_DIRTY - The userspace pager wants to transition pages in the range
// [offset, offset + length) from clean to dirty. This will unblock any writes that
// were waiting on ZX_PAGER_VMO_DIRTY page requests for the specified range.
// |data| must be 0.
//
// ZX_PAGER_OP_FAIL - The userspace pager failed to fulfill page requests for
// |pager_vmo| in the range [offset, offset + length) with command
// ZX_PAGER_VMO_READ or ZX_PAGER_VMO_DIRTY.
//
// |data| contains the error encountered, a zx_status_t error code sign-extended
// to a |uint64_t| value - permitted values are ZX_ERR_IO, ZX_ERR_IO_DATA_INTEGRITY,
// ZX_ERR_BAD_STATE and ZX_ERR_NO_SPACE.
zx_status_t zx_pager_op_range(zx_handle_t pager,
                              uint32_t op,
                              zx_handle_t pager_vmo,
                              uint64_t offset,
                              uint64_t length,
                              uint64_t data);

視檔案系統清除骯髒資料並標示 Clean 的頻率而定,此方法可能會在用戶端寫入頁面時產生大量效能成本。為了避免這類成本,檔案系統可能會想要盡可能延遲清除骯髒資料的時間,但這並不是提供獎勵的好誘因:無法清除骯髒頁面,導致記憶體壓力,而清除之間的間隔時間越長,也可能導致資料遺失的可能性。v2 提案會嘗試降低部分用戶端寫入作業產生的部分效能成本。

ZX_VMO_TRAP_DIRTY v2

將會新增 Syscall zx_pager_set_dirty_pages_limit(),藉此指定特定數量的骯髒頁面數量可讓核心累積。上述情況是,檔案系統已預先為許多骯髒頁面保留空間。此為每頁頁數限制,預設值為 0。 您可以使用 zx_pager_set_dirty_pages_limit() 將限制設為非零值 (如果需要多次)。v1 設計基本上是在這個限制設為零的情況下運作。

zx_status_t zx_pager_set_dirty_pages_limit(zx_handle_t pager_handle,
                                           uint64_t num_dirty_pages);

核心會追蹤每頁頁數的骯髒頁數 (進一步瞭解符合稍後追蹤資格的頁面類型),並增加轉移至 Dirty 的計數,並在轉換至 Clean 時減少。和第 1 版一樣,核心仍會包覆所有 Dirty 轉換,但如果能夠處理,核心仍會增加未完成的骯髒頁面數量,而不會超出配置的骯髒上限。如果新的計數未超過限制,核心就會繼續執行寫入作業,不會涉及使用者呼叫器。這應是正常的作業模式,因此每次網頁清除時,使用者呼叫器都能省下往返費用。

使用這個方法時,使用者呼叫器需要向核心傳達兩項內容:

  1. 呼叫器全頁骯髒限制
  2. 網頁超出限制時,網頁數量仍會計入該上限

2) 我們再次使用 ZX_VMO_TRAP_DIRTY VMO 建立旗標。這個標記現在會觸發新類型的 Pager 要求產生作業:ZX_VMO_DIRTY_MODE。核心陷阱現在寫入時,系統會查詢檔案系統,判斷是否應選擇將這些頁面納入計算至骯髒限制的範圍內。使用者呼叫器回應 zx_pager_op_range,其中包含兩種新的作業類型之一。

  • ZX_PAGER_OP_DIRTY_MODE_POOLED 會告知核心,範圍內的頁面會計算每頁的骯髒限制。這適用於以 CoW 模式運作的檔案,以及「覆寫」模式下的檔案稀疏區域。

  • ZX_PAGER_OP_DIRTY_MODE_UNPOOLED 會告知核心,範圍內的頁面不會計入骯髒限制中。這適用於在「覆寫」模式下運作的非稀疏檔案區域。

如果指定 ZX_PAGER_OP_DIRTY_MODE_POOLED 頁面未超過呼叫器骯髒限制,就會轉換為 Dirty,並增加未處理的分頁符號數量。如果直接清除頁面會超過分頁器的骯髒限制,核心就會開始產生 ZX_PAGER_VMO_DIRTY 封包,也就是第 1 版中所述的預設模式。您可以在寫入網頁時提供選用旗標以設定骯髒模式 (轉換至 Clean),這樣可以省下將日後寫入作業的傳輸費用,進而產生 ZX_VMO_DIRTY_MODE 呼叫器要求。

這種設計可提供靈活的模型,其中檔案系統可在自己的 VMO 上混合不同類型的寫入模式。使用 CoW 模式編寫的檔案會有使用 ZX_VMO_TRAP_DIRTY 建立的 VMO,而其頁面可使用集區模式。同樣地,「覆寫」模式下的稀疏檔案可使用 ZX_VMO_TRAP_DIRTY 旗標建立,並分別針對稀疏和非稀疏地區使用「集區」和「未集區」模式。一律使用「覆寫」模式的檔案可以完全省略 ZX_VMO_TRAP_DIRTY 旗標,也不必支付寫入作業產生的呼叫器要求費用。

一旦使用者呼叫器開始收到 ZX_PAGER_VMO_DIRTY 要求,當配額不足時,系統就會開始清理頁面,為新的骯髒頁面騰出空間。收到 zx_pager_set_dirty_pages_limit() 時,其會使用與之前相同的限製或新的限制來發出訊號。此呼叫結束後,核心將繼續根據日後寫入的骯髒數量檢查累積的骯髒計數,並且只會在再次達到無效限制時產生 ZX_PAGER_VMO_DIRTY 要求。

ZX_VMO_TRAP_DIRTY v1 和 v2 的差異

第 1 版和第 2 版的主要差異,在於負責追蹤預留項目數量的實體。在 v1 中,檔案系統負責追蹤預留項目,而核心會發出通知,告訴系統要何時增加預留項目數量,以及該數量的增加幅度。由於負責攔截潛在變更 (核心) 的實體與執行實際簿記 (檔案系統) 的實體不同,因此兩者之間需要緊密耦合。在 v2 中,我們試著讓核心追蹤預留項目本身數量,藉此稍微放鬆一下。因此,只有在 - 1) VMO 範圍必須設為使用 (或停用) VMO 範圍才能選擇使用 (或停用) 核心預留項目,以及 2) 核心用完預留配額,且檔案系統需要介入的情況下,您才需要與檔案系統進行通訊。

我們預期 2) 是邊緣案例,因為檔案系統會定期將骯髒的頁面清空到磁碟。多數通訊的預期原因為 1)。核心可以多次要求相同範圍的資訊 (例如跨越重疊範圍的寫入作業),同樣地,檔案系統也可以多次為核心提供關於相同範圍的備援資訊。在頁面上設定骯髒模式並不會實際超出骯髒上限,因為骯髒數量只增加實際寫入頁面的次數。因此,檔案系統也可以推測頁面上的骯髒模式,減少日後處理呼叫器要求的效能成本 (需要注意的是,因為頁面剔除可能會受到影響)。

發現髒汙的範圍

使用者呼叫器需要一項機制來找出 VMO 中有問題的頁面,以便將資料寫回。在此需要考慮兩種不同的模型:當使用者從核心查詢無效頁面資訊時提取模型;而核心是傳送使用者呼叫器寫入要求以指出骯髒頁面的推送模型。初始設計從較簡單的提取模型開始,並導入查詢 syscall 的骯髒範圍,如下所示:

// |pager| is the pager handle.
// |pager_vmo| is the vmo handle.
// |offset| and |length| specify the VMO range to query dirty pages within.
// Must be page-aligned.
//
// |buffer| points to an array of type |zx_vmo_dirty_range_t| defined as follows.
// typedef struct zx_vmo_dirty_range {
//   // Represents the range [offset, offset + length).
//   uint64_t offset;
//   uint64_t length;
//   // Any options applicable to the range.
//   // ZX_VMO_DIRTY_RANGE_IS_ZERO indicates that the range contains all zeros.
//   uint64_t options;
// } zx_vmo_dirty_range_t;
//
// |buffer_size| is the size of |buffer|.
//
// |actual| is an optional pointer to return the number of dirty ranges that were
// written to |buffer|.
//
// |avail| is an optional pointer to return the number of dirty ranges that are
// available to read. If |buffer| is insufficiently large, |avail| will be larger
// than |actual|.
//
// Upon success, |actual| will contain the number of dirty ranges that were copied
// out to |buffer|. The number of dirty ranges that are copied out to |buffer| is
// constrained by |buffer_size|, i.e. it is possible for there to exist more dirty
// ranges in [offset, offset + length) that could not be accommodated in |buffer|.
// The caller can assume than any range that had been made dirty prior to
// making the call will either be contained in |buffer|, or will have a start
// offset strictly greater than the last range in |buffer|. Therefore, the caller
// can advance |offset| and make another query to discover further dirty ranges,
// until |avail| is zero.
//
zx_status_t zx_pager_query_dirty_ranges(zx_handle_t pager,
                                        zx_handle_t pager_vmo,
                                        uint64_t offset,
                                        uint64_t length,
                                        void* buffer,
                                        size_t buffer_size,
                                        size_t* actual,
                                        size_t* avail);

使用者呼叫器應會多次叫用這項查詢,以便提高查詢的偏移量,直到處理所有骯髒頁面為止。

使用提取模型時,清理頁面的速率完全取決於檔案系統選擇查詢無效範圍並嘗試寫回的速率。在少數情況下,核心本身可能必須發出寫入返回頁面的要求 (例如記憶體壓力之下),以便清理乾淨的頁面後再釋出。在此情況下,核心可能會以 LRU 順序傳送骯髒頁面的寫入要求。其用意是提醒使用者呼叫器,以便提高清除頁面的速率,例如以延遲方式處理要求。

在 VMO 中,無效範圍 [start, end) 的寫入要求可能如下所示。

zx_packet_page_request_t request {
    .command = ZX_PAGER_VMO_WRITEBACK,
    .flags = ZX_PAGER_MEMORY_PRESSURE,
    // |offset| and |length| will be page-aligned.
    .offset = start,
    .length = end - start,
};

正在寫入骯髒的範圍

zx_pager_op_range() 系統呼叫會擴充支援兩個額外的操作 (ZX_PAGER_OP_WRITEBACK_BEGINZX_PAGER_OP_WRITEBACK_END),以指出使用者呼叫器開始清除頁面及在完成時分別發生的時間。

  • ZX_PAGER_OP_WRITEBACK_BEGIN 會將指定範圍內任何 Dirty 頁面的狀態變更為 AwaitingClean。處於 AwaitingCleanClean 狀態的任何頁面都會遭到忽略,且這些狀態維持不變。
  • ZX_PAGER_OP_WRITEBACK_END 會將指定範圍內任何 AwaitingClean 頁面的狀態變更為 Clean。已經是 Clean 的網頁或處於 Dirty 的網頁,系統會忽略此屬性,且頁面的狀態維持不變。

如果在執行清除作業期間發生任何錯誤 (亦即在 ZX_PAGER_OP_WRITEBACK_BEGIN 之後和 ZX_PAGER_OP_WRITEBACK_END 之前),則使用者呼叫器無須執行其他動作。這些頁面會在核心中維持在 AwaitingClean 狀態,並假設其他寫入未成功。當 Google 再次查詢骯髒頁面時,就會包含 AwaitingClean 頁面和 Dirty 頁面,而使用者呼叫器可以在這些失敗頁面上嘗試再次寫回。

// Supported |op| values are:
// ZX_PAGER_OP_WRITEBACK_BEGIN indicates that the user pager is about to
// begin writing back the specified range and the pages are marked |AwaitingClean|.
// ZX_PAGER_OP_WRITEBACK_END indicates that that user pager is done writing
// back the specified range and the pages are marked |Clean|.
//
// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |offset| and |length| specify the range to apply the |op| to, i.e. [|offset|,
// |offset| + |length|).
// For ZX_PAGER_OP_WRITEBACK_*, |data| is unused and should be 0.
zx_status_t zx_pager_op_range(zx_handle_t pager,
                              uint32_t op,
                              zx_handle_t pager_vmo,
                              uint64_t offset,
                              uint64_t length,
                              uint64_t data);

如果是 ZX_PAGER_OP_WRITEBACK_BEGIN,則可視需要將 data 設為 ZX_VMO_DIRTY_RANGE_IS_ZERO,表示呼叫端想寫回指定範圍為零。此用途適用於呼叫端處理 zx_pager_query_dirty_ranges() 傳回的範圍,且 options 設為 ZX_VMO_DIRTY_RANGE_IS_ZERO。這麼做可確保在查詢之後建立、但寫回啟動前建立的任何非零內容不會遺失,因為會誤以為仍然是零,並標示為清理 (因此可收回)。

調整 VMO 大小

呼叫器支援的 VMO 與匿名 (非頁面式的 VMO) 不同,這是因為 VMO 中沒有內容的處理方式。匿名 VMO 會將初始內容隱含為零,因此未提交的頁面暗示零。這類 VMO 也是如此,因為未修訂的頁面不會暗示是零;僅表示呼叫器尚未為這些網頁提供內容。但是,當頁面大小調整到較大尺寸時,分頁器「無法」在新的延伸範圍中提供頁面,這是因為其內容還不存在於備份來源 (例如儲存空間磁碟) 上,所以無法另做頁面。核心可在這個新的延伸範圍內提供頁面為零,而不會諮詢使用者呼叫器。

調整大小時,系統會追蹤橫跨新調整範圍的零間隔,讓核心間接提供零頁面。使用者呼叫器尚未得知這個零間隔,因此當使用者呼叫骯髒範圍時,這個範圍就會回報為骯髒。此外,zx_vmo_dirty_range_t 中的選項欄位會針對這個範圍設為 ZX_VMO_DIRTY_RANGE_IS_ZERO,表示為全零。

如果使用 ZX_VMO_TRAP_DIRTY 旗標建立 VMO,並且將頁面寫入這個新的擴充範圍,核心會在提交前產生 ZX_PAGER_VMO_DIRTY 呼叫器要求。這是因為檔案系統可能需要為實際 (非零) 網頁保留空間。這個模型假設檔案系統能以稀疏區域的形式,在磁碟上以有效率的方式將零表示為零,因此,只有在新延伸範圍中提交頁面時,系統才會參考檔案系統。

從頁面器卸離 VMO

zx_pager_detach_vmo() 會將 ZX_PAGER_COMPLETE 封包排入佇列,表示使用者呼叫器日後不應再針對該 VMO 提出分頁要求。這也表示使用者呼叫器應查詢並寫回任何未解決的骯髒頁面。請注意,除非骯髒頁面被寫回,否則不會封鎖卸離;只會通知使用者呼叫器可能需要清除。

卸離後,需要產生 Pager 要求的 zx_vmo_read() / zx_vmo_write() 會失敗並顯示 ZX_ERR_BAD_STATE。在進行讀取和寫入時,如果對應也會有必要的呼叫器要求,則會產生嚴重的頁面錯誤例外狀況。核心可以自由捨棄 VMO 中的乾淨頁面。但是,核心會保留在骯髒頁面,直到使用者呼叫器清理為止。也就是說,即使 VMO 已卸離,ZX_PAGER_OP_WRITEBACK_BEGINZX_PAGER_OP_WRITEBACK_END 仍會繼續支援。所有其他作業上的 zx_pager_op_range() 作業和 zx_pager_supply_pages() 會在卸離的 vmo 上執行 ZX_ERR_BAD_STATE 失敗。

如果分頁器在相關 VMO 中因為有問題的頁面而刪除,則無論是否有任何未解決的寫回要求,核心都能在該時間點移除這些頁面。換句話說,只要是頁面程式清理到清理畫面,骯髒頁面就保證會保存在記憶體中。

查詢呼叫器 VMO 統計資料

核心也會追蹤 VMO 是否已修改,且使用者呼叫器可查詢該 VM。能讓使用者呼叫器用來追蹤 mtime

// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |options| can be ZX_PAGER_RESET_VMO_STATS to reset the queried stats.
// |buffer| points to a struct of type |zx_pager_vmo_stats_t|.
// |buffer_size| is the size of the buffer and should be large enough to
// accommodate |zx_pager_vmo_stats_t|.
//
// typedef struct zx_pager_vmo_stats {
//   uint32_t modified;
// } zx_pager_vmo_stats_t;
zx_status_t zx_pager_query_vmo_stats(zx_handle_t pager,
                                     zx_handle_t pager_vmo,
                                     uint32_t options,
                                     void* buffer,
                                     size_t buffer_size);

如果 VMO 經過修改,回傳 zx_pager_vmo_stats_tmodified 欄位會設為 ZX_PAGER_VMO_STATS_MODIFIED,否則設為 0。日後,您可以使用使用者分頁器可能適合查詢的其他欄位來擴充 zx_pager_vmo_stats_t 結構。

系統呼叫 modified 狀態會在修改 VMO (例如 zx_vmo_write()zx_vmo_set_size()),以及第一次透過對應發生寫入頁面錯誤時更新。已追蹤網頁上的第一個寫入錯誤,以正確管理 CleanDirty 轉換作業,因此隨後更新 modified 狀態。但系統並不會追蹤日後透過骯髒頁面對應的寫入作業,這會導致寫入對應 VMO 的速度大幅減慢。因此,對應 VMO 的 modified 狀態可能不完全準確。

如果使用者呼叫器也想重設查詢的統計資料,則 options 可以是 ZX_PAGER_RESET_VMO_STATSoptions 值 0 不會重設任何狀態,並會執行純查詢。請注意,如果指定了 ZX_PAGER_RESET_VMO_STATS 選項,這項呼叫就能使用可查詢狀態,對未來的 zx_pager_query_vmo_stats() 呼叫產生影響。舉例來說,假設 zx_vmo_write() 後面接著具有 ZX_PAGER_RESET_VMO_STATS 選項的兩個連續 zx_pager_query_vmo_stats() 呼叫,則只有第一個呼叫會看到 modified 組合。由於在第一個 zx_pager_query_vmo_stats() 之後沒有進一步修改,第二個 zx_pager_query_vmo_stats() 會傳回 modified 為 0。

實作

Pager 寫入功能一直在開發一段時間,@next vDSO 中會提供所有新的 API 元件。fxfs 已經採用寫回 API,以支援串流讀取/寫入和 mmap

效能

有了 Pager 寫回功能,fxfs 得以從執行 I/O 管道改為使用串流,結果各種基準的效能提升約 40 至 60 倍。

安全性考量

無。

隱私權注意事項

無。

測試

核心核心測試和壓力測試已撰寫以執行呼叫器系統呼叫。另外還提供儲存空間測試和效能基準。

說明文件

核心 syscall 說明文件已更新。

缺點、替代方案和未知

頻率限制回傳要求頻率

在日後的疊代中,當核心產生寫回要求時 (記憶體壓力或背景速率穩定),我們需要一些政策,以控制在呼叫器通訊埠排入佇列的寫入要求數量。其中一個做法是讓核心追蹤處理中的待處理要求數量,並嘗試讓要求保持在特定限制範圍內。

另一個選項是使用者呼叫器設定可調整項,以直接或間接決定回傳要求的產生率。例如,使用者呼叫器可以指定將網頁排入佇列之前的建議時間長度,或是使用者呼叫器可支援寫入的一般資料傳輸速率。某些檔案系統可能需要背景寫回率遠高於全域系統預設值。對使用者呼叫器指定處理要求的精細程度 (多個系統頁面大小) 也可能有幫助。核心可能會在計算範圍時將這點納入考量,整體而言,產生的要求可能會減少。

追蹤和查詢網頁年齡資訊

進行初始實作時,核心會追蹤網頁佇列中的骯髒頁面,並依頁面首次清除時排序。日後可使用這個佇列產生寫回要求,而且頁面清理完成後,即可從骯髒佇列中移至 (乾淨的) Pager 支援佇列,該佇列目前會追蹤讀取和存在時間的唯讀頁面。我們可能也想要進一步追蹤骯髒網頁的年齡,將骯髒的網頁與乾淨的頁面整合成通用集區,善用老化和存取的位元追蹤機制,善用全域工作組。我們可能也想要透過新的 API,向使用者呼叫器公開這項年齡資訊,以便在處理寫入要求時納入考量。

在寫回期間封鎖進一步寫入

此處提出的設計不會封鎖在執行寫回期間 (即 ZX_PAGER_OP_WRITEBACK_BEGINZX_PAGER_OP_WRITEBACK_END 之間) 傳入的新寫入。而寫入的網頁 只會再次標示為骯髒的網頁某些檔案系統可能會在網頁處於 AwaitingClean 狀態時,封鎖寫入作業。我們日後可以考慮新增 ZX_PAGER_OP_WRITEBACK_BEING_SYNC,封鎖寫入期間的寫入作業。請注意,ZX_VMO_TRAP_DIRTY v1 提供透過 ZX_PAGER_VMO_DIRTY 呼叫器要求解決此情況的方法,這種要求檔案系統可在清除作業期間讓處理不中斷。

先前的圖片和參考資料