[PATCH v2 0/4] selftests/mm: check large folio results through check_huge() only

From: Kiryl Shutsemau

Date: Fri Oct 09 2026 - 11:32:22 EST


From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>

The mm selftests had two ways to check that a range came out as folios
of a given order. check_huge_anon() and check_huge_file() counted
folios of the order in the range. is_range_backed_by_order() checked
each window for a folio with its head at the window start. Baolin found
the two sets in khugepaged.c confusing [1], and the answer at the time
was to merge them once Yeoreum's rewrite of check_huge_*() on top of
pagemap and kpageflags landed. It is in mm-new now.

Make check_huge() check windows below the PMD size, route every caller
through it, and make is_range_backed_by_order() an internal of vm_util.c.
The split test's count-based check goes the same way, which also makes
it notice a split at the wrong offset. On the way check_huge() stops
reopening pagemap and kpageflags on every call, which was the other
reason the separate helper existed.

Yeoreum's HPAGE_SIZE series [2] renames read_pmd_pagesize() to
pmd_psize(); the only overlap is the one call in __check_huge().

Tested on mm-new 2589c5593c20 with KASAN, in 16G guests, x86-64/4K under
KVM and arm64/64K under TCG: the khugepaged matrix at every order,
split_huge_page_test, pagemap_ioctl and khugepaged all:all on tmpfs pass
with the same counts as before.

v2:
- Drop the PMD size caching from patch 1; Yeoreum's series caches it in
pmd_psize() (Zi)
- folio_order_check: skip again when /proc/kpageflags is not readable
(Sashiko)
- kpageflags_fd_get(): return early instead of nesting the open

v1: https://lore.kernel.org/all/20260929131521.153079-1-kirill@xxxxxxxxxxxxx/

[1] https://lore.kernel.org/all/65bf1722-c1fd-452a-88c1-c030b042cfbc@xxxxxxxxxxxxxxxxx/
[2] https://lore.kernel.org/all/20261009-fix_hpage_macro-v5-0-54378ae45add@xxxxxxx/

Kiryl Shutsemau (Meta) (4):
selftests/mm: open pagemap and kpageflags once for check_huge()
selftests/mm: check sub-PMD ranges window by window in check_huge()
selftests/mm: check collapse results through check_huge() only
selftests/mm: check split results by position, drop
gather_folio_orders()

.../testing/selftests/mm/folio_order_check.c | 29 +-
tools/testing/selftests/mm/khugepaged.c | 47 ++-
.../selftests/mm/khugepaged_sync_check.c | 7 +-
.../selftests/mm/split_huge_page_test.c | 104 +++---
tools/testing/selftests/mm/vm_util.c | 314 ++++++------------
tools/testing/selftests/mm/vm_util.h | 7 +-
6 files changed, 173 insertions(+), 335 deletions(-)

Range-diff against v1:
1: 887d475288bf ! 1: 058de2172bde selftests/mm: open pagemap and kpageflags once for check_huge()
@@ Commit message
selftests/mm: open pagemap and kpageflags once for check_huge()

check_huge_anon() and check_huge_file() open /proc/self/pagemap and
- /proc/kpageflags on every call and read the PMD size from sysfs on top.
- The khugepaged test calls them hundreds of times.
+ /proc/kpageflags on every call. The khugepaged test calls them hundreds
+ of times.

Open both once and keep the descriptors. A pagemap descriptor is tied
to the mm of the process that opened it, and the khugepaged fork() cases
call check_huge() from the child, so reopen it when getpid() changes.

- Cache the PMD size in read_pmd_pagesize() as well.
-
Assisted-by: LLM
Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>

@@ tools/testing/selftests/mm/vm_util.c
+{
+ static int fd = -1;
+
-+ if (fd < 0) {
-+ fd = open(KPAGEFLAGS_PATH, O_RDONLY);
-+ if (fd < 0)
-+ ksft_exit_fail_msg("open kpageflags fail\n");
-+ }
++ if (fd >= 0)
++ return fd;
++
++ fd = open(KPAGEFLAGS_PATH, O_RDONLY);
++ if (fd < 0)
++ ksft_exit_fail_msg("open kpageflags fail\n");
+
+ return fd;
+}
@@ tools/testing/selftests/mm/vm_util.c
uint64_t pagemap_get_entry(int fd, char *start)
{
const unsigned long pfn = (unsigned long)start / getpagesize();
-@@ tools/testing/selftests/mm/vm_util.c: bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len)
-
- uint64_t read_pmd_pagesize(void)
- {
-+ static uint64_t pmd_pagesize;
- int fd;
- char buf[20];
- ssize_t num_read;
-
-+ if (pmd_pagesize)
-+ return pmd_pagesize;
-+
- fd = open(PMD_SIZE_FILE_PATH, O_RDONLY);
- if (fd == -1)
- return 0;
-@@ tools/testing/selftests/mm/vm_util.c: uint64_t read_pmd_pagesize(void)
- buf[num_read] = '\0';
- close(fd);
-
-- return strtoul(buf, NULL, 10);
-+ pmd_pagesize = strtoul(buf, NULL, 10);
-+ return pmd_pagesize;
- }
-
- unsigned long rss_anon(void)
@@ tools/testing/selftests/mm/vm_util.c: static bool check_huge_type(uint64_t categories, enum check_huge_type type)
static bool __check_huge(void *addr, size_t len, int nr_hpages,
uint64_t hpage_size, enum check_huge_type type)
2: e89550a21307 = 2: a95eb5e9e2c8 selftests/mm: check sub-PMD ranges window by window in check_huge()
3: 3988e8ff5c3b ! 3: 40aa2e377b65 selftests/mm: check collapse results through check_huge() only
@@ tools/testing/selftests/mm/folio_order_check.c: int main(void)
- kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
- if (kpageflags_fd < 0)
- ksft_exit_skip("open(/proc/kpageflags) requires root\n");
--
++ if (access("/proc/kpageflags", R_OK))
++ ksft_exit_skip("/proc/kpageflags requires root\n");
+
orders = thp_supported_orders();
if (!orders)
- ksft_exit_skip("No supported THP orders\n");

## tools/testing/selftests/mm/khugepaged.c ##
@@ tools/testing/selftests/mm/khugepaged.c: static int collapse_order;
4: 898256c09252 = 4: 405414814409 selftests/mm: check split results by position, drop gather_folio_orders()

base-commit: 2589c5593c200e5146db848730baa1d7dd0c9f4e
--
2.54.0