不可驅逐 LRU 基礎設施¶
簡介¶
本文件介紹了 Linux 記憶體管理器的“不可驅逐 LRU”基礎設施,以及使用該基礎設施管理幾種型別的“不可驅逐”頁。
本文件試圖提供該機制的總體原理,以及一些驅動實現的背後設計決策的原理。後面的設計原理將在實現描述的上下文中討論。誠然,人們可以透過閱讀程式碼獲得實現細節——“它做什麼?”。我們希望下面的描述透過提供“它為什麼要這樣做?”的答案來增加價值。
不可驅逐 LRU¶
不可驅逐 LRU 設施添加了一個額外的 LRU 列表來跟蹤不可驅逐的頁面,並將這些頁面從 vmscan 中隱藏起來。這種機制基於 Red Hat 的 Larry Woodman 的一個補丁,該補丁旨在解決 Linux 中頁面回收的幾個可伸縮性問題。這些問題已在客戶站點的大記憶體 x86_64 系統上觀察到。
為了用一個例子來說明這一點,一個具有 128GB 主記憶體的非 NUMA x86_64 平臺將在單個節點中擁有超過 3200 萬個 4k 頁面。當這些頁面中的很大一部分由於任何原因都無法被驅逐時[見下文],vmscan 將花費大量時間掃描 LRU 列表,以尋找可以驅逐的少量頁面。這可能導致所有 CPU 將 100% 的時間花費在 vmscan 中,持續數小時或數天,導致系統完全無響應。
不可驅逐列表解決了以下幾類不可驅逐頁面
ramfs 擁有的頁面。
具有 noswap 掛載選項的 tmpfs 擁有的頁面。
對映到 SHM_LOCK'd 共享記憶體區域中的頁面。
對映到 VM_LOCKED [mlock()ed] VMA 中的頁面。
該基礎設施可能還能夠在未來處理其他使頁面無法被驅逐的情況,無論是根據定義還是根據情況。
不可驅逐 LRU 頁清單¶
不可驅逐 LRU 頁清單是一個謊言。它從來不是一個按 LRU 排序的列表,而是按 LRU 排序的匿名和檔案、活動和非活動頁清單的伴侶;現在它甚至不是一個頁清單。但按照熟悉的慣例,在此文件和原始碼中,我們經常將其想象為第五個 LRU 頁清單。
不可驅逐 LRU 基礎設施由一個額外的、每個節點的 LRU 列表組成,稱為“不可驅逐”列表,以及一個相關的頁標誌 PG_unevictable,用於指示該頁正在不可驅逐列表上進行管理。
PG_unevictable 標誌類似於 PG_active 標誌,並且與其互斥,因為它指示設定 PG_lru 時頁駐留在哪個 LRU 列表上。
不可驅逐 LRU 基礎設施維護不可驅逐頁,就好像它們在額外的 LRU 列表上,原因如下
我們可以“像對待系統中的其他頁一樣對待不可驅逐頁——這意味著我們可以使用相同的程式碼來操作它們,相同的程式碼來隔離它們(用於遷移等),相同的程式碼來跟蹤統計資訊,等等……”[Rik van Riel]
我們希望能夠在節點之間遷移不可驅逐頁,以進行記憶體碎片整理、工作負載管理和記憶體熱插拔。Linux 核心只能遷移它可以成功地從 LRU 列表隔離出來的頁(或“可移動”頁:此處不予考慮)。如果我們將頁維護在 LRU 類列表之外的其他地方,
folio_isolate_lru()可以檢測到它們,我們將阻止它們的遷移。
不可驅逐列表不區分檔案支援的頁和匿名、交換支援的頁。這種區分只有在頁實際上可以驅逐時才重要。
不可驅逐列表受益於 Christoph Lameter 最初提出併發布的每個節點 LRU 列表和統計資訊的“陣列化”。
記憶體控制組互動¶
不可驅逐 LRU 設施透過擴充套件 lru_list 列舉來與記憶體控制組[又名記憶體控制器;參見 記憶體資源控制器]互動。
由於每個節點 LRU 列表的“陣列化”(每個 lru_list 列舉元素一個),記憶體控制器資料結構自動獲得每個節點的不可驅逐列表。記憶體控制器跟蹤頁面進出不可驅逐列表的移動。
當記憶體控制組承受記憶體壓力時,控制器不會嘗試回收不可驅逐列表上的頁面。這會產生幾個影響
由於頁面在不可驅逐列表中被“隱藏”起來無法回收,因此回收過程可能更有效,僅處理有可能被回收的頁面。
另一方面,如果分配給控制組的頁面太多是不可驅逐的,則控制組中任務工作集的可以驅逐部分可能無法放入可用記憶體中。這可能導致控制組抖動或 OOM-kill 任務。
標記地址空間為不可驅逐¶
對於 ramfs 等設施,不得驅逐附加到地址空間的任何頁面。為了防止任何此類頁面被驅逐,提供了 AS_UNEVICTABLE 地址空間標誌,檔案系統可以使用許多包裝函式來操作它
void mapping_set_unevictable(struct address_space *mapping);將地址空間標記為完全不可驅逐。
void mapping_clear_unevictable(struct address_space *mapping);將地址空間標記為可驅逐。
int mapping_unevictable(struct address_space *mapping);查詢地址空間,如果它完全不可驅逐,則返回 true。
這些目前在核心中的三個地方使用
ramfs 在建立其 inode 時標記其 inode 的地址空間,並且此標記在 inode 的生命週期內保持不變。
SYSV SHM 標記 SHM_LOCK'd 地址空間,直到呼叫 SHM_UNLOCK。請注意,如果鎖定的頁面已交換出去,則不需要 SHM_LOCK 將其頁面調入;如果應用程式要確保它們在記憶體中,則必須手動觸控這些頁面。
i915 驅動程式標記已固定的地址空間,直到它被取消固定。i915 驅動程式標記的不可驅逐記憶體量大致是 debugfs/dri/0/i915_gem_objects 中的有界物件大小。
檢測不可驅逐頁¶
mm/internal.h 中的函式 folio_evictable() 使用上面概述的查詢函式[參見 標記地址空間為不可驅逐]來檢查 AS_UNEVICTABLE 標誌,以確定頁面是否可以驅逐。
對於在填充後被如此標記的地址空間(如 SHM 區域),鎖定操作(例如 SHM_LOCK)可以是懶惰的,不需要像 mlock() 那樣為該區域填充頁表,也不需要特別努力將 SHM_LOCK'd 區域中的任何頁面推送到不可驅逐列表。相反,如果 vmscan 在回收掃描期間遇到頁面,它將這樣做。
在解鎖操作(如 SHM_UNLOCK)中,解鎖器(例如 shmctl())必須掃描該區域中的頁面,並從不可驅逐列表中“拯救”它們,如果沒有其他條件使它們保持不可驅逐狀態。如果不可驅逐區域被銷燬,頁面也會在釋放它們的過程中從不可驅逐列表中“拯救”出來。
folio_evictable() 還會透過呼叫 folio_test_mlocked() 來檢查 mlocked 頁面,該函式在頁面被錯誤地放入 VM_LOCKED VMA 中,或者在 VM_LOCKED 的 VMA 中找到時設定。
Vmscan 對不可驅逐頁的處理¶
如果在錯誤路徑中剔除不可驅逐頁,或者在 mlock() 或 mmap() 時將其移動到不可驅逐列表,vmscan 將不會遇到頁面,直到它們再次變為可驅逐(例如透過 munlock()),並且已從不可驅逐列表中“拯救”出來。但是,在某些情況下,為了方便起見,我們可以決定將不可驅逐頁留在常規的活動/非活動 LRU 列表之一上,以便 vmscan 處理。vmscan 在所有 shrink_{active|inactive|folio}_list() 函式中檢查此類頁面,並將“剔除”它遇到的此類頁面:也就是說,它將這些頁面轉移到正在掃描的記憶體 cgroup 和節點的不可驅逐列表。
在某些情況下,頁面可能對映到 VM_LOCKED VMA 中,但頁面沒有設定 mlocked 標誌。此類頁面將一直到達 shrink_active_list() 或 shrink_folio_list(),在 vmscan 在 folio_referenced() 或 try_to_unmap() 中遍歷反向對映時會檢測到它們。當收縮器釋放頁面時,該頁面將被剔除到不可驅逐列表。
要“剔除”不可驅逐頁面,vmscan 只需使用 folio_putback_lru() 將頁面放回 LRU 列表中,這是 folio_isolate_lru() 的逆操作,在刪除頁面鎖後。由於使頁面不可驅逐的條件可能會在頁面解鎖後發生變化,因此 __pagevec_lru_add_fn() 將在將頁面放置在不可驅逐列表上之前重新檢查頁面的不可驅逐狀態。
MLOCKED 頁¶
不可驅逐頁面列表對於 mlock() 也很有用,除了 ramfs 和 SYSV SHM。請注意,mlock() 僅在 CONFIG_MMU=y 的情況下可用;在 NOMMU 的情況下,所有對映實際上都是 mlocked 的。
歷史¶
“不可驅逐 mlocked 頁面”基礎設施基於 Nick Piggin 最初在題為“mm: mlocked pages off LRU”的 RFC 補丁中釋出的工作。Nick 釋出他的補丁是為了替代 Christoph Lameter 釋出的旨在實現相同目標的補丁:從 vmscan 中隱藏 mlocked 頁面。
在 Nick 的補丁中,他使用 struct page LRU 列表連結欄位之一作為對映頁面的 VM_LOCKED VMA 的計數(Rik van Riel 在三年前也有同樣的想法)。但是,將連結欄位用於計數會阻止在 LRU 列表上管理頁面,因此 mlocked 頁面無法遷移,因為 folio_isolate_lru() 無法檢測到它們,並且 LRU 列表連結欄位對遷移子系統不可用。
Nick 透過在嘗試隔離 mlocked 頁面之前將其放回 LRU 列表上來解決此問題,從而放棄了 VM_LOCKED VMA 的計數。當 Nick 的補丁與不可驅逐 LRU 工作整合時,該計數被在解鎖時遍歷反向對映以確定是否有任何其他 VM_LOCKED VMA 仍然對映頁面的過程所取代。
但是,在 munlocking 時遍歷每個頁面的反向對映既醜陋又效率低下,並且當許多 mlocked 程序試圖退出時,可能會導致檔案 rmap 鎖上的災難性爭用。在 5.18 中,重新啟用並將 mlock_count 儲存在不可驅逐 LRU 列表連結欄位中的想法付諸實踐,而不會阻止 mlocked 頁面的遷移。這就是為什麼“不可驅逐 LRU 列表”現在不能是頁面連結串列的原因;但是無論如何都沒有使用連結串列——儘管它的尺寸是為了 meminfo 而維護的。
基本管理¶
mlocked 頁面——對映到 VM_LOCKED VMA 中的頁面——是一類不可驅逐頁面。當記憶體管理子系統“注意到”此類頁面時,該頁會被標記為 PG_mlocked 標誌。可以使用 folio_set_mlocked() 和 folio_clear_mlocked() 函式來操作此標誌。
當 PG_mlocked 頁面新增到 LRU 時,它將放置在不可驅逐列表上。記憶體管理可以在以下幾個位置“注意到”此類頁面
在 mlock()/mlock2()/mlockall() 系統呼叫處理程式中;
在使用 MAP_LOCKED 標誌 mmapping 區域時,在 mmap() 系統呼叫處理程式中;
在使用 MCL_FUTURE 標誌呼叫 mlockall() 的任務中對映一個區域;
在錯誤路徑中以及擴充套件 VM_LOCKED 堆疊段時;或
如上所述,在 vmscan:shrink_folio_list() 中,當嘗試透過
folio_referenced()或try_to_unmap()回收 VM_LOCKED VMA 中的頁面時。
在以下情況下,mlocked 頁面將變為已解鎖並從不可驅逐列表中拯救出來
在透過 munlock()/munlockall() 系統呼叫解鎖的範圍內對映;
從對映頁面的最後一個 VM_LOCKED VMA 中 munmap(),包括在任務退出時取消對映;
當頁面從 mmapped 檔案的最後一個 VM_LOCKED VMA 中截斷時;或
在 VM_LOCKED VMA 中頁面被 COW'd 之前。
mlock()/mlock2()/mlockall() 系統呼叫處理¶
mlock()、mlock2() 和 mlockall() 系統呼叫處理程式繼續對呼叫指定的範圍內的每個 VMA 執行 mlock_fixup()。在 mlockall() 的情況下,這是任務的整個活動地址空間。請注意,mlock_fixup() 用於鎖定和解鎖記憶體範圍。對已經 VM_LOCKED 的 VMA 呼叫 mlock(),或者對未 VM_LOCKED 的 VMA 呼叫 munlock(),都被視為空操作,mlock_fixup() 只需返回。
如果 VMA 通過了下面的“過濾特殊 VMA”中描述的一些過濾,mlock_fixup() 將嘗試將 VMA 與其鄰居合併,或者如果範圍未覆蓋整個 VMA,則拆分 VMA 的子集。然後,VMA 中已經存在的任何頁面都會透過 walk_page_range() 透過 mlock_pte_range() 透過 mlock_vma_pages_range() 由 mlock_folio() 標記為 mlocked。
在從系統呼叫返回之前,do_mlock() 或 mlockall() 將呼叫 __mm_populate() 以透過 get_user_pages() 調入剩餘的頁面,並在這些頁面被錯誤地調入時將其標記為 mlocked。
請注意,正在被 mlocked 的 VMA 可能會使用 PROT_NONE 進行對映。在這種情況下,get_user_pages() 將無法調入頁面。這沒關係。如果頁面最終被錯誤地調入此 VM_LOCKED VMA 中,它們將在錯誤路徑中處理——這也是 mlock2() 的 MLOCK_ONFAULT 區域的處理方式。
對於錯誤地調入 VMA 的每個 PTE(或 PMD),頁面新增 rmap 函式呼叫 mlock_vma_folio(),當 VMA 為 VM_LOCKED 時(除非它是透明大頁的一部分的 PTE 對映),該函式呼叫 mlock_folio()。或者,當它是一個新分配的匿名頁面時,folio_add_lru_vma() 呼叫 mlock_new_folio() 來代替:類似於 mlock_folio(),但可以做出更好的判斷,因為此頁面被獨佔地持有並且已知尚未在 LRU 上。
mlock_folio() 立即設定 PG_mlocked,然後將頁面放置在 CPU 的 mlock 頁面批處理中,以批處理在 lru_lock 下完成的其餘工作,方法是 __mlock_folio()。__mlock_folio() 設定 PG_unevictable,初始化 mlock_count 並將頁面移動到不可驅逐狀態(“不可驅逐 LRU”,但使用 mlock_count 代替 LRU 執行緒)。或者,如果頁面已經是 PG_lru 且 PG_unevictable 且 PG_mlocked,它只會遞增 mlock_count。
但在實踐中,這可能無法理想地工作:頁面可能尚未在 LRU 上,或者它可能已暫時與 LRU 隔離。在這種情況下,mlock_count 欄位無法觸及,但稍後當 __munlock_folio() 將頁面返回到“LRU”時,它將被設定為 0。爭用禁止 mlock_count 當時設定為 1:與其冒著無限期地將頁面滯留在不可驅逐狀態的風險,不如始終在低端出現 mlock_count 錯誤,以便在解鎖時,頁面將被拯救到可驅逐 LRU,然後如果 vmscan 在 VM_LOCKED VMA 中找到它,可能會在稍後再次鎖定。
過濾特殊 VMA¶
mlock_fixup() 過濾了幾類“特殊”VMA
設定了 VM_IO 或 VM_PFNMAP 的 VMA 將完全跳過。這些對映後面的頁面本質上是被固定的,因此我們不需要將它們標記為 mlocked。在任何情況下,大多數頁面都沒有 struct page 來標記頁面。因此,get_user_pages() 對於這些 VMA 將會失敗,因此嘗試訪問它們是沒有意義的。
對映 hugetlbfs 頁面的 VMA 已經有效地固定到記憶體中。我們既不需要也不想 mlock() 這些頁面。但是 __mm_populate() 包括 hugetlbfs 範圍,分配大頁面並填充 PTE。
具有 VM_DONTEXPAND 的 VMA 通常是核心頁面的使用者空間對映,例如 VDSO 頁面、中繼通道頁面等。這些頁面本質上是不可驅逐的,並且不在 LRU 列表上進行管理。__mm_populate() 包括這些範圍,填充 PTE(如果尚未填充)。
設定了 VM_MIXEDMAP 的 VMA 不會被標記為 VM_LOCKED,但是 __mm_populate() 包括這些範圍,填充 PTE(如果尚未填充)。
請注意,對於所有這些特殊 VMA,mlock_fixup() 不會設定 VM_LOCKED 標誌。因此,稍後在 munlock()、munmap() 或任務退出期間,我們將不必處理它們。mlock_fixup() 也不會將這些 VMA 計入任務的“locked_vm”。
munlock()/munlockall() 系統呼叫處理¶
munlock() 和 munlockall() 系統呼叫由與 mlock()、mlock2() 和 mlockall() 系統呼叫相同的 mlock_fixup() 函式處理。如果呼叫 munlock 已經解鎖的 VMA,mlock_fixup() 只需返回。由於上面討論的 VMA 過濾,VM_LOCKED 不會在任何“特殊”VMA 中設定。因此,這些 VMA 將被 munlock 忽略。
如果 VMA 是 VM_LOCKED,mlock_fixup() 將再次嘗試合併或拆分指定的範圍。然後,透過 walk_page_range() 透過 mlock_pte_range() 透過 mlock_vma_pages_range() 由 munlock_folio() 解鎖 VMA 中的所有頁面——與 mlocking VMA 範圍時使用的函式相同,VMA 的新標誌指示正在執行 munlock()。
munlock_folio() 使用 mlock pagevec 來批處理要由 __munlock_folio() 在 lru_lock 下完成的工作。__munlock_folio() 遞減頁面的 mlock_count,當該計數達到 0 時,它會清除 mlocked 標誌並清除不可驅逐標誌,從而將頁面從不可驅逐狀態移動到非活動 LRU。
但在實踐中,這可能無法理想地工作:頁面可能尚未到達“不可驅逐 LRU”,或者它可能已暫時與 LRU 隔離。在這些情況下,其 mlock_count 欄位不可用,必須假定為 0:以便頁面將被拯救到可驅逐 LRU,然後如果 vmscan 在 VM_LOCKED VMA 中找到它,可能會在稍後再次鎖定。
遷移 MLOCKED 頁¶
正在遷移的頁面已從 LRU 列表中隔離出來,並且在頁面的取消對映、更新頁面的地址空間條目以及複製內容和狀態的過程中保持鎖定,直到頁表條目被引用新頁面的條目替換。Linux 支援 mlocked 頁面和其他不可驅逐頁面的遷移。當舊頁面從最後一個 VM_LOCKED VMA 中取消對映時,PG_mlocked 會從舊頁面中清除,並在新頁面對映到位以代替 VM_LOCKED VMA 中的遷移條目時設定。如果頁面由於 mlocked 而不可驅逐,則 PG_unevictable 遵循 PG_mlocked;但如果頁面由於其他原因而不可驅逐,則 PG_unevictable 會被顯式複製。
請注意,頁面遷移可能會與同一頁面的鎖定或解鎖競爭。這主要沒有問題,因為頁面遷移需要取消對映舊頁面的所有 PTE(包括 VM_LOCKED 的 munlock),然後對映新頁面(包括 VM_LOCKED 的 mlock)。頁表鎖提供了足夠的同步。
但是,由於 mlock_vma_pages_range() 首先在 VMA 上設定 VM_LOCKED,然後在鎖定任何已存在的頁面之前,如果在 mlock_pte_range() 到達該頁面之前遷移了其中一個頁面,則它將在 mlock_count 中被計數兩次。為了防止這種情況,mlock_vma_pages_range() 暫時將 VMA 標記為 VM_IO,以便 mlock_vma_folio() 跳過它。
為了完成頁面遷移,我們稍後將舊頁面和新頁面放回 LRU 中。當遷移過程持有的引用計數釋放時,將釋放“不需要的”頁面——成功時的舊頁面,失敗時的新頁面。
壓縮 MLOCKED 頁¶
可以掃描記憶體對映以查詢可壓縮區域,預設行為是允許移動不可驅逐頁面。/proc/sys/vm/compact_unevictable_allowed 控制此行為(請參閱 /proc/sys/vm/ 的文件)。壓縮的工作主要由頁面遷移程式碼處理,並且將應用與遷移 MLOCKED 頁面中描述的工作流程相同。
MLOCKING 透明大頁¶
透明大頁由 LRU 列表上的單個條目表示。因此,我們只能使整個複合頁面不可驅逐,而不是單個子頁面。
如果使用者嘗試 mlock() 大頁的一部分,並且沒有使用者 mlock() 整個大頁,我們希望頁面的其餘部分是可回收的。
我們不能像 split_huge_page() 那樣在部分 mlock() 上拆分頁面,因為 split_huge_page() 可能會失敗,並且 syscall 的一種新的間歇性故障模式是不受歡迎的。
我們透過將 PTE-mlocked 大頁儲存在可驅逐 LRU 列表上來處理此問題:VM_LOCKED VMA 邊界上的 PMD 將拆分為 PTE 表。
這樣,大頁就可以供 vmscan 訪問。在記憶體壓力下,頁面將被拆分,屬於 VM_LOCKED VMA 的子頁面將移動到不可驅逐 LRU,其餘部分可以回收。
/proc/meminfo 的不可驅逐和 Mlocked 金額不包括透明大頁的那些僅由 VM_LOCKED VMA 中的 PTE 對映的部分。
mmap(MAP_LOCKED) 系統呼叫處理¶
除了 mlock()、mlock2() 和 mlockall() 系統呼叫之外,應用程式還可以透過向 mmap() 呼叫提供 MAP_LOCKED 標誌來請求 mlocked 記憶體區域。但是,這裡有一個重要而微妙的區別。如果範圍無法被錯誤地調入(例如,因為 mm_populate 失敗),mmap() + mlock() 將失敗並返回 ENOMEM,而 mmap(MAP_LOCKED) 不會失敗。mmap 區域仍將具有鎖定區域的屬性——頁面不會被交換出去——但是可能仍然會發生重大頁面錯誤,以將記憶體錯誤地調入。
此外,任何 mmap() 呼叫或 brk() 呼叫,透過先前使用 MCL_FUTURE 標誌呼叫 mlockall() 的任務來擴充套件堆,將導致新對映的記憶體被鎖定。在不可驅逐/mlock 更改之前,核心只是呼叫 make_pages_present() 來分配頁面並填充頁表。
要在不可驅逐/mlock 基礎設施下鎖定記憶體範圍,mmap() 處理程式和任務地址空間擴充套件函式呼叫 populate_vma_page_range(),指定 vma 和要鎖定的地址範圍。
munmap()/exit()/exec() 系統呼叫處理¶
當取消對映 mlocked 記憶體區域時,無論是透過顯式呼叫 munmap() 還是透過 exit() 或 exec() 處理的內部取消對映,如果我們要刪除對映頁面的最後一個 VM_LOCKED VMA,我們必須解鎖頁面。在不可驅逐/mlock 更改之前,mlocking 沒有以任何方式標記頁面,因此取消對映它們不需要任何處理。
對於從 VMA 中取消對映的每個 PTE(或 PMD),folio_remove_rmap_*() 呼叫 munlock_vma_folio(),當 VMA 為 VM_LOCKED 時(除非它是透明大頁的一部分的 PTE 對映),該函式呼叫 munlock_folio()。
munlock_folio() 使用 mlock pagevec 來批處理要由 __munlock_folio() 在 lru_lock 下完成的工作。__munlock_folio() 遞減頁面的 mlock_count,當該計數達到 0 時,它會清除 mlocked 標誌並清除不可驅逐標誌,從而將頁面從不可驅逐狀態移動到非活動 LRU。
但在實踐中,這可能無法理想地工作:頁面可能尚未到達“不可驅逐 LRU”,或者它可能已暫時與 LRU 隔離。在這些情況下,其 mlock_count 欄位不可用,必須假定為 0:以便頁面將被拯救到可驅逐 LRU,然後如果 vmscan 在 VM_LOCKED VMA 中找到它,可能會在稍後再次鎖定。
截斷 MLOCKED 頁¶
檔案截斷或空洞強制取消對映使用者空間中已刪除的頁面;截斷甚至會取消對映和刪除從現在被截斷的檔案頁面複製而來任何私有匿名頁面。
可以以這種方式解鎖和刪除 Mlocked 頁面:與 munmap() 類似,對於從 VMA 中取消對映的每個 PTE(或 PMD),folio_remove_rmap_*() 呼叫 munlock_vma_folio(),當 VMA 為 VM_LOCKED 時(除非它是透明大頁的一部分的 PTE 對映),該函式呼叫 munlock_folio()。
但是,如果存在競爭 munlock(),因為 mlock_vma_pages_range() 首先透過從 VMA 中清除 VM_LOCKED 來開始 munlocking,然後在解鎖所有存在的頁面之前,如果其中一個頁面在 mlock_pte_range() 到達之前被截斷或空洞刪除取消對映,它將不會被此 VMA 識別為 mlocked,並且不會從 mlock_count 中計數。在這種極少數情況下,頁面可能在完全取消對映後仍顯示為 PG_mlocked:並且留給 release_pages()(或 __page_cache_release())在釋放之前清除它並更新統計資訊(此事件在 /proc/vmstat unevictable_pgs_cleared 中計數,通常為 0)。
shrink_*_list() 中的頁面回收¶
vmscan 的 shrink_active_list() 會剔除任何明顯不可驅逐的頁面——即 !page_evictable(page) 頁面——並將它們轉移到不可驅逐列表。但是,shrink_active_list() 只會看到那些進入活動/非活動 LRU 列表的不可驅逐頁面。請注意,這些頁面沒有設定 PG_unevictable——否則它們將在不可驅逐列表上,並且 shrink_active_list() 永遠不會看到它們。
LRU 列表上這些不可驅逐頁面的一些示例包括
首次分配時放置在 LRU 列表上的 ramfs 頁面。
SHM_LOCK'd 共享記憶體頁面。shmctl(SHM_LOCK) 不會嘗試在共享記憶體區域中分配或錯誤調入頁面。這會在應用程式在 SHM_LOCK'ing 段之後第一次訪問頁面時發生。
仍然對映到 VM_LOCKED VMA 中的頁面,這些頁面應該標記為 mlocked,但是事件使 mlock_count 太低,因此它們過早地被解鎖。
vmscan 的 shrink_inactive_list() 和 shrink_folio_list() 還會將非活動列表上發現的明顯不可驅逐頁面轉移到相應的記憶體 cgroup 和節點不可驅逐列表。
rmap 的 folio_referenced_one()(透過 vmscan 的 shrink_active_list() 或 shrink_folio_list() 呼叫)和 rmap 的 try_to_unmap_one()(透過 shrink_folio_list() 呼叫)檢查仍然對映到 VM_LOCKED VMA 中的 (3) 頁面,並呼叫 mlock_vma_folio() 來更正它們。此類頁面在收縮器釋放後會被剔除到不可驅逐列表。