英語

何時需要在頁表鎖內進行通知?

當清除 pte/pmd 時,我們可以選擇透過(*_clear_flush 的通知版本呼叫 mmu_notifier_invalidate_range)在頁表鎖下通知該事件。 但並非在所有情況下都必須進行通知。

對於輔助 TLB(非 CPU TLB),如 IOMMU TLB 或裝置 TLB(當裝置使用諸如 ATS/PASID 之類的東西來使 IOMMU 遍歷 CPU 頁表以訪問程序虛擬地址空間時)。 只有在以下兩種情況下需要在持有頁表鎖的同時通知這些輔助 TLB 以清除 pte/pmd

  1. 頁面後備地址在 mmu_notifier_invalidate_range_end() 之前被釋放

  2. 頁表條目被更新以指向新頁面(COW,零頁上的寫入錯誤,__replace_page(),...)

情況 A 很明顯,您不希望裝置冒著寫入可能現在被某些完全不同的任務使用的頁面的風險。

情況 B 更加微妙。 對於正確性,它需要發生以下順序

  • 獲取頁表鎖

  • 清除頁表條目並通知 ([pmd/pte]p_huge_clear_flush_notify())

  • 設定頁表條目以指向新頁面

如果在設定新的 pte/pmd 值之前清除頁表條目後未進行通知,則可能會破壞裝置的記憶體模型,如 C11 或 C++11。

考慮以下場景(裝置使用類似於 ATS/PASID 的功能)

兩個地址 addrA 和 addrB 使得 |addrA - addrB| >= PAGE_SIZE,我們假設它們受到寫入保護以進行 COW(B 的其他情況也適用)。

[Time N] --------------------------------------------------------------------
CPU-thread-0  {try to write to addrA}
CPU-thread-1  {try to write to addrB}
CPU-thread-2  {}
CPU-thread-3  {}
DEV-thread-0  {read addrA and populate device TLB}
DEV-thread-2  {read addrB and populate device TLB}
[Time N+1] ------------------------------------------------------------------
CPU-thread-0  {COW_step0: {mmu_notifier_invalidate_range_start(addrA)}}
CPU-thread-1  {COW_step0: {mmu_notifier_invalidate_range_start(addrB)}}
CPU-thread-2  {}
CPU-thread-3  {}
DEV-thread-0  {}
DEV-thread-2  {}
[Time N+2] ------------------------------------------------------------------
CPU-thread-0  {COW_step1: {update page table to point to new page for addrA}}
CPU-thread-1  {COW_step1: {update page table to point to new page for addrB}}
CPU-thread-2  {}
CPU-thread-3  {}
DEV-thread-0  {}
DEV-thread-2  {}
[Time N+3] ------------------------------------------------------------------
CPU-thread-0  {preempted}
CPU-thread-1  {preempted}
CPU-thread-2  {write to addrA which is a write to new page}
CPU-thread-3  {}
DEV-thread-0  {}
DEV-thread-2  {}
[Time N+3] ------------------------------------------------------------------
CPU-thread-0  {preempted}
CPU-thread-1  {preempted}
CPU-thread-2  {}
CPU-thread-3  {write to addrB which is a write to new page}
DEV-thread-0  {}
DEV-thread-2  {}
[Time N+4] ------------------------------------------------------------------
CPU-thread-0  {preempted}
CPU-thread-1  {COW_step3: {mmu_notifier_invalidate_range_end(addrB)}}
CPU-thread-2  {}
CPU-thread-3  {}
DEV-thread-0  {}
DEV-thread-2  {}
[Time N+5] ------------------------------------------------------------------
CPU-thread-0  {preempted}
CPU-thread-1  {}
CPU-thread-2  {}
CPU-thread-3  {}
DEV-thread-0  {read addrA from old page}
DEV-thread-2  {read addrB from new page}

因此,在這裡,因為在 N+2 時,清除頁表條目未與使輔助 TLB 無效的通知配對,所以裝置在看到 addrA 的新值之前看到了 addrB 的新值。 這破壞了裝置的總體記憶體排序。

當更改 pte 以進行防寫或指向具有相同內容(KSM)的新防寫頁面時,可以延遲 mmu_notifier_invalidate_range 呼叫到 mmu_notifier_invalidate_range_end() 到頁表鎖之外。 即使執行頁表更新的執行緒在釋放頁表鎖之後但在呼叫 mmu_notifier_invalidate_range_end() 之前被搶佔,也是如此。