linux/tools/testing/selftests/mm/hugepage_settings.c
Mike Rapoport (Microsoft) 505c9605ee selftests/mm: move read_file(), read_num() and write_num() to vm_util
These are useful helpers for writing and reading sysfs and proc files.

Make them available to the tests that don't use thp_settings.

While on it make write_num() use "%lu" instead of "%ld" to match 'unsigned
long num' argument type.

Link: https://lore.kernel.org/20260511162840.375890-30-rppt@kernel.org
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Tested-by: Luiz Capitulino <luizcap@redhat.com>
Tested-by: Sarthak Sharma <sarthak.sharma@arm.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Donet Tom <donettom@linux.ibm.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: John Hubbard <jhubbard@nvidia.com>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Leon Romanovsky <leon@kernel.org>
Cc: Liam Howlett <liam@infradead.org>
Cc: Li Wang <li.wang@linux.dev>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Mark Brown <broonie@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Nico Pache <npache@redhat.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-06-21 11:37:24 -07:00

601 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "vm_util.h"
#include "hugepage_settings.h"
#define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/"
#define MAX_SETTINGS_DEPTH 4
static struct thp_settings settings_stack[MAX_SETTINGS_DEPTH];
static int settings_index;
static struct thp_settings saved_settings;
static char dev_queue_read_ahead_path[PATH_MAX];
static bool thp_settings_saved;
static const char * const thp_enabled_strings[] = {
"never",
"always",
"inherit",
"madvise",
NULL
};
static const char * const thp_defrag_strings[] = {
"always",
"defer",
"defer+madvise",
"madvise",
"never",
NULL
};
static const char * const shmem_enabled_strings[] = {
"never",
"always",
"within_size",
"advise",
"inherit",
"deny",
"force",
NULL
};
int thp_read_string(const char *name, const char * const strings[])
{
char path[PATH_MAX];
char buf[256];
char *c;
int ret;
ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
if (ret >= PATH_MAX) {
printf("%s: Pathname is too long\n", __func__);
exit(EXIT_FAILURE);
}
if (!read_file(path, buf, sizeof(buf))) {
perror(path);
exit(EXIT_FAILURE);
}
c = strchr(buf, '[');
if (!c) {
printf("%s: Parse failure\n", __func__);
exit(EXIT_FAILURE);
}
c++;
memmove(buf, c, sizeof(buf) - (c - buf));
c = strchr(buf, ']');
if (!c) {
printf("%s: Parse failure\n", __func__);
exit(EXIT_FAILURE);
}
*c = '\0';
ret = 0;
while (strings[ret]) {
if (!strcmp(strings[ret], buf))
return ret;
ret++;
}
printf("Failed to parse %s\n", name);
exit(EXIT_FAILURE);
}
void thp_write_string(const char *name, const char *val)
{
char path[PATH_MAX];
int ret;
ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
if (ret >= PATH_MAX) {
printf("%s: Pathname is too long\n", __func__);
exit(EXIT_FAILURE);
}
write_file(path, val, strlen(val) + 1);
}
unsigned long thp_read_num(const char *name)
{
char path[PATH_MAX];
int ret;
ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
if (ret >= PATH_MAX) {
printf("%s: Pathname is too long\n", __func__);
exit(EXIT_FAILURE);
}
return read_num(path);
}
void thp_write_num(const char *name, unsigned long num)
{
char path[PATH_MAX];
int ret;
ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
if (ret >= PATH_MAX) {
printf("%s: Pathname is too long\n", __func__);
exit(EXIT_FAILURE);
}
write_num(path, num);
}
void thp_read_settings(struct thp_settings *settings)
{
unsigned long orders = thp_supported_orders();
unsigned long shmem_orders = thp_shmem_supported_orders();
char path[PATH_MAX];
int i;
*settings = (struct thp_settings) {
.thp_enabled = thp_read_string("enabled", thp_enabled_strings),
.thp_defrag = thp_read_string("defrag", thp_defrag_strings),
.shmem_enabled =
thp_read_string("shmem_enabled", shmem_enabled_strings),
.use_zero_page = thp_read_num("use_zero_page"),
};
settings->khugepaged = (struct khugepaged_settings) {
.defrag = thp_read_num("khugepaged/defrag"),
.alloc_sleep_millisecs =
thp_read_num("khugepaged/alloc_sleep_millisecs"),
.scan_sleep_millisecs =
thp_read_num("khugepaged/scan_sleep_millisecs"),
.max_ptes_none = thp_read_num("khugepaged/max_ptes_none"),
.max_ptes_swap = thp_read_num("khugepaged/max_ptes_swap"),
.max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"),
.pages_to_scan = thp_read_num("khugepaged/pages_to_scan"),
};
if (dev_queue_read_ahead_path[0])
settings->read_ahead_kb = read_num(dev_queue_read_ahead_path);
for (i = 0; i < NR_ORDERS; i++) {
if (!((1 << i) & orders)) {
settings->hugepages[i].enabled = THP_NEVER;
continue;
}
snprintf(path, PATH_MAX, "hugepages-%ukB/enabled",
(getpagesize() >> 10) << i);
settings->hugepages[i].enabled =
thp_read_string(path, thp_enabled_strings);
}
for (i = 0; i < NR_ORDERS; i++) {
if (!((1 << i) & shmem_orders)) {
settings->shmem_hugepages[i].enabled = SHMEM_NEVER;
continue;
}
snprintf(path, PATH_MAX, "hugepages-%ukB/shmem_enabled",
(getpagesize() >> 10) << i);
settings->shmem_hugepages[i].enabled =
thp_read_string(path, shmem_enabled_strings);
}
}
void thp_write_settings(struct thp_settings *settings)
{
struct khugepaged_settings *khugepaged = &settings->khugepaged;
unsigned long orders = thp_supported_orders();
unsigned long shmem_orders = thp_shmem_supported_orders();
char path[PATH_MAX];
int enabled;
int i;
thp_write_string("enabled", thp_enabled_strings[settings->thp_enabled]);
thp_write_string("defrag", thp_defrag_strings[settings->thp_defrag]);
thp_write_string("shmem_enabled",
shmem_enabled_strings[settings->shmem_enabled]);
thp_write_num("use_zero_page", settings->use_zero_page);
thp_write_num("khugepaged/defrag", khugepaged->defrag);
thp_write_num("khugepaged/alloc_sleep_millisecs",
khugepaged->alloc_sleep_millisecs);
thp_write_num("khugepaged/scan_sleep_millisecs",
khugepaged->scan_sleep_millisecs);
thp_write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
thp_write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
if (dev_queue_read_ahead_path[0])
write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
for (i = 0; i < NR_ORDERS; i++) {
if (!((1 << i) & orders))
continue;
snprintf(path, PATH_MAX, "hugepages-%ukB/enabled",
(getpagesize() >> 10) << i);
enabled = settings->hugepages[i].enabled;
thp_write_string(path, thp_enabled_strings[enabled]);
}
for (i = 0; i < NR_ORDERS; i++) {
if (!((1 << i) & shmem_orders))
continue;
snprintf(path, PATH_MAX, "hugepages-%ukB/shmem_enabled",
(getpagesize() >> 10) << i);
enabled = settings->shmem_hugepages[i].enabled;
thp_write_string(path, shmem_enabled_strings[enabled]);
}
}
struct thp_settings *thp_current_settings(void)
{
if (!settings_index) {
printf("Fail: No settings set");
exit(EXIT_FAILURE);
}
return settings_stack + settings_index - 1;
}
void thp_push_settings(struct thp_settings *settings)
{
if (settings_index >= MAX_SETTINGS_DEPTH) {
printf("Fail: Settings stack exceeded");
exit(EXIT_FAILURE);
}
settings_stack[settings_index++] = *settings;
thp_write_settings(thp_current_settings());
}
void thp_pop_settings(void)
{
if (settings_index <= 0) {
printf("Fail: Settings stack empty");
exit(EXIT_FAILURE);
}
--settings_index;
thp_write_settings(thp_current_settings());
}
void thp_restore_settings(void)
{
if (thp_settings_saved)
thp_write_settings(&saved_settings);
}
static void __thp_save_settings(void)
{
if (!thp_available())
return;
if (thp_settings_saved)
return;
thp_read_settings(&saved_settings);
thp_settings_saved = true;
}
void thp_set_read_ahead_path(char *path)
{
if (!path) {
dev_queue_read_ahead_path[0] = '\0';
return;
}
strncpy(dev_queue_read_ahead_path, path,
sizeof(dev_queue_read_ahead_path));
dev_queue_read_ahead_path[sizeof(dev_queue_read_ahead_path) - 1] = '\0';
}
static unsigned long __thp_supported_orders(bool is_shmem)
{
unsigned long orders = 0;
char path[PATH_MAX];
char buf[256];
int ret, i;
char anon_dir[] = "enabled";
char shmem_dir[] = "shmem_enabled";
for (i = 0; i < NR_ORDERS; i++) {
ret = snprintf(path, PATH_MAX, THP_SYSFS "hugepages-%ukB/%s",
(getpagesize() >> 10) << i, is_shmem ? shmem_dir : anon_dir);
if (ret >= PATH_MAX) {
printf("%s: Pathname is too long\n", __func__);
exit(EXIT_FAILURE);
}
ret = read_file(path, buf, sizeof(buf));
if (ret)
orders |= 1UL << i;
}
return orders;
}
unsigned long thp_supported_orders(void)
{
return __thp_supported_orders(false);
}
unsigned long thp_shmem_supported_orders(void)
{
return __thp_supported_orders(true);
}
bool thp_available(void)
{
if (access(THP_SYSFS, F_OK) != 0)
return false;
return true;
}
bool thp_is_enabled(void)
{
if (!thp_available())
return false;
int mode = thp_read_string("enabled", thp_enabled_strings);
/* THP is considered enabled if it's either "always" or "madvise" */
return mode == 1 || mode == 3;
}
#define HUGETLB_MAX_NR_PAGESIZES 10
struct hugetlb_settings {
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;
static bool hugetlb_settings_saved;
int detect_hugetlb_page_sizes(unsigned long sizes[], int max)
{
static struct hugetlb_settings *settings = &hugetlb_saved_settings;
DIR *dir;
int count = 0;
if (settings->nr_sizes) {
if (settings->nr_sizes < max)
max = settings->nr_sizes;
for (count = 0; count < max; count++)
sizes[count] = settings->sizes[count];
return count;
}
dir = opendir("/sys/kernel/mm/hugepages/");
if (!dir)
return 0;
while (count < max) {
struct dirent *entry = readdir(dir);
size_t kb;
if (!entry)
break;
if (entry->d_type != DT_DIR)
continue;
if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1)
continue;
sizes[count++] = kb * 1024;
ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n",
kb);
}
closedir(dir);
return count;
}
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;
if (settings->default_size)
return settings->default_size;
f = fopen("/proc/meminfo", "r");
if (!f)
return 0;
while (getline(&line, &linelen, f) > 0) {
if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) {
hps <<= 10;
break;
}
}
free(line);
fclose(f);
return hps;
}
static void hugetlb_sysfs_path(char *buf, size_t buflen,
unsigned long size, const char *attr)
{
snprintf(buf, buflen, "/sys/kernel/mm/hugepages/hugepages-%lukB/%s",
size / 1024, attr);
}
unsigned long hugetlb_nr_pages(unsigned long size)
{
char path[PATH_MAX];
hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
return read_num(path);
}
void hugetlb_set_nr_pages(unsigned long size, unsigned long nr)
{
char path[PATH_MAX];
hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
write_num(path, nr);
}
unsigned long hugetlb_free_pages(unsigned long size)
{
char path[PATH_MAX];
hugetlb_sysfs_path(path, sizeof(path), size, "free_hugepages");
return read_num(path);
}
static bool __hugetlb_setup(unsigned long size, unsigned long nr)
{
unsigned long free = hugetlb_free_pages(size);
unsigned long total = hugetlb_nr_pages(size);
if (free >= nr)
return true;
hugetlb_set_nr_pages(size, total + (nr - free));
return hugetlb_free_pages(size) >= nr;
}
bool hugetlb_setup_default(unsigned long nr)
{
unsigned long size;
hugetlb_save_settings();
size = default_huge_page_size();
if (!size)
return false;
return __hugetlb_setup(size, nr);
}
bool hugetlb_setup_default_exact(unsigned long nr)
{
unsigned long size;
hugetlb_save_settings();
size = default_huge_page_size();
if (!size)
return false;
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;
hugetlb_save_settings();
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;
}
/* request nr HugeTLB pages of every size. */
for (int i = 0; i < nr_enabled; i++) {
if (!__hugetlb_setup(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;
if (hugetlb_settings_saved)
return;
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->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 hugepage_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 and HugeTLB 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);
}