檢查程序頁表

pagemap 是核心中一套新(自 2.6.25 版本起)的介面,它允許使用者空間程式透過讀取 /proc 中的檔案來檢查頁表及相關資訊。

pagemap 有四個組成部分

  • /proc/pid/pagemap。此檔案允許使用者空間程序找出每個虛擬頁對映到的物理幀。它為每個虛擬頁包含一個 64 位值,包含以下資料(來自 fs/proc/task_mmu.c,在 pagemap_read 之上)

    • 位 0-54 頁幀號 (PFN)(如果存在)

    • 位 0-4 交換型別(如果已交換)

    • 位 5-54 交換偏移量(如果已交換)

    • 位 55 pte 是軟髒的(請參閱 軟髒 PTE

    • 位 56 頁被獨佔對映(自 4.2 版本起)

    • 位 57 pte 受 uffd-wp 防寫(自 5.13 版本起)(請參閱 Userfaultfd

    • 位 58 pte 是一個保護區域(自 6.15 版本起)(請參閱 madvise (2) 手冊頁)

    • 位 59-60 零

    • 位 61 頁是檔案頁或共享匿名頁(自 3.5 版本起)

    • 位 62 頁已交換

    • 位 63 頁存在

    自 Linux 4.0 版本起,只有具有 CAP_SYS_ADMIN 能力的使用者才能獲取 PFN。在 4.0 和 4.1 版本中,非特權使用者的開啟操作會因 -EPERM 而失敗。從 4.2 版本開始,如果使用者不具有 CAP_SYS_ADMIN,則 PFN 欄位將被清零。原因:關於 PFN 的資訊有助於利用 Rowhammer 漏洞。

    如果頁不存在但處於交換狀態,則 PFN 包含交換檔案號和頁在交換空間中的偏移量的編碼。未對映的頁返回空 PFN。這允許精確地確定哪些頁已對映(或處於交換狀態)並比較程序之間對映的頁。

    傳統上,位 56 表示頁只被精確對映一次,當頁被多次對映時(即使在同一程序中被多次對映),位 56 會被清除。在某些核心配置中,作為較大分配(例如 THP)一部分的頁的語義可能會有所不同:如果相應較大分配的所有頁都確定地對映在同一程序中,即使該頁在該程序中被多次對映,位 56 也會被設定。當較大分配的任何頁可能對映在不同程序中時,位 56 會被清除。在某些情況下,即使不再是這種情況,較大的分配也可能被視為“可能被多個程序對映”。

    此介面的高效使用者將使用 /proc/pid/maps 來確定記憶體的哪些區域實際已對映,並使用 llseek 跳過未對映的區域。

  • /proc/kpagecount。此檔案包含一個 64 位計數,表示每個頁被對映的次數,並按 PFN 索引。某些核心配置不跟蹤作為較大分配(例如 THP)一部分的頁被對映的精確次數。在這些配置中,將返回此較大分配中每個頁的平均對映次數。但是,如果較大分配的任何頁已對映,則返回的值將至少為 1。

tools/mm 目錄中的 page-types 工具可用於查詢頁被對映的次數。

  • /proc/kpageflags。此檔案包含一個 64 位標誌集,用於每個頁,並按 PFN 索引。

    這些標誌是(來自 fs/proc/page.c,在 kpageflags_read 之上)

    1. LOCKED(鎖定)

    2. ERROR(錯誤)

    3. REFERENCED(已引用)

    4. UPTODATE(最新)

    5. DIRTY(髒)

    6. LRU

    7. ACTIVE(活躍)

    8. SLAB

    9. WRITEBACK(回寫)

    10. RECLAIM(回收)

    11. BUDDY

    12. MMAP(記憶體對映)

    13. ANON(匿名)

    14. SWAPCACHE(交換快取)

    15. SWAPBACKED(交換支援)

    16. COMPOUND_HEAD(複合頁頭)

    17. COMPOUND_TAIL(複合頁尾)

    18. HUGE(巨頁)

    19. UNEVICTABLE(不可回收)

    20. HWPOISON(硬體中毒)

    21. NOPAGE(無頁)

    22. KSM

    23. THP

    24. OFFLINE(離線)

    25. ZERO_PAGE(零頁)

    26. IDLE(空閒)

    27. PGTABLE(頁表)

  • /proc/kpagecgroup。此檔案包含一個 64 位 inode 號,表示每個頁所屬的記憶體 cgroup,並按 PFN 索引。僅當 CONFIG_MEMCG 設定時可用。

頁標誌的簡短描述

0 - LOCKED(鎖定)

頁正在被鎖定以進行獨佔訪問,例如透過讀/寫 IO。

7 - SLAB

該頁由 SLAB/SLUB 核心記憶體分配器管理。當使用複合頁時,兩者都只在頭部頁上設定此標誌。

10 - BUDDY

由夥伴系統分配器管理的空閒記憶體塊。夥伴系統以不同階次的塊組織空閒記憶體。一個 N 階塊具有 2^N 個物理連續頁,BUDDY 標誌僅為第一頁設定。

15 - COMPOUND_HEAD(複合頁頭)

一個 N 階複合頁由 2^N 個物理連續頁組成。一個 2 階複合頁的形式為“HTTT”,其中 H 表示其頭部頁,T 表示其尾部頁。複合頁的主要消費者是 hugeTLB 頁(HugeTLB 頁)、SLUB 等記憶體分配器和各種裝置驅動程式。然而,在此介面中,只有巨頁/千兆頁對終端使用者可見。

16 - COMPOUND_TAIL(複合頁尾)

複合頁尾(見上述描述)。

17 - HUGE(巨頁)

這是 HugeTLB 頁的組成部分。

19 - HWPOISON(硬體中毒)

硬體檢測到此頁上的記憶體損壞:請勿觸碰資料!

20 - NOPAGE(無頁)

請求的地址處不存在頁幀。

21 - KSM

一個或多個程序之間動態共享的相同記憶體頁。

22 - THP

構造任意大小 THP 並以任意粒度對映的連續頁。

23 - OFFLINE(離線)

該頁邏輯上處於離線狀態。

24 - ZERO_PAGE(零頁)

pfn_zero 或 huge_zero 頁的零頁。

25 - IDLE(空閒)

該頁自被標記為空閒以來未被訪問(請參閱 空閒頁跟蹤)。請注意,如果該頁透過 PTE 訪問,則此標誌可能已過時。為確保標誌最新,必須首先讀取 /sys/kernel/mm/page_idle/bitmap

26 - PGTABLE(頁表)

該頁正在用作頁表。

共享記憶體的例外情況

當共享頁被清理或換出時,其頁表條目會被清除。這使得換出頁與從未分配的頁無法區分。

在核心空間中,交換位置仍然可以從頁快取中檢索。然而,僅儲存在普通 PTE 上的值在頁換出時會不可恢復地丟失(即 SOFT_DIRTY)。

在使用者空間中,可以藉助 lseek 和/或 mincore 系統呼叫來推斷頁是存在、已交換還是不存在。

lseek() 可以透過在頁所支援的檔案上指定 SEEK_DATA 標誌來區分已訪問的頁(存在或已換出)和空洞(無/未分配)。對於匿名共享頁,可以在 /proc/pid/map_files/ 中找到檔案。

mincore() 可以區分記憶體中的頁(存在,包括交換快取)和記憶體外的頁(已換出或無/未分配)。

其他注意事項

如果您沒有在 8 位元組邊界上開始讀取(例如,如果您在檔案中查找了奇數個位元組),或者如果讀取的大小不是 8 位元組的倍數,則從任何檔案中讀取都將返回 -EINVAL。

在 Linux 3.11 之前,pagemap 的位 55-60 用於“頁移位”(在大多數架構中始終為 12)。自 Linux 3.11 以來,在首次清除軟髒位後,它們的含義發生變化。自 Linux 4.2 以來,它們無條件地用於標誌。

Pagemap 掃描 IOCTL

pagemap 檔案上的 PAGEMAP_SCAN IOCTL 可用於獲取或可選地清除有關頁表條目的資訊。此 IOCTL 支援以下操作:

  • 掃描地址範圍並獲取與所提供條件匹配的記憶體範圍。當指定輸出緩衝區時執行此操作。

  • 防寫頁。PM_SCAN_WP_MATCHING 用於防寫感興趣的頁。如果發現非非同步防寫頁,PM_SCAN_CHECK_WPASYNC 將中止操作。PM_SCAN_WP_MATCHING 可以與 PM_SCAN_CHECK_WPASYNC 一起使用,也可以不使用。

  • 這兩個操作可以組合成一個原子操作,我們可以在其中獲取並防寫頁。

目前支援以下頁標誌:

  • PAGE_IS_WPALLOWED - 頁已啟用非同步防寫

  • PAGE_IS_WRITTEN - 頁自被防寫以來已被寫入

  • PAGE_IS_FILE - 頁由檔案支援

  • PAGE_IS_PRESENT - 頁存在於記憶體中

  • PAGE_IS_SWAPPED - 頁已交換

  • PAGE_IS_PFNZERO - 頁具有零 PFN

  • PAGE_IS_HUGE - 頁是 PMD 對映的 THP 或 HugeTLB 支援的

  • PAGE_IS_SOFT_DIRTY - 頁是軟髒的

  • PAGE_IS_GUARD - 頁是保護區域的一部分

struct pm_scan_arg 用作 IOCTL 的引數。

  1. struct pm_scan_arg 的大小必須在 size 欄位中指定。如果以後進行擴充套件,此欄位將有助於識別結構。

  2. 標誌可以在 flags 欄位中指定。目前只有 PM_SCAN_WP_MATCHINGPM_SCAN_CHECK_WPASYNC 是新增的標誌。獲取操作是可選執行的,取決於是否提供了輸出緩衝區。

  3. 範圍透過 startend 指定。

  4. 遍歷可能會在訪問完整範圍之前中止,例如使用者緩衝區可能已滿等。遍歷結束地址在 end_walk 中指定。

  5. struct page_region 陣列的輸出緩衝區及其大小在 vecvec_len 中指定。

  6. 可選的最大請求頁數在 max_pages 中指定。

  7. 掩碼在 category_maskcategory_anyof_maskcategory_invertedreturn_mask 中指定。

查詢已被寫入的頁並將其防寫

struct pm_scan_arg arg = {
.size = sizeof(arg),
.flags = PM_SCAN_CHECK_WPASYNC | PM_SCAN_CHECK_WPASYNC,
..
.category_mask = PAGE_IS_WRITTEN,
.return_mask = PAGE_IS_WRITTEN,
};

查詢已被寫入、由檔案支援、未交換且存在或為巨頁的頁

struct pm_scan_arg arg = {
.size = sizeof(arg),
.flags = 0,
..
.category_mask = PAGE_IS_WRITTEN | PAGE_IS_SWAPPED,
.category_inverted = PAGE_IS_SWAPPED,
.category_anyof_mask = PAGE_IS_PRESENT | PAGE_IS_HUGE,
.return_mask = PAGE_IS_WRITTEN | PAGE_IS_SWAPPED |
               PAGE_IS_PRESENT | PAGE_IS_HUGE,
};

PAGE_IS_WRITTEN 標誌可以被認為是 soft-dirty 標誌的一個性能更好的替代方案。它不受核心 VMA 合併的影響,因此使用者可以在普通頁的情況下找到真正的軟髒頁。(對於 THP 或 Hugetlb 頁,仍可能報告額外的髒頁。)

“PAGE_IS_WRITTEN” 類別與啟用 uffd 防寫的範圍一起使用,以在使用者空間中實現記憶體髒頁跟蹤

  1. userfaultfd 檔案描述符透過 userfaultfd 系統呼叫建立。

  2. UFFD_FEATURE_WP_UNPOPULATEDUFFD_FEATURE_WP_ASYNC 功能透過 UFFDIO_API IOCTL 設定。

  3. 記憶體範圍透過 UFFDIO_REGISTER IOCTL 以 UFFDIO_REGISTER_MODE_WP 模式註冊。

  4. 然後,註冊記憶體的任何部分或整個記憶體區域必須使用帶有 PM_SCAN_WP_MATCHING 標誌的 PAGEMAP_SCAN IOCTL 或 UFFDIO_WRITEPROTECT IOCTL 進行防寫。兩者執行相同的操作。前者在效能方面更好。

  5. 現在,PAGEMAP_SCAN IOCTL 可以用於查詢自上次標記以來已被寫入的頁,和/或可選地防寫這些頁。