[PATCH 3/4] selftests/mm: check collapse results through check_huge() only
From: Kiryl Shutsemau
Date: Tue Sep 29 2026 - 09:20:18 EST
From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>
khugepaged.c checks collapse results in two ways. Generic cases go
through the mem_ops check_huge() callback. collapse_order_* cases call
is_range_backed_by_order() on descriptors of their own. Baolin Wang
found that confusing. The split only existed because check_huge()
counted folios instead of windows.
Now that check_huge() checks windows, the two give the same answer.
Use the callback for the collapse_order_* cases and drop the private
descriptors.
Convert khugepaged_sync_check and folio_order_check the same way. The
latter still checks that each order is detected as that order, not as
the one below and not as order 0, only now through check_huge_anon().
At the PMD order that means the PMD-mapping check rather than the
page-flag walk.
is_range_backed_by_order() has no users outside vm_util.c left. Make it
static and let it use the cached descriptors.
Assisted-by: LLM
Signed-off-by: Kiryl Shutsemau (Meta) <kas@xxxxxxxxxx>
---
.../testing/selftests/mm/folio_order_check.c | 28 +++--------
tools/testing/selftests/mm/khugepaged.c | 47 ++++++++-----------
.../selftests/mm/khugepaged_sync_check.c | 7 +--
tools/testing/selftests/mm/vm_util.c | 19 +++-----
tools/testing/selftests/mm/vm_util.h | 2 -
5 files changed, 34 insertions(+), 69 deletions(-)
diff --git a/tools/testing/selftests/mm/folio_order_check.c b/tools/testing/selftests/mm/folio_order_check.c
index 5eafbcc1b4f3..aa0586047620 100644
--- a/tools/testing/selftests/mm/folio_order_check.c
+++ b/tools/testing/selftests/mm/folio_order_check.c
@@ -1,13 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Self-check for the vm_util folio-order helpers, is_backed_by_folio() and
- * is_range_backed_by_order(), which the khugepaged mTHP cases use to detect
- * collapse results. For every anon THP order the kernel supports, fault
- * memory in with only that order enabled and require the helpers to report
- * exactly that order.
+ * Self-check for check_huge_anon(), which the khugepaged mTHP cases use to
+ * tell collapse results. For every anon THP order the kernel supports,
+ * fault memory in with only that order enabled and require the check to
+ * report exactly that order: not the order below it, and not order 0.
*/
#define _GNU_SOURCE
-#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
@@ -17,9 +15,6 @@
#include "vm_util.h"
#include <mm/hugepage_settings.h>
-static int pagemap_fd;
-static int kpageflags_fd;
-
static char *alloc_aligned(size_t size)
{
size_t len = size * 2;
@@ -56,22 +51,20 @@ static void check_order(int order)
p = alloc_aligned(size);
*p = 1;
- if (!is_range_backed_by_order(p, size, order, pagemap_fd, kpageflags_fd)) {
+ if (!check_huge_anon(p, size, 1, size)) {
ksft_print_msg("order %d not detected after fault\n", order);
ok = false;
}
/* A lower order must be rejected: the folio is larger */
- if (order && is_range_backed_by_order(p, size, order - 1,
- pagemap_fd, kpageflags_fd)) {
+ if (order && check_huge_anon(p, size, 2, size / 2)) {
ksft_print_msg("order %d also reported as order %d\n",
order, order - 1);
ok = false;
}
/* A large folio must not pass as order 0 */
- if (order && is_range_backed_by_order(p, size, 0,
- pagemap_fd, kpageflags_fd)) {
+ if (order && check_huge_anon(p, size, 1 << order, psize())) {
ksft_print_msg("order %d also reported as order 0\n", order);
ok = false;
}
@@ -93,13 +86,6 @@ int main(void)
if (!thp_available())
ksft_exit_skip("Transparent Hugepages not available\n");
- pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
- if (pagemap_fd < 0)
- ksft_exit_fail_perror("open(/proc/self/pagemap)");
- kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
- if (kpageflags_fd < 0)
- ksft_exit_skip("open(/proc/kpageflags) requires root\n");
-
orders = thp_supported_orders();
if (!orders)
ksft_exit_skip("No supported THP orders\n");
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 61bd69d604d2..a5b682eca888 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -33,8 +33,6 @@ static int collapse_order;
static bool collapse_order_set;
static int collapse_orders[NR_ORDERS];
static int nr_collapse_orders;
-static int pagemap_fd = -1;
-static int kpageflags_fd = -1;
#define PID_SMAPS "/proc/self/smaps"
#define TEST_FILE "collapse_test_file"
@@ -1330,19 +1328,20 @@ static void mthp_push_target_order(void)
thp_push_settings(&settings);
}
-static bool all_windows_at_order(void *p, size_t len)
+static bool all_windows_at_order(struct mem_ops *ops, void *p, size_t len)
{
- return is_range_backed_by_order(p, len, collapse_order,
- pagemap_fd, kpageflags_fd);
+ size_t window = mthp_window_size();
+
+ return ops->check_huge(p, len, len / window, window);
}
-static bool any_window_at_order(void *p, size_t len)
+static bool any_window_at_order(struct mem_ops *ops, void *p, size_t len)
{
size_t window = mthp_window_size();
char *addr = p;
for (; len >= window; addr += window, len -= window) {
- if (all_windows_at_order(addr, window))
+ if (ops->check_huge(addr, window, 1, window))
return true;
}
return false;
@@ -1358,7 +1357,7 @@ static void collapse_order_single_window(struct collapse_context *c,
p = ops->setup_area(1);
ops->fault(p, window, 2 * window);
- if (any_window_at_order(p, hpage_pmd_size))
+ if (any_window_at_order(ops, p, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected large folio after fault\n");
if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
@@ -1366,9 +1365,9 @@ static void collapse_order_single_window(struct collapse_context *c,
ksft_print_msg("Collapse one fully populated window...");
if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
fail("Timeout");
- else if (all_windows_at_order(p + window, window) &&
- !any_window_at_order(p, window) &&
- !any_window_at_order(p + 2 * window,
+ else if (all_windows_at_order(ops, p + window, window) &&
+ !any_window_at_order(ops, p, window) &&
+ !any_window_at_order(ops, p + 2 * window,
hpage_pmd_size - 2 * window))
success("OK");
else
@@ -1389,7 +1388,7 @@ static void collapse_order_partial_window(struct collapse_context *c,
p = ops->setup_area(1);
ops->fault(p, 0, page_size);
- if (any_window_at_order(p, hpage_pmd_size))
+ if (any_window_at_order(ops, p, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected large folio after fault\n");
if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
@@ -1397,7 +1396,7 @@ static void collapse_order_partial_window(struct collapse_context *c,
ksft_print_msg("Collapse window with single PTE entry present...");
if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
fail("Timeout");
- else if (all_windows_at_order(p, mthp_window_size()))
+ else if (all_windows_at_order(ops, p, mthp_window_size()))
success("OK");
else
fail("Fail");
@@ -1422,7 +1421,7 @@ static void collapse_order_max_ptes_none(struct collapse_context *c,
p = ops->setup_area(1);
ops->fault(p, 0, 2 * window - page_size);
- if (any_window_at_order(p, hpage_pmd_size))
+ if (any_window_at_order(ops, p, hpage_pmd_size))
ksft_exit_fail_msg("Unexpected large folio after fault\n");
if (madvise(p, hpage_pmd_size, MADV_HUGEPAGE))
@@ -1430,8 +1429,8 @@ static void collapse_order_max_ptes_none(struct collapse_context *c,
ksft_print_msg("Collapse full window, not the one missing a page...");
if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
fail("Timeout");
- else if (all_windows_at_order(p, window) &&
- !any_window_at_order(p + window, window))
+ else if (all_windows_at_order(ops, p, window) &&
+ !any_window_at_order(ops, p + window, window))
success("OK");
else
fail("Fail");
@@ -1447,6 +1446,7 @@ static void collapse_order_mixed_sources(struct collapse_context *c,
struct mem_ops *ops)
{
int source_order = anon_order ? anon_order : MIN_MTHP_ORDER;
+ size_t source_size = page_size << source_order;
struct thp_settings settings;
void *p;
@@ -1470,8 +1470,8 @@ static void collapse_order_mixed_sources(struct collapse_context *c,
* The allocator can fall back to smaller folios under fragmentation;
* having nothing to collapse from is not a failure.
*/
- if (!is_range_backed_by_order(p, hpage_pmd_size, source_order,
- pagemap_fd, kpageflags_fd)) {
+ if (!ops->check_huge(p, hpage_pmd_size, hpage_pmd_size / source_size,
+ source_size)) {
ksft_print_msg("No order-%d sources to collapse...", source_order);
skip("Skip");
ops->cleanup_area(p, hpage_pmd_size);
@@ -1486,7 +1486,7 @@ static void collapse_order_mixed_sources(struct collapse_context *c,
source_order);
if (!khugepaged_full_pass(MTHP_PASS_TIMEOUT_S))
fail("Timeout");
- else if (all_windows_at_order(p, hpage_pmd_size))
+ else if (all_windows_at_order(ops, p, hpage_pmd_size))
success("OK");
else
fail("Fail");
@@ -1715,15 +1715,6 @@ int main(int argc, char **argv)
}
}
- if (mthp_khugepaged_context) {
- pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
- if (pagemap_fd < 0)
- ksft_exit_fail_perror("open(/proc/self/pagemap)");
- kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
- if (kpageflags_fd < 0)
- ksft_exit_fail_perror("open(/proc/kpageflags)");
- }
-
setbuf(stdout, NULL);
/*
diff --git a/tools/testing/selftests/mm/khugepaged_sync_check.c b/tools/testing/selftests/mm/khugepaged_sync_check.c
index 28a9b1ff5d44..40cba121a987 100644
--- a/tools/testing/selftests/mm/khugepaged_sync_check.c
+++ b/tools/testing/selftests/mm/khugepaged_sync_check.c
@@ -26,7 +26,6 @@
#define PASS_TIMEOUT_S 30
static int pagemap_fd;
-static int kpageflags_fd;
static int trace_events_fd = -1;
static unsigned long hpage_pmd_size;
@@ -116,8 +115,7 @@ static void one_step(int iteration)
if (!passed)
ksft_exit_fail_msg("khugepaged did not complete a full pass\n");
- collapsed = is_range_backed_by_order(p, window, TARGET_ORDER,
- pagemap_fd, kpageflags_fd);
+ collapsed = check_huge_anon(p, window, 1, window);
attributed = count_attributed(pfns, nr_pages, TARGET_ORDER);
ksft_test_result(collapsed && attributed == 1,
@@ -146,9 +144,6 @@ int main(void)
pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
if (pagemap_fd < 0)
ksft_exit_fail_perror("open(/proc/self/pagemap)");
- kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
- if (kpageflags_fd < 0)
- ksft_exit_skip("open(/proc/kpageflags) requires root\n");
trace_events_fd = tracing_events_open("huge_memory");
if (trace_events_fd < 0)
ksft_exit_skip("huge_memory events require tracefs and root\n");
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 0a4b85e29e14..4c0d0e6f2553 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -401,8 +401,6 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
* @start: start of the range, a multiple of the folio size
* @len: length of the range in bytes, a multiple of the folio size
* @order: the folio order to check for
- * @pagemap_fd: open /proc/<pid>/pagemap of the range's owner
- * @kpageflags_fd: open /proc/kpageflags
*
* Every folio-sized, folio-aligned part of the range must map one folio of
* @order, head to tail, with the head at the start of the part. A part
@@ -411,11 +409,12 @@ char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
*
* Returns: true if the whole range is backed that way, false otherwise.
*/
-bool is_range_backed_by_order(char *start, size_t len, int order,
- int pagemap_fd, int kpageflags_fd)
+static bool is_range_backed_by_order(char *start, size_t len, int order)
{
const unsigned long nr_pages = 1UL << order;
const size_t folio_size = nr_pages * psize();
+ const int pagemap_fd = pagemap_fd_get();
+ const int kpageflags_fd = kpageflags_fd_get();
char *vaddr;
if ((uintptr_t)start % folio_size || len % folio_size)
@@ -448,16 +447,14 @@ bool is_range_backed_by_order(char *start, size_t len, int order,
* size each, with the folio's head at the window start. A folio mapped off
* its alignment or split across two windows counts for neither.
*/
-static int count_windows_at_order(char *start, size_t len, uint64_t hpage_size,
- int pagemap_fd, int kpageflags_fd)
+static int count_windows_at_order(char *start, size_t len, uint64_t hpage_size)
{
const int order = sz2ord(hpage_size, psize());
int nr_windows = 0;
char *addr;
for (addr = start; addr + hpage_size <= start + len; addr += hpage_size) {
- if (is_range_backed_by_order(addr, hpage_size, order,
- pagemap_fd, kpageflags_fd))
+ if (is_range_backed_by_order(addr, hpage_size, order))
nr_windows++;
}
@@ -486,7 +483,7 @@ 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)
{
- int pagemap_fd, kpageflags_fd;
+ int pagemap_fd;
int nr_pmd_mappings = 0;
uint64_t pmd_pagesize, scan_mapping_size;
uint64_t categories;
@@ -505,11 +502,9 @@ static bool __check_huge(void *addr, size_t len, int nr_hpages,
allow_nonpresent = (uint64_t)nr_hpages * hpage_size < len;
pagemap_fd = pagemap_fd_get();
- kpageflags_fd = kpageflags_fd_get();
if (!check_pmd_mapping &&
- nr_hpages != count_windows_at_order(start, len, hpage_size,
- pagemap_fd, kpageflags_fd))
+ nr_hpages != count_windows_at_order(start, len, hpage_size))
return false;
for (; start < end; start += scan_mapping_size) {
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index b5d59729d432..2bf64d5b42aa 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -102,8 +102,6 @@ int gather_folio_orders(char *vaddr_start, size_t len,
int pagemap_fd, int kpageflags_fd, int orders[], int nr_orders);
bool is_backed_by_folio(char *vaddr, int order, int pagemap_fd,
int kpageflags_fd);
-bool is_range_backed_by_order(char *start, size_t len, int order,
- int pagemap_fd, int kpageflags_fd);
int uffd_register(int uffd, void *addr, uint64_t len,
bool miss, bool wp, bool minor);
--
2.54.0