diff options
| author | Kiryl Shutsemau (Meta) <kas@kernel.org> | 2026-07-08 12:14:12 +0100 |
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2026-07-26 22:49:18 -0700 |
| commit | 84a956c1b15071b222305b561b38a2abcf07e9a9 (patch) | |
| tree | 04bc7cd9b6db8d04c8f81a137e6c8c8806a6ff67 /fs | |
| parent | bd1397efbf7662b164155f6a3305036663ce6e25 (diff) | |
| download | linux-next-84a956c1b15071b222305b561b38a2abcf07e9a9.tar.gz linux-next-84a956c1b15071b222305b561b38a2abcf07e9a9.zip | |
mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking
PAGEMAP_SCAN already reports PAGE_IS_WRITTEN from the inverted uffd PTE
bit, targeting the UFFDIO_WRITEPROTECT workflow. UFFDIO_RWPROTECT reuses
the same PTE bit as a marker for read-write protection, but "has been
written" and "has been accessed" are distinct semantic signals — they
happen to share one PTE bit today only because the two implementations
share infrastructure.
Give RWP its own pagemap category so the UAPI does not conflate them:
PAGE_IS_WRITTEN reported on VM_UFFD_WP VMAs, !pte_uffd(pte)
PAGE_IS_ACCESSED reported on VM_UFFD_RWP VMAs, !pte_uffd(pte)
Both still read the same PTE bit today, but each is scoped to the VMA
whose registered mode makes the bit meaningful. If a future
implementation moves RWP to a separate PTE bit, only PAGE_IS_ACCESSED
switches over.
This is a UAPI narrowing. Outside VM_UFFD_WP VMAs the uffd bit is always
clear, so PAGEMAP_SCAN used to flag PAGE_IS_WRITTEN on every present PTE
there — a meaningless duplicate of PAGE_IS_PRESENT. Now PAGE_IS_WRITTEN
fires only inside VM_UFFD_WP VMAs.
pagemap_hugetlb_category() now takes the vma like its PTE/PMD peers.
Link: https://lore.kernel.org/20260708111417.173443-12-kirill@shutemov.name
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: David Hildenbrand <david@kernel.org>
Cc: James Houghton <jthoughton@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Sean Christopherson <seanjc@google.com>
Cc: SeongJae Park <sj@kernel.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'fs')
| -rw-r--r-- | fs/proc/task_mmu.c | 81 |
1 files changed, 55 insertions, 26 deletions
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 407d8dde2a90..817e3e0f9194 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -2284,7 +2284,7 @@ static const struct mm_walk_ops pagemap_ops = { * Bits 5-54 swap offset if swapped * Bit 55 pte is soft-dirty (see Documentation/admin-guide/mm/soft-dirty.rst) * Bit 56 page exclusively mapped - * Bit 57 pte is uffd-wp write-protected + * Bit 57 pte is tracked by userfaultfd (uffd-wp or RWP) * Bit 58 pte is a guard region * Bits 59-60 zero * Bit 61 page is file-page or shared-anon @@ -2419,7 +2419,7 @@ static int pagemap_release(struct inode *inode, struct file *file) PAGE_IS_FILE | PAGE_IS_PRESENT | \ PAGE_IS_SWAPPED | PAGE_IS_PFNZERO | \ PAGE_IS_HUGE | PAGE_IS_SOFT_DIRTY | \ - PAGE_IS_GUARD) + PAGE_IS_GUARD | PAGE_IS_ACCESSED) #define PM_SCAN_FLAGS (PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC) struct pagemap_scan_private { @@ -2438,15 +2438,17 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, if (pte_none(pte)) { /* - * An unpopulated pte carries no uffd-wp marker, i.e. it is not - * write-protected, the same condition under which the present - * and swap cases below report PAGE_IS_WRITTEN. Report it here - * too so this generic path agrees with the PAGE_IS_WRITTEN fast - * path in pagemap_scan_pmd_entry(), which reports pte_none as - * written and, under PM_SCAN_WP_MATCHING, arms a marker. The - * fast path applies no VMA test, so neither does this. + * An unpopulated pte carries no uffd bit, i.e. it is not + * write-protected. The PAGE_IS_WRITTEN fast path in + * pagemap_scan_pmd_entry() is now gated on a VM_UFFD_WP VMA; + * gate the pte_none report here the same way so the two paths + * still agree. RWP has no such fast path and an unpopulated + * page is not part of the RWP working set, so it is reported as + * neither. */ - return PAGE_IS_WRITTEN; + if (userfaultfd_wp(vma)) + return PAGE_IS_WRITTEN; + return 0; } if (pte_present(pte)) { @@ -2454,8 +2456,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, categories = PAGE_IS_PRESENT; - if (!pte_uffd(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_uffd(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (p->masks_of_interest & PAGE_IS_FILE) { page = vm_normal_page(vma, addr, pte); @@ -2472,8 +2478,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, categories = PAGE_IS_SWAPPED; - if (!pte_swp_uffd_any(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_swp_uffd_any(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } entry = softleaf_from_pte(pte); if (softleaf_is_guard_marker(entry)) @@ -2522,8 +2532,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p, struct page *page; categories |= PAGE_IS_PRESENT; - if (!pmd_uffd(pmd)) - categories |= PAGE_IS_WRITTEN; + if (!pmd_uffd(pmd)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (p->masks_of_interest & PAGE_IS_FILE) { page = vm_normal_page_pmd(vma, addr, pmd); @@ -2537,8 +2551,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p, categories |= PAGE_IS_SOFT_DIRTY; } else { categories |= PAGE_IS_SWAPPED; - if (!pmd_swp_uffd(pmd)) - categories |= PAGE_IS_WRITTEN; + if (!pmd_swp_uffd(pmd)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (pmd_swp_soft_dirty(pmd)) categories |= PAGE_IS_SOFT_DIRTY; @@ -2571,7 +2589,8 @@ static void make_uffd_wp_pmd(struct vm_area_struct *vma, #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ #ifdef CONFIG_HUGETLB_PAGE -static unsigned long pagemap_hugetlb_category(pte_t pte) +static unsigned long pagemap_hugetlb_category(struct vm_area_struct *vma, + pte_t pte) { unsigned long categories = PAGE_IS_HUGE; @@ -2586,8 +2605,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte) if (pte_present(pte)) { categories |= PAGE_IS_PRESENT; - if (!huge_pte_uffd(pte)) - categories |= PAGE_IS_WRITTEN; + if (!huge_pte_uffd(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (!PageAnon(pte_page(pte))) categories |= PAGE_IS_FILE; if (is_zero_pfn(pte_pfn(pte))) @@ -2597,8 +2620,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte) } else { categories |= PAGE_IS_SWAPPED; - if (!pte_swp_uffd_any(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_swp_uffd_any(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (pte_swp_soft_dirty(pte)) categories |= PAGE_IS_SOFT_DIRTY; } @@ -2874,7 +2901,8 @@ static int pagemap_scan_pmd_entry(pmd_t *pmd, unsigned long start, goto flush_and_return; } - if (!p->arg.category_anyof_mask && !p->arg.category_inverted && + if (userfaultfd_wp(vma) && !p->arg.category_anyof_mask && + !p->arg.category_inverted && p->arg.category_mask == PAGE_IS_WRITTEN && p->arg.return_mask == PAGE_IS_WRITTEN) { for (addr = start; addr < end; pte++, addr += PAGE_SIZE) { @@ -2949,7 +2977,8 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask, /* Go the short route when not write-protecting pages. */ pte = huge_ptep_get(walk->mm, start, ptep); - categories = p->cur_vma_category | pagemap_hugetlb_category(pte); + categories = p->cur_vma_category | + pagemap_hugetlb_category(vma, pte); if (!pagemap_scan_is_interesting_page(categories, p)) return 0; @@ -2961,7 +2990,7 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask, ptl = huge_pte_lock(hstate_vma(vma), vma->vm_mm, ptep); pte = huge_ptep_get(walk->mm, start, ptep); - categories = p->cur_vma_category | pagemap_hugetlb_category(pte); + categories = p->cur_vma_category | pagemap_hugetlb_category(vma, pte); if (!pagemap_scan_is_interesting_page(categories, p)) goto out_unlock; |
