[PATCH 26/53] selftests/mm: hugepage_settings: add APIs for HugeTLB setup and teardown

Mike Rapoport
2026-04-06T14:20:17Z

A lot of tests require free HugeTLB pages. Some need just a few default
huge pages, some need a certain amount of memory available as HugeTLB, and
some just skip lots of tests if huge pages of all supported sizes are not
available.

This all resulted in a huge mess in run_vmtests.sh that sets up some huge
pages, adjusts them later and restores some of the settings if the stars
align.

Add APIs that allow saving the state of HugeTLB and setting up the desired
amount of HugeTLB pages.

Saving the state also registers atexit() callback and signal handler that
will ensure restoration of HugeTLB state.

Since many tests use both HugeTLB and THP, the atexit() callbacks and
signal handler are restoring both.

For kselftest_harness tests that run fixture setups and test in child
processes add a constructor that will save and restore settings in the main
process.

Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
.../testing/selftests/mm/hugepage_settings.c | 176 +++++++++++++++---
.../testing/selftests/mm/hugepage_settings.h | 30 ++-
2 files changed, 181 insertions(+), 25 deletions(-)

diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c
index 4ae7332b5e1b..3eef87e812ba 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -306,31 +306,12 @@ void thp_restore_settings(void)
thp_write_settings(&saved_settings);
}

-static void thp_restore_settings_atexit(void)
+static void __thp_save_settings(void)
{
- thp_restore_settings();
-}
-
-static void thp_restore_settings_sighandler(int sig)
-{
- /* exit() will invoke the thp_restore_settings_atexit handler. */
- exit(KSFT_FAIL);
-}
+ if (!thp_available())
+ return;

-void thp_save_settings(void)
-{
thp_read_settings(&saved_settings);
-
- /*
- * setup exit hooks to make sure THP settings are restored on graceful
- * and error exits and signals
- */
- atexit(thp_restore_settings_atexit);
- signal(SIGTERM, thp_restore_settings_sighandler);
- signal(SIGINT, thp_restore_settings_sighandler);
- signal(SIGHUP, thp_restore_settings_sighandler);
- signal(SIGQUIT, thp_restore_settings_sighandler);
-
thp_settings_saved = true;
}

@@ -399,11 +380,31 @@ bool thp_is_enabled(void)
return mode == 1 || mode == 3;
}

+#define HUGETLB_MAX_NR_PAGESIZES 10
+struct hugetlb_settings {
+ unsigned long free_hugepages[HUGETLB_MAX_NR_PAGESIZES];
+ unsigned long nr_hugepages[HUGETLB_MAX_NR_PAGESIZES];
+ unsigned long sizes[HUGETLB_MAX_NR_PAGESIZES];
+ unsigned long default_size;
+ int nr_sizes;
+};
+
+static struct hugetlb_settings hugetlb_saved_settings;
+bool hugetlb_settings_saved;
+
int detect_hugetlb_page_sizes(unsigned long sizes[], int max)
{
- DIR *dir = opendir("/sys/kernel/mm/hugepages/");
+ static struct hugetlb_settings *settings = &hugetlb_saved_settings;
+ DIR *dir;
int count = 0;

+ if (settings->nr_sizes) {
+ for (count = 0; count < settings->nr_sizes; count++)
+ sizes[count] = settings->sizes[count];
+ return settings->nr_sizes;
+ }
+
+ dir = opendir("/sys/kernel/mm/hugepages/");
if (!dir)
return 0;

@@ -427,11 +428,16 @@ int detect_hugetlb_page_sizes(unsigned long sizes[], int max)

unsigned long default_huge_page_size(void)
{
+ static struct hugetlb_settings *settings = &hugetlb_saved_settings;
unsigned long hps = 0;
char *line = NULL;
size_t linelen = 0;
- FILE *f = fopen("/proc/meminfo", "r");
+ FILE *f;
+
+ if (settings->default_size)
+ return settings->default_size;

+ f = fopen("/proc/meminfo", "r");
if (!f)
return 0;
while (getline(&line, &linelen, f) > 0) {
@@ -479,3 +485,125 @@ unsigned long hugetlb_free_pages(unsigned long size)

return read_num(path);
}
+
+bool hugetlb_setup_default(unsigned long nr)
+{
+ unsigned long size;
+
+ hugetlb_save_settings();
+
+ size = default_huge_page_size();
+ hugetlb_set_nr_pages(size, nr);
+
+ return hugetlb_free_pages(size) == nr;
+}
+
+unsigned long hugetlb_setup(unsigned long nr, unsigned long sizes[],
+ int max)
+{
+ unsigned long enabled[10];
+ int nr_sizes = 0;
+ int nr_enabled;
+
+ nr_enabled = detect_hugetlb_page_sizes(enabled, ARRAY_SIZE(enabled));
+ if (!nr_enabled)
+ return 0;
+
+ if (nr_enabled > max) {
+ ksft_print_msg("detected %d huge page sizes, will only test %d\n", nr_enabled, max);
+ nr_enabled = max;
+ }
+
+ /* If HugeTLB is supported, request 2 HugeTLB pages of every size. */
+ for (int i = 0; i < nr_enabled; i++) {
+ hugetlb_set_nr_pages(enabled[i], nr);
+ if (hugetlb_free_pages(enabled[i]) < nr)
+ continue;
+
+ sizes[nr_sizes++] = enabled[i];
+ }
+
+ return nr_sizes;
+}
+
+static void __hugetlb_save_settings(void)
+{
+ struct hugetlb_settings *settings = &hugetlb_saved_settings;
+ int nr_sizes;
+
+ settings->default_size = default_huge_page_size();
+ if (!settings->default_size)
+ return;
+
+ nr_sizes = detect_hugetlb_page_sizes(settings->sizes,
+ HUGETLB_MAX_NR_PAGESIZES);
+ if (!nr_sizes) {
+ settings->default_size = 0;
+ return;
+ }
+
+ for (int i = 0; i < nr_sizes; i++) {
+ unsigned long sz = settings->sizes[i];
+
+ if (!sz)
+ continue;
+
+ settings->free_hugepages[i] = hugetlb_free_pages(sz);
+ settings->nr_hugepages[i] = hugetlb_nr_pages(sz);
+ }
+
+ settings->nr_sizes = nr_sizes;
+ hugetlb_settings_saved = true;
+}
+
+void hugetlb_restore_settings(void)
+{
+ struct hugetlb_settings *settings = &hugetlb_saved_settings;
+
+ if (!hugetlb_settings_saved || !settings->default_size)
+ return;
+
+ for (int i = 0; i < HUGETLB_MAX_NR_PAGESIZES; i++) {
+ unsigned long sz = settings->sizes[i];
+
+ if (!sz)
+ continue;
+
+ hugetlb_set_nr_pages(sz, settings->nr_hugepages[i]);
+ }
+}
+
+static void hugepage_restore_settings_atexit(void)
+{
+ if (thp_settings_saved)
+ thp_restore_settings();
+ if (hugetlb_settings_saved)
+ hugetlb_restore_settings();
+}
+
+static void hugepage_restore_settings_sighandler(int sig)
+{
+ /* exit() will invoke the hugetlb_restore_settings_atexit handler. */
+ exit(KSFT_FAIL);
+}
+
+void hugepage_save_settings(bool thp, bool hugetlb)
+{
+ if (!thp && !hugetlb)
+ return;
+
+ if (thp)
+ __thp_save_settings();
+ if (hugetlb)
+ __hugetlb_save_settings();
+
+ /*
+ * setup exit hooks to make sure THP settings are restored on graceful
+ * and error exits and signals
+ */
+ atexit(hugepage_restore_settings_atexit);
+ signal(SIGTERM, hugepage_restore_settings_sighandler);
+ signal(SIGINT, hugepage_restore_settings_sighandler);
+ signal(SIGHUP, hugepage_restore_settings_sighandler);
+ signal(SIGQUIT, hugepage_restore_settings_sighandler);
+}
diff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/testing/selftests/mm/hugepage_settings.h
index 57fbf2f57e13..d6a1b4e5f734 100644
--- a/tools/testing/selftests/mm/hugepage_settings.h
+++ b/tools/testing/selftests/mm/hugepage_settings.h
@@ -6,6 +6,8 @@
#include <stddef.h>
#include <stdint.h>

+void hugepage_save_settings(bool thp, bool hugetlb);
+
/* Transparent Huge Pages (THP) */

enum thp_enabled {
@@ -79,7 +81,11 @@ struct thp_settings *thp_current_settings(void);
void thp_push_settings(struct thp_settings *settings);
void thp_pop_settings(void);
void thp_restore_settings(void);
-void thp_save_settings(void);
+
+static inline void thp_save_settings(void)
+{
+ hugepage_save_settings(/* thp = */ true, /* hugetlb = */ false);
+}

void thp_set_read_ahead_path(char *path);
unsigned long thp_supported_orders(void);
@@ -97,6 +103,13 @@ unsigned long hugetlb_nr_pages(unsigned long size);
void hugetlb_set_nr_pages(unsigned long size, unsigned long nr);
unsigned long hugetlb_free_pages(unsigned long size);

+static inline void hugetlb_save_settings(void)
+{
+ hugepage_save_settings(/* thp = */ false, /* hugetlb = */ true);
+}
+
+void hugetlb_restore_settings(void);
+
static inline unsigned long hugetlb_nr_default_pages(void)
{
unsigned long size = default_huge_page_size();
@@ -127,4 +140,19 @@ static inline unsigned long hugetlb_free_default_pages(void)
return hugetlb_free_pages(size);
}

+static inline bool hugetlb_available(void)
+{
+ return default_huge_page_size() != 0;
+}
+
+bool hugetlb_setup_default(unsigned long nr);
+unsigned long hugetlb_setup(unsigned long nr, unsigned long sizes[],
+ int max);
+
+#define HUGETLB_SETUP_DEFAULT_PAGES(nr_pages) \
+static void __attribute__((constructor)) __hugetlb_setup_default(void) \
+{ \
+ hugetlb_setup_default((nr_pages)); \
+}
+
#endif /* __THP_SETTINGS_H__ */
--
2.53.0