| // 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_ignore_einval(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); | |
| } |