From: Kiryl Shutsemau <kirill@shutemov.name>
To: "David Hildenbrand (Arm)" <david@kernel.org>
Cc: akpm@linux-foundation.org, usama.anjum@arm.com,
peterx@redhat.com, liam@infradead.org, ljs@kernel.org,
vbabka@kernel.org, jannh@google.com, pfalcato@suse.de,
rppt@kernel.org, surenb@google.com, mhocko@suse.com,
zenghui.yu@linux.dev, shuah@kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kselftest@vger.kernel.org, stable@vger.kernel.org,
kernel-team@meta.com
Subject: Re: [PATCH v5 2/2] selftests/mm: add PAGEMAP_SCAN test for THP PMD holes
Date: Thu, 16 Jul 2026 14:03:43 +0100 [thread overview]
Message-ID: <aljWYfPRCVc6IB2b@thinkstation> (raw)
In-Reply-To: <714bdcbb-92cd-4dde-820c-e3761cbbaf07@kernel.org>
On Thu, Jul 16, 2026 at 01:25:22PM +0200, David Hildenbrand (Arm) wrote:
>
> > + /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
> > + ret = pagemap_ioctl(mem, hpage_size, regions, ARRAY_SIZE(regions), 0, 0,
> > + PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
> > + for (i = 0; ret > 0 && i < ret; i++)
> > + fast += LEN(regions[i]);
> > +
> > + /* Generic path: same query expressed via category_anyof_mask. */
> > + ret = pagemap_ioctl(mem, hpage_size, regions, ARRAY_SIZE(regions), 0, 0,
> > + 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
> > + for (i = 0; ret > 0 && i < ret; i++)
> > + slow += LEN(regions[i]);
> > +
> > + ksft_test_result(fast == npages && slow == npages,
> > + "%s pmd-hole reported written by both paths (%ld, %ld of %d)\n",
> > + __func__, fast, slow, npages);
> > +out:
> > + wp_free(mem, hpage_size);
> > + munmap(area, 2 * hpage_size);
> > +}
>
> Sorry if I wasn't clear. I think unpopulated_thp_scan_test() can actually reuse
> bits of unpopulated_scan_test().
Ah. Make sense.
Here's my take on this. Looks good?
-- >8 --
Date: Thu, 16 Jul 2026 13:24:29 +0100
Subject: [PATCH v6 2/2] selftests/mm: add PAGEMAP_SCAN test for THP PMD holes
Add coverage for the PMD-hole case fixed by "fs/proc/task_mmu: fix
PAGEMAP_SCAN written state for PMD holes": a MAP_PRIVATE|MAP_ANON THP that
is uffd-wp'd and then dropped with MADV_DONTNEED leaves a pmd_none hole
with no page table, which PAGEMAP_SCAN must still report as written.
Factor the populate/drop/scan-both-paths sequence out of
unpopulated_scan_test() into a helper, and add unpopulated_thp_scan_test()
that reuses it with a THP.
Include <linux/mman.h> for MADV_COLLAPSE; <sys/mman.h> lacks it on older
glibc (e.g. 2.34). Same approach as commit fd5295afae91 ("selftests/mm:
hmm-tests: include linux/mman.h to access MADV_COLLAPSE").
Cc: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Zenghui Yu <zenghui.yu@linux.dev>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-fable-5
---
tools/testing/selftests/mm/pagemap_ioctl.c | 118 +++++++++++++++------
1 file changed, 83 insertions(+), 35 deletions(-)
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index 550d1f2c059b..693030f1e631 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -8,6 +8,7 @@
#include <errno.h>
#include <malloc.h>
#include <linux/types.h>
+#include <linux/mman.h>
#include <linux/memfd.h>
#include <linux/userfaultfd.h>
#include <linux/fs.h>
@@ -1058,50 +1059,96 @@ static void test_simple(void)
* the generic path (reached e.g. via category_anyof_mask) must report every
* page written.
*/
+/*
+ * Populate @mem (optionally collapsing it into a THP first), drop it with
+ * MADV_DONTNEED, then check PAGEMAP_SCAN reports the whole range written via
+ * both the fast and generic query paths. A dropped THP leaves a pmd_none hole
+ * with no page table, exercising pagemap_scan_pte_hole(); a base-page range
+ * leaves pte_none entries.
+ */
+static void unpopulated_written_test(const char *name, char *mem, long size,
+ bool use_thp)
+{
+ long npages = size / page_size, fast = 0, slow = 0, ret;
+ struct page_region regions[16];
+ int i;
+
+ wp_init(mem, size);
+
+ /* Populate, optionally collapse to a THP, then drop it. */
+ memset(mem, 1, size);
+ if (use_thp &&
+ (madvise(mem, size, MADV_COLLAPSE) ||
+ !check_huge_anon(mem, size / hpage_size, hpage_size))) {
+ ksft_test_result_skip("%s could not form a THP\n", name);
+ goto out;
+ }
+ if (madvise(mem, size, MADV_DONTNEED)) {
+ ksft_test_result_fail("%s MADV_DONTNEED failed\n", name);
+ goto out;
+ }
+
+ /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
+ ret = pagemap_ioctl(mem, size, regions, ARRAY_SIZE(regions), 0, 0,
+ PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
+ for (i = 0; ret > 0 && i < ret; i++)
+ fast += LEN(regions[i]);
+
+ /* Generic path: same query expressed via category_anyof_mask. */
+ ret = pagemap_ioctl(mem, size, regions, ARRAY_SIZE(regions), 0, 0,
+ 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
+ for (i = 0; ret > 0 && i < ret; i++)
+ slow += LEN(regions[i]);
+
+ ksft_test_result(fast == npages && slow == npages,
+ "%s unpopulated range reported written by both paths (%ld, %ld of %ld)\n",
+ name, fast, slow, npages);
+out:
+ wp_free(mem, size);
+}
+
static void unpopulated_scan_test(void)
{
- int npages = 16, i;
- long mem_size = npages * page_size;
- struct page_region regions[16];
- long fast = 0, slow = 0, ret;
+ long mem_size = 16 * page_size;
char *mem;
mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (mem == MAP_FAILED)
- ksft_exit_fail_msg("%s mmap failed\n", __func__);
+ if (mem == MAP_FAILED) {
+ ksft_test_result_skip("%s mmap failed\n", __func__);
+ return;
+ }
- wp_init(mem, mem_size);
-
- /* Populate, then drop: the ptes become pte_none without a marker. */
- memset(mem, 1, mem_size);
- if (madvise(mem, mem_size, MADV_DONTNEED))
- ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__);
-
- /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
- ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
- PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
- if (ret < 0)
- ksft_exit_fail_msg("%s fast scan failed\n", __func__);
- for (i = 0; i < ret; i++)
- fast += LEN(regions[i]);
-
- /* Generic path: same query expressed via category_anyof_mask. */
- ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
- 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
- if (ret < 0)
- ksft_exit_fail_msg("%s generic scan failed\n", __func__);
- for (i = 0; i < ret; i++)
- slow += LEN(regions[i]);
-
- ksft_test_result(fast == npages && slow == npages,
- "%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n",
- __func__, fast, slow, npages);
-
- wp_free(mem, mem_size);
+ unpopulated_written_test(__func__, mem, mem_size, false);
munmap(mem, mem_size);
}
+/*
+ * Same as unpopulated_scan_test(), but the range is a THP: a full-PMD
+ * MADV_DONTNEED leaves a pmd_none hole with no page table.
+ */
+static void unpopulated_thp_scan_test(void)
+{
+ char *area, *mem;
+
+ if (!hpage_size) {
+ ksft_test_result_skip("%s THP not supported\n", __func__);
+ return;
+ }
+
+ /* Over-allocate so a PMD-aligned, THP-sized range fits inside. */
+ area = mmap(NULL, 2 * hpage_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (area == MAP_FAILED) {
+ ksft_test_result_skip("%s mmap failed\n", __func__);
+ return;
+ }
+ mem = (char *)(((unsigned long)area + hpage_size - 1) & ~(hpage_size - 1));
+
+ unpopulated_written_test(__func__, mem, hpage_size, true);
+ munmap(area, 2 * hpage_size);
+}
+
int sanity_tests(void)
{
unsigned long long mem_size, vec_size;
@@ -1610,7 +1657,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
if (!hugetlb_setup_default(4))
ksft_print_msg("HugeTLB test will be skipped\n");
- ksft_set_plan(118);
+ ksft_set_plan(119);
page_size = getpagesize();
hpage_size = read_pmd_pagesize();
@@ -1790,6 +1837,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
/* 18. Unpopulated pte scan-path consistency */
unpopulated_scan_test();
+ unpopulated_thp_scan_test();
close(pagemap_fd);
ksft_finished();
--
Kiryl Shutsemau / Kirill A. Shutemov
next prev parent reply other threads:[~2026-07-16 13:03 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-15 14:42 [PATCH v5 0/2] fs/proc/task_mmu: fix PAGEMAP_SCAN written state for PMD holes Kiryl Shutsemau
2026-07-15 14:42 ` [PATCH v5 1/2] " Kiryl Shutsemau
2026-07-16 11:21 ` David Hildenbrand (Arm)
2026-07-16 12:57 ` Muhammad Usama Anjum
2026-07-15 14:42 ` [PATCH v5 2/2] selftests/mm: add PAGEMAP_SCAN test for THP " Kiryl Shutsemau
2026-07-16 11:25 ` David Hildenbrand (Arm)
2026-07-16 13:03 ` Kiryl Shutsemau [this message]
2026-07-16 14:40 ` David Hildenbrand (Arm)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aljWYfPRCVc6IB2b@thinkstation \
--to=kirill@shutemov.name \
--cc=akpm@linux-foundation.org \
--cc=david@kernel.org \
--cc=jannh@google.com \
--cc=kernel-team@meta.com \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=peterx@redhat.com \
--cc=pfalcato@suse.de \
--cc=rppt@kernel.org \
--cc=shuah@kernel.org \
--cc=stable@vger.kernel.org \
--cc=surenb@google.com \
--cc=usama.anjum@arm.com \
--cc=vbabka@kernel.org \
--cc=zenghui.yu@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.