From nobody Sat Oct 3 12:49:17 2026 Received: from out199-9.us.a.mail.aliyun.com (out199-9.us.a.mail.aliyun.com [47.90.199.9]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F47C421249; Sat, 1 Aug 2026 06:27:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=47.90.199.9 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565628; cv=none; b=B0vZZGNWVlTGQEXlGvhvjcY8Ktusi0Ah8F6qzNBqUYBVNeab66+17HSomHn0xCZ1OvhB4PV81h+lM+v+u8H7CZZqOFpZ2cexvfLDao71ITOZXhJJGueOgNH64dqk+3tld1iqpID8/3BRuPn0y9SRfVC6YC2CDFwbnVYsvA3Rf40= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565628; c=relaxed/simple; bh=STXMLDK76LXWB0P0PpiYu+IZe/MYK1IfI8/IdKsnuw0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KDXgFQl59kJLadAeiGIrWp7xPwTd5tDATtKB3ES4ArC5ImW2Lut/f/2d3/aroNUbDQOIXkTgCrW5h7eYiWoBc0nHBY93EoPLHf4j6QSpov2Ep7jy5E+KCfJe3Kn4z+gMXnl2OZhBu9Z/lnQ7lqhXqOMX8Kv9oGLptIJeuxUje6k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com; spf=pass smtp.mailfrom=linux.alibaba.com; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b=mNXh93VX; arc=none smtp.client-ip=47.90.199.9 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b="mNXh93VX" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.alibaba.com; s=default; t=1785565610; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=pHqBnqUX4SkeXEZFjh12UVw6qYOSznOPPAAYenFC/Eg=; b=mNXh93VXz1P5toscQacdu52FeERYFgZqxSK7Pems1L3iOCe5eYzXuzJYZxX/4cwylo8p1J+NmtL4W94SSwybmda8EPeoX4q+POAbvMEcx+5Ok3zBVmVc+xPVeJSSJ7yt4bI+Q4achA70Zf7IkRrelXqvJg2In3I6nSlR58mlXCM= X-Alimail-AntiSpam: AC=PASS;BC=-1|-1;BR=01201311R171e4;CH=green;DM=||false|;DS=||;FP=0|-1|-1|-1|0|-1|-1|-1;HT=maildocker-contentspam033037026112;MF=baolin.wang@linux.alibaba.com;NM=1;PH=DS;RN=15;SR=0;TI=SMTPD_---0X89g2M._1785565609; Received: from localhost(mailfrom:baolin.wang@linux.alibaba.com fp:SMTPD_---0X89g2M._1785565609 cluster:ay36) by smtp.aliyun-inc.com; Sat, 01 Aug 2026 14:26:50 +0800 From: Baolin Wang To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org Cc: ziy@nvidia.com, liam@infradead.org, nico.pache@linux.dev, dev.jain@arm.com, ryan.roberts@arm.com, baohua@kernel.org, lance.yang@linux.dev, usama.arif@linux.dev, baolin.wang@linux.alibaba.com, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 1/4] selftests: mm: extend the check_huge() to support mTHP check Date: Sat, 1 Aug 2026 14:26:35 +0800 Message-ID: X-Mailer: git-send-email 2.43.5 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" To support checking for various sized mTHPs during mTHP collapse, extend the check_huge() function prototype to accept two new parameters specifying the address range and mTHP size, in preparation for the following patches. No functional changes. Signed-off-by: Baolin Wang Acked-by: Kiryl Shutsemau (Meta) Acked-by: Zi Yan Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) --- .../selftests/mm/folio_split_race_test.c | 2 +- tools/testing/selftests/mm/khugepaged.c | 66 ++++++++++--------- tools/testing/selftests/mm/pagemap_ioctl.c | 2 +- .../testing/selftests/mm/prctl_thp_disable.c | 2 +- tools/testing/selftests/mm/soft-dirty.c | 2 +- .../selftests/mm/split_huge_page_test.c | 14 ++-- tools/testing/selftests/mm/uffd-common.c | 4 +- tools/testing/selftests/mm/vm_util.c | 6 +- tools/testing/selftests/mm/vm_util.h | 6 +- 9 files changed, 56 insertions(+), 48 deletions(-) diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/tes= ting/selftests/mm/folio_split_race_test.c index 6329e37fff4c..45b84f7b364e 100644 --- a/tools/testing/selftests/mm/folio_split_race_test.c +++ b/tools/testing/selftests/mm/folio_split_race_test.c @@ -182,7 +182,7 @@ static uint64_t run_iteration(void) for (i =3D 0; i < TOTAL_PAGES; i++) fill_page(mmap_base, i); =20 - if (!check_huge_shmem(mmap_base, NR_PMD_PAGE, pmd_pagesize)) + if (!check_huge_shmem(mmap_base, FILE_SIZE, NR_PMD_PAGE, pmd_pagesize)) ksft_exit_fail_msg("No shmem THP is allocated\n"); =20 if (pthread_barrier_init(&ctl.barrier, NULL, NUM_READER_THREADS + 1) !=3D= 0) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selfte= sts/mm/khugepaged.c index 10e8dedcb087..f69be6be0ecd 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -51,7 +51,7 @@ struct mem_ops { void *(*setup_area)(int nr_hpages); void (*cleanup_area)(void *p, unsigned long size); void (*fault)(void *p, unsigned long start, unsigned long end); - bool (*check_huge)(void *addr, int nr_hpages); + bool (*check_huge)(void *addr, unsigned long size, int nr_hpages, unsigne= d long hpage_size); const char *name; }; =20 @@ -276,7 +276,7 @@ static void *alloc_hpage(struct mem_ops *ops) ksft_print_msg("Allocate huge page..."); if (madvise_collapse_retry(p, hpage_pmd_size)) ksft_exit_fail_perror("madvise(MADV_COLLAPSE)"); - if (!ops->check_huge(p, 1)) + if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) ksft_exit_fail_perror("madvise(MADV_COLLAPSE)"); if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE)) ksft_exit_fail_perror("madvise(MADV_HUGEPAGE)"); @@ -310,9 +310,10 @@ static void anon_fault(void *p, unsigned long start, u= nsigned long end) fill_memory(p, start, end); } =20 -static bool anon_check_huge(void *addr, int nr_hpages) +static bool anon_check_huge(void *addr, unsigned long size, + int nr_hpages, unsigned long hpage_size) { - return check_huge_anon(addr, nr_hpages, hpage_pmd_size); + return check_huge_anon(addr, size, nr_hpages, hpage_size); } =20 static void *file_setup_area_common(int nr_hpages, enum file_setup_ops set= up) @@ -412,13 +413,14 @@ static void file_fault_write(void *p, unsigned long s= tart, unsigned long end) ksft_exit_fail_perror("madvise(MADV_POPULATE_WRITE)"); } =20 -static bool file_check_huge(void *addr, int nr_hpages) +static bool file_check_huge(void *addr, unsigned long size, + int nr_hpages, unsigned long hpage_size) { switch (finfo.type) { case VMA_FILE: - return check_huge_file(addr, nr_hpages, hpage_pmd_size); + return check_huge_file(addr, size, nr_hpages, hpage_size); case VMA_SHMEM: - return check_huge_shmem(addr, nr_hpages, hpage_pmd_size); + return check_huge_shmem(addr, size, nr_hpages, hpage_size); default: exit(EXIT_FAILURE); return false; @@ -448,9 +450,10 @@ static void shmem_cleanup_area(void *p, unsigned long = size) close(finfo.fd); } =20 -static bool shmem_check_huge(void *addr, int nr_hpages) +static bool shmem_check_huge(void *addr, unsigned long size, + int nr_hpages, unsigned long hpage_size) { - return check_huge_shmem(addr, nr_hpages, hpage_pmd_size); + return check_huge_shmem(addr, size, nr_hpages, hpage_size); } =20 static struct mem_ops __anon_ops =3D { @@ -533,7 +536,7 @@ static void __madvise_collapse(const char *msg, char *p= , int nr_hpages, ret =3D madvise_collapse_retry(p, nr_hpages * hpage_pmd_size); if (((bool)ret) =3D=3D expect) fail("Fail: Bad return value"); - else if (!ops->check_huge(p, expect ? nr_hpages : 0)) + else if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, expect ? nr_hpag= es : 0, hpage_pmd_size)) fail("Fail: check_huge()"); else success("OK"); @@ -545,7 +548,7 @@ static void madvise_collapse(const char *msg, char *p, = int nr_hpages, struct mem_ops *ops, bool expect) { /* Sanity check */ - if (!ops->check_huge(p, 0)) + if (!ops->check_huge(p, nr_hpages * hpage_pmd_size, 0, hpage_pmd_size)) ksft_exit_fail_msg("Unexpected huge page\n"); __madvise_collapse(msg, p, nr_hpages, ops, expect); } @@ -554,11 +557,12 @@ static void madvise_collapse(const char *msg, char *p= , int nr_hpages, static bool wait_for_scan(const char *msg, char *p, int nr_hpages, struct mem_ops *ops) { + unsigned long size =3D nr_hpages * hpage_pmd_size; int full_scans; int timeout =3D 6; /* 3 seconds */ =20 /* Sanity check */ - if (!ops->check_huge(p, 0)) + if (!ops->check_huge(p, size, 0, hpage_pmd_size)) ksft_exit_fail_msg("Unexpected huge page\n"); =20 madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); @@ -568,7 +572,7 @@ static bool wait_for_scan(const char *msg, char *p, int= nr_hpages, =20 ksft_print_msg("%s...", msg); while (timeout--) { - if (ops->check_huge(p, nr_hpages)) + if (ops->check_huge(p, size, nr_hpages, hpage_pmd_size)) break; if (thp_read_num("khugepaged/full_scans") >=3D full_scans) break; @@ -582,6 +586,8 @@ static bool wait_for_scan(const char *msg, char *p, int= nr_hpages, static void khugepaged_collapse(const char *msg, char *p, int nr_hpages, struct mem_ops *ops, bool expect) { + unsigned long size =3D nr_hpages * hpage_pmd_size; + /* * read&write file collapse fails since khugepaged does not flush * the target dirty folios @@ -605,7 +611,7 @@ static void khugepaged_collapse(const char *msg, char *= p, int nr_hpages, if (ops !=3D &__anon_ops) ops->fault(p, 0, nr_hpages * hpage_pmd_size); =20 - if (ops->check_huge(p, expect ? nr_hpages : 0)) + if (ops->check_huge(p, size, expect ? nr_hpages : 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -634,7 +640,7 @@ static void alloc_at_fault(void) p =3D alloc_mapping(1); *p =3D 1; ksft_print_msg("Allocate huge page on fault..."); - if (check_huge_anon(p, 1, hpage_pmd_size)) + if (check_huge_anon(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -643,7 +649,7 @@ static void alloc_at_fault(void) =20 madvise(p, page_size, MADV_DONTNEED); ksft_print_msg("Split huge PMD on MADV_DONTNEED..."); - if (check_huge_anon(p, 0, hpage_pmd_size)) + if (check_huge_anon(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -815,7 +821,7 @@ static void collapse_single_pte_entry_compound(struct c= ollapse_context *c, struc madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); ksft_print_msg("Split huge page leaving single PTE mapping compound page.= .."); madvise(p + page_size, hpage_pmd_size - page_size, MADV_DONTNEED); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -836,7 +842,7 @@ static void collapse_full_of_compound(struct collapse_c= ontext *c, struct mem_ops ksft_print_msg("Split huge page leaving single PTE page table full of com= pound pages..."); madvise(p, page_size, MADV_NOHUGEPAGE); madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -858,7 +864,7 @@ static void collapse_compound_extreme(struct collapse_c= ontext *c, struct mem_ops for (i =3D 0; i < hpage_pmd_nr; i++) { madvise(BASE_ADDR, hpage_pmd_size, MADV_HUGEPAGE); ops->fault(BASE_ADDR, 0, hpage_pmd_size); - if (!ops->check_huge(BASE_ADDR, 1)) + if (!ops->check_huge(BASE_ADDR, hpage_pmd_size, 1, hpage_pmd_size)) ksft_exit_fail_msg("Failed to allocate huge page\n"); madvise(BASE_ADDR, hpage_pmd_size, MADV_NOHUGEPAGE); =20 @@ -881,7 +887,7 @@ static void collapse_compound_extreme(struct collapse_c= ontext *c, struct mem_ops =20 ops->cleanup_area(BASE_ADDR, hpage_pmd_size); ops->fault(p, 0, hpage_pmd_size); - if (!ops->check_huge(p, 1)) + if (!ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -903,7 +909,7 @@ static void collapse_fork(struct collapse_context *c, s= truct mem_ops *ops) =20 ksft_print_msg("Allocate small page..."); ops->fault(p, 0, page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -911,7 +917,7 @@ static void collapse_fork(struct collapse_context *c, s= truct mem_ops *ops) ksft_print_msg("Share small page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -929,7 +935,7 @@ static void collapse_fork(struct collapse_context *c, s= truct mem_ops *ops) exit_status =3D WEXITSTATUS(wstatus); =20 ksft_print_msg("Check if parent still has small page..."); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -947,7 +953,7 @@ static void collapse_fork_compound(struct collapse_cont= ext *c, struct mem_ops *o ksft_print_msg("Share huge page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -955,7 +961,7 @@ static void collapse_fork_compound(struct collapse_cont= ext *c, struct mem_ops *o ksft_print_msg("Split huge page PMD in child process..."); madvise(p, page_size, MADV_NOHUGEPAGE); madvise(p, hpage_pmd_size, MADV_NOHUGEPAGE); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -976,7 +982,7 @@ static void collapse_fork_compound(struct collapse_cont= ext *c, struct mem_ops *o exit_status =3D WEXITSTATUS(wstatus); =20 ksft_print_msg("Check if parent still has huge page..."); - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -995,7 +1001,7 @@ static void collapse_max_ptes_shared(struct collapse_c= ontext *c, struct mem_ops ksft_print_msg("Share huge page over fork()..."); if (!fork()) { /* Do not touch settings on child exit */ - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1003,7 +1009,7 @@ static void collapse_max_ptes_shared(struct collapse_= context *c, struct mem_ops ksft_print_msg("Trigger CoW on page %d of %d...", hpage_pmd_nr - max_ptes_shared - 1, hpage_pmd_nr); ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared - 1) * page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1016,7 +1022,7 @@ static void collapse_max_ptes_shared(struct collapse_= context *c, struct mem_ops hpage_pmd_nr - max_ptes_shared, hpage_pmd_nr); ops->fault(p, 0, (hpage_pmd_nr - max_ptes_shared) * page_size); - if (ops->check_huge(p, 0)) + if (ops->check_huge(p, hpage_pmd_size, 0, hpage_pmd_size)) success("OK"); else fail("Fail"); @@ -1034,7 +1040,7 @@ static void collapse_max_ptes_shared(struct collapse_= context *c, struct mem_ops exit_status =3D WEXITSTATUS(wstatus); =20 ksft_print_msg("Check if parent still has huge page..."); - if (ops->check_huge(p, 1)) + if (ops->check_huge(p, hpage_pmd_size, 1, hpage_pmd_size)) success("OK"); else fail("Fail"); diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/sel= ftests/mm/pagemap_ioctl.c index 1b2dffcc999b..cfd1987339c1 100644 --- a/tools/testing/selftests/mm/pagemap_ioctl.c +++ b/tools/testing/selftests/mm/pagemap_ioctl.c @@ -1085,7 +1085,7 @@ static void unpopulated_written_test(const char *name= , char *mem, long size, memset(mem, 1, size); if (use_thp && (madvise(mem, size, MADV_COLLAPSE) || - !check_huge_anon(mem, size / hpage_size, hpage_size))) { + !check_huge_anon(mem, size, size / hpage_size, hpage_size))) { ksft_test_result_skip("%s could not form a THP\n", name); goto out; } diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing= /selftests/mm/prctl_thp_disable.c index d8d9d1de57b8..82c6e96ea6eb 100644 --- a/tools/testing/selftests/mm/prctl_thp_disable.c +++ b/tools/testing/selftests/mm/prctl_thp_disable.c @@ -67,7 +67,7 @@ static int test_mmap_thp(enum thp_collapse_type madvise_b= uf, size_t pmdsize) /* HACK: make sure we have a separate VMA that we can check reliably. */ mprotect(mem, pmdsize, PROT_READ); =20 - ret =3D check_huge_anon(mem, 1, pmdsize); + ret =3D check_huge_anon(mem, pmdsize, 1, pmdsize); munmap(mmap_mem, mmap_size); return ret; } diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selfte= sts/mm/soft-dirty.c index fb1864a68e1c..e198facf78bb 100644 --- a/tools/testing/selftests/mm/soft-dirty.c +++ b/tools/testing/selftests/mm/soft-dirty.c @@ -103,7 +103,7 @@ static void test_hugepage(int pagemap_fd, int pagesize) for (i =3D 0; i < hpage_len; i++) map[i] =3D (char)i; =20 - if (check_huge_anon(map, 1, hpage_len)) { + if (check_huge_anon(map, hpage_len, 1, hpage_len)) { ksft_test_result_pass("Test %s huge page allocation\n", __func__); =20 clear_softdirty(); diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/test= ing/selftests/mm/split_huge_page_test.c index 32b991472f74..4cc70873a674 100644 --- a/tools/testing/selftests/mm/split_huge_page_test.c +++ b/tools/testing/selftests/mm/split_huge_page_test.c @@ -296,7 +296,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(= char *one_page, int nr_hp unsigned long rss_anon_before, rss_anon_after; size_t i; =20 - if (!check_huge_anon(one_page, nr_hpages, pmd_pagesize)) + if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, nr_hpages, pmd_p= agesize)) ksft_exit_fail_msg("No THP is allocated\n"); =20 rss_anon_before =3D rss_anon(); @@ -311,7 +311,7 @@ static void verify_rss_anon_split_huge_page_all_zeroes(= char *one_page, int nr_hp if (one_page[i] !=3D (char)0) ksft_exit_fail_msg("%ld byte corrupted\n", i); =20 - if (!check_huge_anon(one_page, 0, pmd_pagesize)) + if (!check_huge_anon(one_page, nr_hpages * pmd_pagesize, 0, pmd_pagesize)) ksft_exit_fail_msg("Still AnonHugePages not split\n"); =20 rss_anon_after =3D rss_anon(); @@ -347,7 +347,7 @@ static void split_pmd_thp_to_order(int order) for (i =3D 0; i < len; i++) one_page[i] =3D (char)i; =20 - if (!check_huge_anon(one_page, 4, pmd_pagesize)) + if (!check_huge_anon(one_page, 4 * pmd_pagesize, 4, pmd_pagesize)) ksft_exit_fail_msg("No THP is allocated\n"); =20 /* split all THPs */ @@ -366,7 +366,7 @@ static void split_pmd_thp_to_order(int order) (pmd_order + 1))) ksft_exit_fail_msg("Unexpected THP split\n"); =20 - if (!check_huge_anon(one_page, 0, pmd_pagesize)) + if (!check_huge_anon(one_page, 4 * pmd_pagesize, 0, pmd_pagesize)) ksft_exit_fail_msg("Still AnonHugePages not split\n"); =20 ksft_test_result_pass("Split huge pages to order %d successful\n", order); @@ -393,7 +393,7 @@ static void split_pte_mapped_thp(void) for (i =3D 0; i < thp_area_size; i++) thp_area[i] =3D (char)i; =20 - if (!check_huge_anon(thp_area, nr_thps, pmd_pagesize)) { + if (!check_huge_anon(thp_area, nr_thps * pmd_pagesize, nr_thps, pmd_pages= ize)) { ksft_test_result_skip("Not all THPs allocated\n"); goto out; } @@ -657,7 +657,7 @@ static int create_pagecache_thp_and_fd(const char *test= file, size_t fd_size, =20 force_read_pages(*addr, fd_size / pmd_pagesize, pmd_pagesize); =20 - if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) { + if (!check_huge_file(*addr, fd_size, fd_size / pmd_pagesize, pmd_pagesize= )) { ksft_print_msg("No large pagecache folio generated, please provide a fil= esystem supporting large folio\n"); munmap(*addr, fd_size); close(*fd); @@ -735,7 +735,7 @@ static void split_thp_in_pagecache_to_order_at(size_t f= d_size, goto out; } =20 - if (!check_huge_file(addr, 0, pmd_pagesize)) { + if (!check_huge_file(addr, fd_size, 0, pmd_pagesize)) { ksft_print_msg("Still FilePmdMapped not split\n"); err =3D EXIT_FAILURE; goto out; diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selft= ests/mm/uffd-common.c index f48f5d4594ab..fa46c8c3a434 100644 --- a/tools/testing/selftests/mm/uffd-common.c +++ b/tools/testing/selftests/mm/uffd-common.c @@ -194,7 +194,9 @@ static void shmem_alias_mapping(uffd_global_test_opts_t= *gopts, __u64 *start, =20 static void shmem_check_pmd_mapping(uffd_global_test_opts_t *gopts, void *= p, int expect_nr_hpages) { - if (!check_huge_shmem(gopts->area_dst_alias, expect_nr_hpages, + unsigned long size =3D expect_nr_hpages * read_pmd_pagesize(); + + if (!check_huge_shmem(gopts->area_dst_alias, size, expect_nr_hpages, read_pmd_pagesize())) err("Did not find expected %d number of hugepages", expect_nr_hpages); diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests= /mm/vm_util.c index ef1ea11981a7..d92c819f3717 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -247,17 +247,17 @@ bool __check_huge(void *addr, char *pattern, int nr_h= pages, return thp =3D=3D (nr_hpages * (hpage_size >> 10)); } =20 -bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size) { return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); } =20 -bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size) { return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); } =20 -bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size) +bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint6= 4_t hpage_size) { return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); } diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests= /mm/vm_util.h index 7799154b67ee..c7ae28dfda63 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -90,9 +90,9 @@ void clear_softdirty(void); bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t le= n); uint64_t read_pmd_pagesize(void); unsigned long rss_anon(void); -bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size); -bool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size); -bool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size); +bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size); +bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size); +bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint6= 4_t hpage_size); int64_t allocate_transhuge(void *ptr, int pagemap_fd); int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags); =20 --=20 2.47.3 From nobody Sat Oct 3 12:49:17 2026 Received: from out30-113.freemail.mail.aliyun.com (out30-113.freemail.mail.aliyun.com [115.124.30.113]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CC1A839FCDC; Sat, 1 Aug 2026 06:26:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.30.113 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565624; cv=none; b=APF9y4g4rCe+8Fzb0Z2XbLfzaCLBUv+rekWaHvF5cMsPsZBuhomL+hMea2uW4N+ugomDDyrdxAzl6IHru7iRklQgHXN5pTf27mekSdZejquRNjL45a8fCYFNU4zkBou04oJZzKk8Them+WOcFEsiE02iVg760zTPU+Uomgk3i4U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565624; c=relaxed/simple; bh=j2ybTJiHBVhUIeQRA30sZk9fGPx8R4hpdavSVKuazGA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aMi8HVeCu32hTHvz2Bj/TRmTiIJMpPk1LMVurZ3We3bGWJ4voAsfieycOIEkn3d/6PHOnAjS1RS1MaVOS9umVkiYUmo2DM+t82J9xknkTRJo+IwiZ9AYYagXe4pafo2y2LrKfwirUuVgo5BfTn0urVP3t121AUyPIAIUyUsz2v4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com; spf=pass smtp.mailfrom=linux.alibaba.com; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b=vAQCShDl; arc=none smtp.client-ip=115.124.30.113 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b="vAQCShDl" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.alibaba.com; s=default; t=1785565611; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=PB3rmpgw9H0XxiAUvpBD648Jdd5NiwEYnJ6cr6ny1fE=; b=vAQCShDlvUq7aEkJs+tfDjOSarvkCMlPakXMdCt11LNlB33af82X+EApOH576uAehHrCPVkdqX4hXeOHPXaIshJYea9kWdCxhiJF3OHkx0vJpZ415kK4JFqX2/Z2p8Y53TXL1S7m9ZtLLIPBEpMFqZ+h9Q/jjQUjxy66L3UKqEI= X-Alimail-AntiSpam: AC=PASS;BC=-1|-1;BR=01201311R351e4;CH=green;DM=||false|;DS=||;FP=0|-1|-1|-1|0|-1|-1|-1;HT=maildocker-contentspam033037033178;MF=baolin.wang@linux.alibaba.com;NM=1;PH=DS;RN=15;SR=0;TI=SMTPD_---0X89c9e5_1785565610; Received: from localhost(mailfrom:baolin.wang@linux.alibaba.com fp:SMTPD_---0X89c9e5_1785565610 cluster:ay36) by smtp.aliyun-inc.com; Sat, 01 Aug 2026 14:26:50 +0800 From: Baolin Wang To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org Cc: ziy@nvidia.com, liam@infradead.org, nico.pache@linux.dev, dev.jain@arm.com, ryan.roberts@arm.com, baohua@kernel.org, lance.yang@linux.dev, usama.arif@linux.dev, baolin.wang@linux.alibaba.com, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 2/4] selftests: mm: move gather_after_split_folio_orders() into vm_util.c file Date: Sat, 1 Aug 2026 14:26:36 +0800 Message-ID: X-Mailer: git-send-email 2.43.5 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Move gather_after_split_folio_orders() to vm_util.c as a helper function in preparation for implementing checks for mTHP collapse. While we are at it, rename this function to indicate that it is not only used for large folio splits. No functional changes. Signed-off-by: Baolin Wang Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Nico Pache (Red Hat) Reviewed-by: Zi Yan Tested-by: Nico Pache (Red Hat) --- .../selftests/mm/split_huge_page_test.c | 125 +----------------- tools/testing/selftests/mm/vm_util.c | 119 +++++++++++++++++ tools/testing/selftests/mm/vm_util.h | 2 + 3 files changed, 122 insertions(+), 124 deletions(-) diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/test= ing/selftests/mm/split_huge_page_test.c index 4cc70873a674..86a603692826 100644 --- a/tools/testing/selftests/mm/split_huge_page_test.c +++ b/tools/testing/selftests/mm/split_huge_page_test.c @@ -104,129 +104,6 @@ static bool is_backed_by_folio(char *vaddr, int order= , int pagemap_fd, return false; } =20 -static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags= _fd, - uint64_t *flags) -{ - unsigned long pfn; - - pfn =3D pagemap_get_pfn(pagemap_fd, vaddr); - - /* non-present PFN */ - if (pfn =3D=3D -1UL) - return 1; - - if (pageflags_get(pfn, kpageflags_fd, flags)) - return -1; - - return 0; -} - -/* - * gather_after_split_folio_orders - scan through [vaddr_start, len) and r= ecord - * folio orders - * - * @vaddr_start: start vaddr - * @len: range length - * @pagemap_fd: file descriptor to /proc//pagemap - * @kpageflags_fd: file descriptor to /proc/kpageflags - * @orders: output folio order array - * @nr_orders: folio order array size - * - * gather_after_split_folio_orders() scan through [vaddr_start, len) and c= heck - * all folios within the range and record their orders. All order-0 pages = will - * be recorded. Non-present vaddr is skipped. - * - * NOTE: the function is used to check folio orders after a split is perfo= rmed, - * so it assumes [vaddr_start, len) fully maps to after-split folios withi= n that - * range. - * - * Return: 0 - no error, -1 - unhandled cases - */ -static int gather_after_split_folio_orders(char *vaddr_start, size_t len, - int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) -{ - uint64_t page_flags =3D 0; - int cur_order =3D -1; - char *vaddr; - - if (pagemap_fd =3D=3D -1 || kpageflags_fd =3D=3D -1) - return -1; - if (!orders) - return -1; - if (nr_orders <=3D 0) - return -1; - - for (vaddr =3D vaddr_start; vaddr < vaddr_start + len;) { - char *next_folio_vaddr; - int status; - - status =3D vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd, - &page_flags); - if (status < 0) - return -1; - - /* skip non present vaddr */ - if (status =3D=3D 1) { - vaddr +=3D psize(); - continue; - } - - /* all order-0 pages with possible false postive (non folio) */ - if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { - orders[0]++; - vaddr +=3D psize(); - continue; - } - - /* skip non thp compound pages */ - if (!(page_flags & KPF_THP)) { - vaddr +=3D psize(); - continue; - } - - /* vpn points to part of a THP at this point */ - if (page_flags & KPF_COMPOUND_HEAD) - cur_order =3D 1; - else { - vaddr +=3D psize(); - continue; - } - - next_folio_vaddr =3D vaddr + (1UL << (cur_order + pshift())); - - if (next_folio_vaddr >=3D vaddr_start + len) - break; - - while ((status =3D vaddr_pageflags_get(next_folio_vaddr, - pagemap_fd, kpageflags_fd, - &page_flags)) >=3D 0) { - /* - * non present vaddr, next compound head page, or - * order-0 page - */ - if (status =3D=3D 1 || - (page_flags & KPF_COMPOUND_HEAD) || - !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { - if (cur_order < nr_orders) { - orders[cur_order]++; - cur_order =3D -1; - vaddr =3D next_folio_vaddr; - } - break; - } - - cur_order++; - next_folio_vaddr =3D vaddr + (1UL << (cur_order + pshift())); - } - - if (status < 0) - return status; - } - if (cur_order > 0 && cur_order < nr_orders) - orders[cur_order]++; - return 0; -} - static int check_after_split_folio_orders(char *vaddr_start, size_t len, int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) { @@ -240,7 +117,7 @@ static int check_after_split_folio_orders(char *vaddr_s= tart, size_t len, ksft_exit_fail_msg("Cannot allocate memory for vaddr_orders"); =20 memset(vaddr_orders, 0, sizeof(int) * nr_orders); - status =3D gather_after_split_folio_orders(vaddr_start, len, pagemap_fd, + status =3D gather_folio_orders(vaddr_start, len, pagemap_fd, kpageflags_fd, vaddr_orders, nr_orders); if (status) ksft_exit_fail_msg("gather folio info failed\n"); diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests= /mm/vm_util.c index d92c819f3717..09e5d5cabe21 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -194,6 +194,125 @@ unsigned long rss_anon(void) return rss_anon; } =20 +static int vaddr_pageflags_get(char *vaddr, int pagemap_fd, int kpageflags= _fd, + uint64_t *flags) +{ + unsigned long pfn; + + pfn =3D pagemap_get_pfn(pagemap_fd, vaddr); + + /* non-present PFN */ + if (pfn =3D=3D -1UL) + return 1; + + if (pageflags_get(pfn, kpageflags_fd, flags)) + return -1; + + return 0; +} + +/* + * gather_folio_orders - scan through [vaddr_start, len) and record + * folio orders + * + * @vaddr_start: start vaddr + * @len: range length + * @pagemap_fd: file descriptor to /proc//pagemap + * @kpageflags_fd: file descriptor to /proc/kpageflags + * @orders: output folio order array + * @nr_orders: folio order array size + * + * gather_folio_orders() scan through [vaddr_start, len) and check + * all folios within the range and record their orders. All order-0 pages = will + * be recorded. Non-present vaddr is skipped. + * + * Return: 0 - no error, -1 - unhandled cases + */ +int gather_folio_orders(char *vaddr_start, size_t len, + int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders) +{ + uint64_t page_flags =3D 0; + int cur_order =3D -1; + char *vaddr; + + if (pagemap_fd =3D=3D -1 || kpageflags_fd =3D=3D -1) + return -1; + if (!orders) + return -1; + if (nr_orders <=3D 0) + return -1; + + for (vaddr =3D vaddr_start; vaddr < vaddr_start + len;) { + char *next_folio_vaddr; + int status; + + status =3D vaddr_pageflags_get(vaddr, pagemap_fd, kpageflags_fd, + &page_flags); + if (status < 0) + return -1; + + /* skip non present vaddr */ + if (status =3D=3D 1) { + vaddr +=3D psize(); + continue; + } + + /* all order-0 pages with possible false postive (non folio) */ + if (!(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { + orders[0]++; + vaddr +=3D psize(); + continue; + } + + /* skip non thp compound pages */ + if (!(page_flags & KPF_THP)) { + vaddr +=3D psize(); + continue; + } + + /* vpn points to part of a THP at this point */ + if (page_flags & KPF_COMPOUND_HEAD) + cur_order =3D 1; + else { + vaddr +=3D psize(); + continue; + } + + next_folio_vaddr =3D vaddr + (1UL << (cur_order + pshift())); + + if (next_folio_vaddr >=3D vaddr_start + len) + break; + + while ((status =3D vaddr_pageflags_get(next_folio_vaddr, + pagemap_fd, kpageflags_fd, + &page_flags)) >=3D 0) { + /* + * non present vaddr, next compound head page, or + * order-0 page + */ + if (status =3D=3D 1 || + (page_flags & KPF_COMPOUND_HEAD) || + !(page_flags & (KPF_COMPOUND_HEAD | KPF_COMPOUND_TAIL))) { + if (cur_order < nr_orders) { + orders[cur_order]++; + cur_order =3D -1; + vaddr =3D next_folio_vaddr; + } + break; + } + + cur_order++; + next_folio_vaddr =3D vaddr + (1UL << (cur_order + pshift())); + } + + if (status < 0) + return status; + } + if (cur_order > 0 && cur_order < nr_orders) + orders[cur_order]++; + return 0; +} + char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t = len) { int ret; diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests= /mm/vm_util.h index c7ae28dfda63..20af3e574cd8 100644 --- a/tools/testing/selftests/mm/vm_util.h +++ b/tools/testing/selftests/mm/vm_util.h @@ -95,6 +95,8 @@ bool check_huge_file(void *addr, unsigned long size, int = nr_hpages, uint64_t hpa bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint6= 4_t hpage_size); int64_t allocate_transhuge(void *ptr, int pagemap_fd); int pageflags_get(unsigned long pfn, int kpageflags_fd, uint64_t *flags); +int gather_folio_orders(char *vaddr_start, size_t len, + int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders); =20 int uffd_register(int uffd, void *addr, uint64_t len, bool miss, bool wp, bool minor); --=20 2.47.3 From nobody Sat Oct 3 12:49:17 2026 Received: from out30-112.freemail.mail.aliyun.com (out30-112.freemail.mail.aliyun.com [115.124.30.112]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9722E423A79; Sat, 1 Aug 2026 06:27:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.30.112 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565626; cv=none; b=i2HqYZSN7LyYnYiPD16BcPAZzpQTXWuN1fUn3FZVZg8FWsxjpRF3r7CHARuqYGvEndzcs+w57/uFB48997TsFs3Nm07Ho9JWNFzyuUl/VntvKebOzSIbFptnUTYQsFEfoionuXboa2s6SnwLjCc8Acm2HPfe4ofIA+f0Un2KL0w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565626; c=relaxed/simple; bh=IgTtnJLKgDNfRDgbed54dlP5ENNsk723PK9VeE3MPnY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jqmrwtteEsCGZqj/7upD3UaNKgPvmGOff6MG0FQkXTliWtldUL52nl8sbQNoi0ogfC2CjcrH4pr7yhAsMTouIRA95h75oNvqaq8K+CNAfBar9Jlf2GBvWQ0lK7t221GfhpQ6zTGUKCcyBC9h4st1ybiJk1SaQ3/WC0YjMWlqAr0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com; spf=pass smtp.mailfrom=linux.alibaba.com; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b=djItqNkp; arc=none smtp.client-ip=115.124.30.112 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b="djItqNkp" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.alibaba.com; s=default; t=1785565612; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=N8stx6QvPfTkiuk/kES36f5Gwycdy/i4pO9WmWrwrHc=; b=djItqNkpwCvcn+MK5UKl8selyhGZdWxTk6vNDNPPNdGTN20MbFJrpezJaeat3/PPKORjE3HTG8gXOssGrSx+3Ip6dXlJMhvGUVF1A4Ol8dASXm4FgTzyDc3QXNwCdR8W52OKNVYL6wb3jPcKIrwBMIg+wu42Dnmwoobu3jwTZ5M= X-Alimail-AntiSpam: AC=PASS;BC=-1|-1;BR=01201311R181e4;CH=green;DM=||false|;DS=||;FP=0|-1|-1|-1|0|-1|-1|-1;HT=maildocker-contentspam033045098064;MF=baolin.wang@linux.alibaba.com;NM=1;PH=DS;RN=15;SR=0;TI=SMTPD_---0X89cw8M_1785565611; Received: from localhost(mailfrom:baolin.wang@linux.alibaba.com fp:SMTPD_---0X89cw8M_1785565611 cluster:ay36) by smtp.aliyun-inc.com; Sat, 01 Aug 2026 14:26:51 +0800 From: Baolin Wang To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org Cc: ziy@nvidia.com, liam@infradead.org, nico.pache@linux.dev, dev.jain@arm.com, ryan.roberts@arm.com, baohua@kernel.org, lance.yang@linux.dev, usama.arif@linux.dev, baolin.wang@linux.alibaba.com, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 3/4] selftests: mm: implement the mTHP-sized hugepage check helpers Date: Sat, 1 Aug 2026 14:26:37 +0800 Message-ID: X-Mailer: git-send-email 2.43.5 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Implement mTHP-sized hugepage checking helpers using gather_folio_orders(). Also rename the existing PMD-sized huge page check function to __check_pmd_huge() for clarity. Signed-off-by: Baolin Wang Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) --- tools/testing/selftests/mm/vm_util.c | 60 ++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests= /mm/vm_util.c index 09e5d5cabe21..1240d783669c 100644 --- a/tools/testing/selftests/mm/vm_util.c +++ b/tools/testing/selftests/mm/vm_util.c @@ -15,6 +15,10 @@ #define SMAP_FILE_PATH "/proc/self/smaps" #define STATUS_FILE_PATH "/proc/self/status" #define MAX_LINE_LENGTH 500 +#define PAGEMAP_PATH "/proc/self/pagemap" +#define KPAGEFLAGS_PATH "/proc/kpageflags" +#define GET_ORDER(nr_pages) (31 - __builtin_clz(nr_pages)) +#define NR_ORDERS 20 =20 unsigned int __page_size; unsigned int __page_shift; @@ -348,7 +352,7 @@ char *__get_smap_entry(void *addr, const char *pattern,= char *buf, size_t len) return entry; } =20 -bool __check_huge(void *addr, char *pattern, int nr_hpages, +static bool __check_pmd_huge(void *addr, char *pattern, int nr_hpages, uint64_t hpage_size) { char buffer[MAX_LINE_LENGTH]; @@ -366,19 +370,67 @@ bool __check_huge(void *addr, char *pattern, int nr_h= pages, return thp =3D=3D (nr_hpages * (hpage_size >> 10)); } =20 +static bool check_large_folios(void *addr, unsigned long size, int nr_hpag= es, uint64_t hpage_size) +{ + int order =3D 0, pagesize =3D getpagesize(); + int nr_pages =3D hpage_size / pagesize; + int pagemap_fd, kpageflags_fd; + int orders[NR_ORDERS], status; + bool ret =3D false; + + if (nr_pages > 0) + order =3D GET_ORDER(nr_pages); + + if (!order || order >=3D NR_ORDERS) + ksft_exit_fail_msg("invalid order\n"); + + memset(orders, 0, sizeof(int) * NR_ORDERS); + pagemap_fd =3D open(PAGEMAP_PATH, O_RDONLY); + if (pagemap_fd =3D=3D -1) + ksft_exit_fail_msg("read pagemap fail\n"); + + kpageflags_fd =3D open(KPAGEFLAGS_PATH, O_RDONLY); + if (kpageflags_fd =3D=3D -1) { + close(pagemap_fd); + ksft_exit_fail_msg("read kpageflags fail\n"); + } + + status =3D gather_folio_orders(addr, size, pagemap_fd, + kpageflags_fd, orders, NR_ORDERS); + if (status) + goto out; + + if (orders[order] =3D=3D nr_hpages) + ret =3D true; + +out: + close(pagemap_fd); + close(kpageflags_fd); + return ret; +} + bool check_huge_anon(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size) { - return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); + if (hpage_size =3D=3D read_pmd_pagesize()) + return __check_pmd_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); + + return check_large_folios(addr, size, nr_hpages, hpage_size); } =20 bool check_huge_file(void *addr, unsigned long size, int nr_hpages, uint64= _t hpage_size) { - return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); + if (hpage_size =3D=3D read_pmd_pagesize()) + return __check_pmd_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); + + return check_large_folios(addr, size, nr_hpages, hpage_size); } =20 bool check_huge_shmem(void *addr, unsigned long size, int nr_hpages, uint6= 4_t hpage_size) { - return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); + if (hpage_size =3D=3D read_pmd_pagesize()) + return __check_pmd_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); + + return check_large_folios(addr, size, nr_hpages, hpage_size); } =20 int64_t allocate_transhuge(void *ptr, int pagemap_fd) --=20 2.47.3 From nobody Sat Oct 3 12:49:17 2026 Received: from out30-111.freemail.mail.aliyun.com (out30-111.freemail.mail.aliyun.com [115.124.30.111]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A272B221DB3; Sat, 1 Aug 2026 06:26:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.30.111 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565619; cv=none; b=BRiYeIeH5Jc7sQn++CBvfpt5K3zYjlHDgIeZijbJHwZaHVZTg2UrNNPcpyY9Mn5YwKciCOXD/0NCiM2qtI81J6W5HYexxqeT7FJ5oNcRIm7w59lbMdoJIUVa/YW6YDuRz2Qs3lbaIA7mW1alYw7vcdz0NPKnZvDTftCkYI7ocOM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785565619; c=relaxed/simple; bh=cvM3HsqWpL7TR13ce/jBDNkjH9D/WBsSLG/pukNoTTs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pSywBkrcZDXE/8xZI/u1OA9i2xjP7wlODQEbHK06s8Rlfmfcwjv7Z2u2gKF/2mJocYQD/vVcHUhy1jfCqWM1mpu08GQPsIHOeGcIugAFiGp7YDZ3MDLAgzQxeJQnHzvFqP+zVFP7UMRcdgFohfqR+TDYFd+Cg333DFj576JmgNU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com; spf=pass smtp.mailfrom=linux.alibaba.com; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b=yjzAedOe; arc=none smtp.client-ip=115.124.30.111 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.alibaba.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.alibaba.com header.i=@linux.alibaba.com header.b="yjzAedOe" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.alibaba.com; s=default; t=1785565613; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=FeTN6/wbbbJzcWBYOmzqiIEjf07PxMHihk3dLZnDlrk=; b=yjzAedOe0h7r3zejJXKWU4raT1u+q+yYK27lYwekA0Zn+phVm3J14UXBTtm4XSZ+Cs8jiG6gE8ejNFXsDqrF8+++oQQ1RXx6wLqCaAIAv7aHcyPXGnNEZF2U0jqihwENzYAJUMQt7PMtpDyffmPC4L3IWhRqMjd2ZWZ0uY3cF24= X-Alimail-AntiSpam: AC=PASS;BC=-1|-1;BR=01201311R181e4;CH=green;DM=||false|;DS=||;FP=0|-1|-1|-1|0|-1|-1|-1;HT=maildocker-contentspam011083073210;MF=baolin.wang@linux.alibaba.com;NM=1;PH=DS;RN=15;SR=0;TI=SMTPD_---0X89c9eS_1785565611; Received: from localhost(mailfrom:baolin.wang@linux.alibaba.com fp:SMTPD_---0X89c9eS_1785565611 cluster:ay36) by smtp.aliyun-inc.com; Sat, 01 Aug 2026 14:26:52 +0800 From: Baolin Wang To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org Cc: ziy@nvidia.com, liam@infradead.org, nico.pache@linux.dev, dev.jain@arm.com, ryan.roberts@arm.com, baohua@kernel.org, lance.yang@linux.dev, usama.arif@linux.dev, baolin.wang@linux.alibaba.com, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 4/4] selftests: mm: add mTHP collapse test cases Date: Sat, 1 Aug 2026 14:26:38 +0800 Message-ID: X-Mailer: git-send-email 2.43.5 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Added a new command 'mthp_khugepaged' for mTHP collapse, along with the '-c' parameter to specify the collapse order. Additionally, added mTHP collapse test cases for 'collapse_full', 'collapse_empty', and 'collapse_single_mthp' for anonymous folios. All khugepaged test cases passed. Signed-off-by: Baolin Wang Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Nico Pache (Red Hat) Tested-by: Nico Pache (Red Hat) --- tools/testing/selftests/mm/khugepaged.c | 130 ++++++++++++++++++---- tools/testing/selftests/mm/run_vmtests.sh | 2 + 2 files changed, 113 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selfte= sts/mm/khugepaged.c index f69be6be0ecd..64d6da6aa90d 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -26,9 +26,11 @@ =20 #define BASE_ADDR ((void *)(1UL << 30)) static unsigned long hpage_pmd_size; +static int hpage_pmd_order; static unsigned long page_size; static int hpage_pmd_nr; static int anon_order; +static int collapse_order; =20 #define PID_SMAPS "/proc/self/smaps" #define TEST_FILE "collapse_test_file" @@ -69,6 +71,7 @@ struct collapse_context { }; =20 static struct collapse_context *khugepaged_context; +static struct collapse_context *mthp_khugepaged_context; static struct collapse_context *madvise_context; =20 struct file_info { @@ -554,25 +557,25 @@ static void madvise_collapse(const char *msg, char *p= , int nr_hpages, } =20 #define TICK 500000 -static bool wait_for_scan(const char *msg, char *p, int nr_hpages, - struct mem_ops *ops) +static bool wait_for_scan(const char *msg, char *p, unsigned long size, + int nr_hpages, int collap_order, struct mem_ops *ops) { - unsigned long size =3D nr_hpages * hpage_pmd_size; + unsigned long hpage_size =3D page_size << collap_order; int full_scans; int timeout =3D 6; /* 3 seconds */ =20 /* Sanity check */ - if (!ops->check_huge(p, size, 0, hpage_pmd_size)) + if (!ops->check_huge(p, size, 0, hpage_size)) ksft_exit_fail_msg("Unexpected huge page\n"); =20 - madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); + madvise(p, size, MADV_HUGEPAGE); =20 /* Wait until the second full_scan completed */ full_scans =3D thp_read_num("khugepaged/full_scans") + 2; =20 ksft_print_msg("%s...", msg); while (timeout--) { - if (ops->check_huge(p, size, nr_hpages, hpage_pmd_size)) + if (ops->check_huge(p, size, nr_hpages, hpage_size)) break; if (thp_read_num("khugepaged/full_scans") >=3D full_scans) break; @@ -595,7 +598,7 @@ static void khugepaged_collapse(const char *msg, char *= p, int nr_hpages, if (!is_tmpfs(ops) && ops =3D=3D &__read_write_file_write_ops) expect =3D false; =20 - if (wait_for_scan(msg, p, nr_hpages, ops)) { + if (wait_for_scan(msg, p, size, nr_hpages, hpage_pmd_order, ops)) { if (expect) fail("Timeout"); else @@ -617,12 +620,62 @@ static void khugepaged_collapse(const char *msg, char= *p, int nr_hpages, fail("Fail"); } =20 +static void mthp_khugepaged_collapse(const char *msg, char *p, int nr_hpag= es, + struct mem_ops *ops, bool expect) +{ + unsigned long hpage_size =3D page_size << collapse_order; + struct thp_settings settings =3D *thp_current_settings(); + /* mTHP collpase only allocates PMD sized memory */ + unsigned long size =3D hpage_pmd_size; + + /* Set mTHP setting for mTHP collapse */ + if (ops =3D=3D &__anon_ops) { + settings.thp_enabled =3D THP_NEVER; + settings.hugepages[collapse_order].enabled =3D THP_MADVISE; + } + + thp_push_settings(&settings); + + if (wait_for_scan(msg, p, size, nr_hpages, collapse_order, ops)) { + if (expect) + fail("Timeout"); + else + success("OK"); + + /* Restore THP settings for mTHP collapse. */ + thp_pop_settings(); + return; + } + + /* + * For file and shmem memory, khugepaged only retracts pte entries after + * putting the new hugepage in the page cache. The hugepage must be + * subsequently refaulted to install the pmd mapping for the mm. + */ + if (ops !=3D &__anon_ops) + ops->fault(p, 0, nr_hpages * hpage_size); + + if (ops->check_huge(p, size, expect ? nr_hpages : 0, hpage_size)) + success("OK"); + else + fail("Fail"); + + /* Restore THP settings for mTHP collapse. */ + thp_pop_settings(); +} + static struct collapse_context __khugepaged_context =3D { .collapse =3D &khugepaged_collapse, .enforce_pte_scan_limits =3D true, .name =3D "khugepaged", }; =20 +static struct collapse_context __mthp_khugepaged_context =3D { + .collapse =3D &mthp_khugepaged_collapse, + .enforce_pte_scan_limits =3D true, + .name =3D "mthp_khugepaged", +}; + static struct collapse_context __madvise_context =3D { .collapse =3D &madvise_collapse, .enforce_pte_scan_limits =3D false, @@ -661,10 +714,17 @@ static void alloc_at_fault(void) static void collapse_full(struct collapse_context *c, struct mem_ops *ops) { void *p; - int nr_hpages =3D 4; + int nr_pmds =3D 4, nr_hpages =3D 4; unsigned long size =3D nr_hpages * hpage_pmd_size; =20 - p =3D ops->setup_area(nr_hpages); + /* Only try 1 PMD sized range for mTHP collapse. */ + if (c =3D=3D &__mthp_khugepaged_context) { + nr_pmds =3D 1; + nr_hpages =3D 1 << (hpage_pmd_order - collapse_order); + size =3D hpage_pmd_size; + } + + p =3D ops->setup_area(nr_pmds); ops->fault(p, 0, size); c->collapse("Collapse multiple fully populated PTE table", p, nr_hpages, ops, true); @@ -676,10 +736,31 @@ static void collapse_full(struct collapse_context *c,= struct mem_ops *ops) =20 static void collapse_empty(struct collapse_context *c, struct mem_ops *ops) { + int nr_hpages =3D 1; + void *p; + + if (c =3D=3D &__mthp_khugepaged_context) + nr_hpages =3D 1 << (hpage_pmd_order - collapse_order); + + p =3D ops->setup_area(1); + c->collapse("Do not collapse empty PTE table", p, nr_hpages, ops, false); + ops->cleanup_area(p, hpage_pmd_size); + ksft_test_result_report(exit_status, "%s\n", __func__); +} + +static void collapse_single_mthp(struct collapse_context *c, struct mem_op= s *ops) +{ + unsigned long hpage_size =3D page_size << collapse_order; void *p; =20 p =3D ops->setup_area(1); - c->collapse("Do not collapse empty PTE table", p, 1, ops, false); + /* + * Only fault collapse_order sized ranges, and only check 1 + * collapse_order sized huge page. + */ + ops->fault(p, 0, hpage_size); + c->collapse("Collapse PTE table with half PTE entries present", + p, 1, ops, true); ops->cleanup_area(p, hpage_pmd_size); ksft_test_result_report(exit_status, "%s\n", __func__); } @@ -1081,8 +1162,8 @@ static void madvise_retracted_page_tables(struct coll= apse_context *c, ops->fault(p, 0, size); =20 /* Let khugepaged collapse and leave pmd cleared */ - if (wait_for_scan("Collapse and leave PMD cleared", p, nr_hpages, - ops)) { + if (wait_for_scan("Collapse and leave PMD cleared", p, size, nr_hpages, + hpage_pmd_order, ops)) { fail("Timeout"); return; } @@ -1098,7 +1179,7 @@ static void usage(void) { fprintf(stderr, "\nUsage: ./khugepaged [OPTIONS] [dir]\n\n"); fprintf(stderr, "\t\t: :\n"); - fprintf(stderr, "\t\t: [all|khugepaged|madvise]\n"); + fprintf(stderr, "\t\t: [all|khugepaged|mthp_khugepaged|madvise]\= n"); fprintf(stderr, "\t\t: [all|anon|file|shmem]\n"); fprintf(stderr, "\n\t\"file,all\" mem_type requires [dir] argument\n"); fprintf(stderr, "\n\t\"file,all\" mem_type requires a file system\n"); @@ -1109,6 +1190,7 @@ static void usage(void) fprintf(stderr, "\t\t-h: This help message.\n"); fprintf(stderr, "\t\t-s: mTHP size, expressed as page order.\n"); fprintf(stderr, "\t\t Defaults to 0. Use this size for anon or shmem a= llocations.\n"); + fprintf(stderr, "\t\t-c: collapse order for mTHP collapse, expressed as p= age order.\n"); exit(1); } =20 @@ -1118,11 +1200,14 @@ static void parse_test_type(int argc, char **argv) char *buf; const char *token; =20 - while ((opt =3D getopt(argc, argv, "s:h")) !=3D -1) { + while ((opt =3D getopt(argc, argv, "s:c:h")) !=3D -1) { switch (opt) { case 's': anon_order =3D atoi(optarg); break; + case 'c': + collapse_order =3D atoi(optarg); + break; case 'h': default: usage(); @@ -1148,6 +1233,10 @@ static void parse_test_type(int argc, char **argv) madvise_context =3D &__madvise_context; } else if (!strcmp(token, "khugepaged")) { khugepaged_context =3D &__khugepaged_context; + } else if (!strcmp(token, "mthp_khugepaged")) { + mthp_khugepaged_context =3D &__mthp_khugepaged_context; + if (collapse_order <=3D 0 || collapse_order >=3D hpage_pmd_order) + usage(); } else if (!strcmp(token, "madvise")) { madvise_context =3D &__madvise_context; } else { @@ -1213,7 +1302,6 @@ static int nr_test_cases; =20 int main(int argc, char **argv) { - int hpage_pmd_order; struct thp_settings default_settings =3D { .thp_enabled =3D THP_MADVISE, .thp_defrag =3D THP_DEFRAG_ALWAYS, @@ -1239,10 +1327,6 @@ int main(int argc, char **argv) if (!thp_is_enabled()) ksft_exit_skip("Transparent Hugepages not available\n"); =20 - parse_test_type(argc, argv); - - setbuf(stdout, NULL); - page_size =3D getpagesize(); hpage_pmd_size =3D read_pmd_pagesize(); if (!hpage_pmd_size) @@ -1250,6 +1334,10 @@ int main(int argc, char **argv) hpage_pmd_nr =3D hpage_pmd_size / page_size; hpage_pmd_order =3D __builtin_ctz(hpage_pmd_nr); =20 + parse_test_type(argc, argv); + + setbuf(stdout, NULL); + default_settings.khugepaged.max_ptes_none =3D hpage_pmd_nr - 1; default_settings.khugepaged.max_ptes_swap =3D hpage_pmd_nr / 8; default_settings.khugepaged.max_ptes_shared =3D hpage_pmd_nr / 2; @@ -1267,6 +1355,7 @@ int main(int argc, char **argv) TEST(collapse_full, khugepaged_context, read_write_file_read_ops); TEST(collapse_full, khugepaged_context, read_write_file_write_ops); TEST(collapse_full, khugepaged_context, shmem_ops); + TEST(collapse_full, mthp_khugepaged_context, anon_ops); TEST(collapse_full, madvise_context, anon_ops); TEST(collapse_full, madvise_context, read_only_file_ops); TEST(collapse_full, madvise_context, read_write_file_read_ops); @@ -1274,8 +1363,11 @@ int main(int argc, char **argv) TEST(collapse_full, madvise_context, shmem_ops); =20 TEST(collapse_empty, khugepaged_context, anon_ops); + TEST(collapse_empty, mthp_khugepaged_context, anon_ops); TEST(collapse_empty, madvise_context, anon_ops); =20 + TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops); + TEST(collapse_single_pte_entry, khugepaged_context, anon_ops); TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops); TEST(collapse_single_pte_entry, khugepaged_context, read_write_file_read_= ops); diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/self= tests/mm/run_vmtests.sh index 687d115e3bd8..d09f9f6a384e 100755 --- a/tools/testing/selftests/mm/run_vmtests.sh +++ b/tools/testing/selftests/mm/run_vmtests.sh @@ -410,6 +410,8 @@ CATEGORY=3D"thp" run_test ./khugepaged all:shmem =20 CATEGORY=3D"thp" run_test ./khugepaged -s 4 all:shmem =20 +CATEGORY=3D"thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon + # Try to create XFS if not provided if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then if test_selected "thp"; then --=20 2.47.3