Message ID | 20231122162950.3854897-7-ryan.roberts@arm.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Small-sized THP for anonymous memory | expand |
Ryan Roberts <ryan.roberts@arm.com> writes: > The khugepaged test has a useful framework for save/restore/pop/push of > all thp settings via the sysfs interface. This will be useful to > explicitly control small-sized THP settings in other tests, so let's > move it out of khugepaged and into its own thp_settings.[c|h] utility. > > Signed-off-by: Ryan Roberts <ryan.roberts@arm.com> I've only glanced at the code as I assume it is a straight forward cut-and-paste with no behavioural change. At least I didn't observe any change running the khugepage test on my x86_64 development machine so feel free to add: Tested-by: Alistair Popple <apopple@nvidia.com> > --- > tools/testing/selftests/mm/Makefile | 4 +- > tools/testing/selftests/mm/khugepaged.c | 346 ++-------------------- > tools/testing/selftests/mm/thp_settings.c | 296 ++++++++++++++++++ > tools/testing/selftests/mm/thp_settings.h | 71 +++++ > 4 files changed, 391 insertions(+), 326 deletions(-) > create mode 100644 tools/testing/selftests/mm/thp_settings.c > create mode 100644 tools/testing/selftests/mm/thp_settings.h > > diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile > index 78dfec8bc676..a3eb20c186e7 100644 > --- a/tools/testing/selftests/mm/Makefile > +++ b/tools/testing/selftests/mm/Makefile > @@ -117,8 +117,8 @@ TEST_FILES += va_high_addr_switch.sh > > include ../lib.mk > > -$(TEST_GEN_PROGS): vm_util.c > -$(TEST_GEN_FILES): vm_util.c > +$(TEST_GEN_PROGS): vm_util.c thp_settings.c > +$(TEST_GEN_FILES): vm_util.c thp_settings.c > > $(OUTPUT)/uffd-stress: uffd-common.c > $(OUTPUT)/uffd-unit-tests: uffd-common.c > diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c > index fc47a1c4944c..b15e7fd70176 100644 > --- a/tools/testing/selftests/mm/khugepaged.c > +++ b/tools/testing/selftests/mm/khugepaged.c > @@ -22,13 +22,13 @@ > #include "linux/magic.h" > > #include "vm_util.h" > +#include "thp_settings.h" > > #define BASE_ADDR ((void *)(1UL << 30)) > static unsigned long hpage_pmd_size; > static unsigned long page_size; > static int hpage_pmd_nr; > > -#define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/" > #define PID_SMAPS "/proc/self/smaps" > #define TEST_FILE "collapse_test_file" > > @@ -71,78 +71,7 @@ struct file_info { > }; > > static struct file_info finfo; > - > -enum thp_enabled { > - THP_ALWAYS, > - THP_MADVISE, > - THP_NEVER, > -}; > - > -static const char *thp_enabled_strings[] = { > - "always", > - "madvise", > - "never", > - NULL > -}; > - > -enum thp_defrag { > - THP_DEFRAG_ALWAYS, > - THP_DEFRAG_DEFER, > - THP_DEFRAG_DEFER_MADVISE, > - THP_DEFRAG_MADVISE, > - THP_DEFRAG_NEVER, > -}; > - > -static const char *thp_defrag_strings[] = { > - "always", > - "defer", > - "defer+madvise", > - "madvise", > - "never", > - NULL > -}; > - > -enum shmem_enabled { > - SHMEM_ALWAYS, > - SHMEM_WITHIN_SIZE, > - SHMEM_ADVISE, > - SHMEM_NEVER, > - SHMEM_DENY, > - SHMEM_FORCE, > -}; > - > -static const char *shmem_enabled_strings[] = { > - "always", > - "within_size", > - "advise", > - "never", > - "deny", > - "force", > - NULL > -}; > - > -struct khugepaged_settings { > - bool defrag; > - unsigned int alloc_sleep_millisecs; > - unsigned int scan_sleep_millisecs; > - unsigned int max_ptes_none; > - unsigned int max_ptes_swap; > - unsigned int max_ptes_shared; > - unsigned long pages_to_scan; > -}; > - > -struct settings { > - enum thp_enabled thp_enabled; > - enum thp_defrag thp_defrag; > - enum shmem_enabled shmem_enabled; > - bool use_zero_page; > - struct khugepaged_settings khugepaged; > - unsigned long read_ahead_kb; > -}; > - > -static struct settings saved_settings; > static bool skip_settings_restore; > - > static int exit_status; > > static void success(const char *msg) > @@ -161,226 +90,13 @@ static void skip(const char *msg) > printf(" \e[33m%s\e[0m\n", msg); > } > > -static int read_file(const char *path, char *buf, size_t buflen) > -{ > - int fd; > - ssize_t numread; > - > - fd = open(path, O_RDONLY); > - if (fd == -1) > - return 0; > - > - numread = read(fd, buf, buflen - 1); > - if (numread < 1) { > - close(fd); > - return 0; > - } > - > - buf[numread] = '\0'; > - close(fd); > - > - return (unsigned int) numread; > -} > - > -static int write_file(const char *path, const char *buf, size_t buflen) > -{ > - int fd; > - ssize_t numwritten; > - > - fd = open(path, O_WRONLY); > - if (fd == -1) { > - printf("open(%s)\n", path); > - exit(EXIT_FAILURE); > - return 0; > - } > - > - numwritten = write(fd, buf, buflen - 1); > - close(fd); > - if (numwritten < 1) { > - printf("write(%s)\n", buf); > - exit(EXIT_FAILURE); > - return 0; > - } > - > - return (unsigned int) numwritten; > -} > - > -static int read_string(const char *name, const char *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); > -} > - > -static void 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); > - } > - > - if (!write_file(path, val, strlen(val) + 1)) { > - perror(path); > - exit(EXIT_FAILURE); > - } > -} > - > -static const unsigned long _read_num(const char *path) > -{ > - char buf[21]; > - > - if (read_file(path, buf, sizeof(buf)) < 0) { > - perror("read_file(read_num)"); > - exit(EXIT_FAILURE); > - } > - > - return strtoul(buf, NULL, 10); > -} > - > -static const unsigned long 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); > -} > - > -static void _write_num(const char *path, unsigned long num) > -{ > - char buf[21]; > - > - sprintf(buf, "%ld", num); > - if (!write_file(path, buf, strlen(buf) + 1)) { > - perror(path); > - exit(EXIT_FAILURE); > - } > -} > - > -static void 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); > -} > - > -static void write_settings(struct settings *settings) > -{ > - struct khugepaged_settings *khugepaged = &settings->khugepaged; > - > - write_string("enabled", thp_enabled_strings[settings->thp_enabled]); > - write_string("defrag", thp_defrag_strings[settings->thp_defrag]); > - write_string("shmem_enabled", > - shmem_enabled_strings[settings->shmem_enabled]); > - write_num("use_zero_page", settings->use_zero_page); > - > - write_num("khugepaged/defrag", khugepaged->defrag); > - write_num("khugepaged/alloc_sleep_millisecs", > - khugepaged->alloc_sleep_millisecs); > - write_num("khugepaged/scan_sleep_millisecs", > - khugepaged->scan_sleep_millisecs); > - write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none); > - write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap); > - write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared); > - write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan); > - > - if (file_ops && finfo.type == VMA_FILE) > - _write_num(finfo.dev_queue_read_ahead_path, > - settings->read_ahead_kb); > -} > - > -#define MAX_SETTINGS_DEPTH 4 > -static struct settings settings_stack[MAX_SETTINGS_DEPTH]; > -static int settings_index; > - > -static struct settings *current_settings(void) > -{ > - if (!settings_index) { > - printf("Fail: No settings set"); > - exit(EXIT_FAILURE); > - } > - return settings_stack + settings_index - 1; > -} > - > -static void push_settings(struct settings *settings) > -{ > - if (settings_index >= MAX_SETTINGS_DEPTH) { > - printf("Fail: Settings stack exceeded"); > - exit(EXIT_FAILURE); > - } > - settings_stack[settings_index++] = *settings; > - write_settings(current_settings()); > -} > - > -static void pop_settings(void) > -{ > - if (settings_index <= 0) { > - printf("Fail: Settings stack empty"); > - exit(EXIT_FAILURE); > - } > - --settings_index; > - write_settings(current_settings()); > -} > - > static void restore_settings_atexit(void) > { > if (skip_settings_restore) > return; > > printf("Restore THP and khugepaged settings..."); > - write_settings(&saved_settings); > + thp_restore_settings(); > success("OK"); > > skip_settings_restore = true; > @@ -395,27 +111,9 @@ static void restore_settings(int sig) > static void save_settings(void) > { > printf("Save THP and khugepaged settings..."); > - saved_settings = (struct settings) { > - .thp_enabled = read_string("enabled", thp_enabled_strings), > - .thp_defrag = read_string("defrag", thp_defrag_strings), > - .shmem_enabled = > - read_string("shmem_enabled", shmem_enabled_strings), > - .use_zero_page = read_num("use_zero_page"), > - }; > - saved_settings.khugepaged = (struct khugepaged_settings) { > - .defrag = read_num("khugepaged/defrag"), > - .alloc_sleep_millisecs = > - read_num("khugepaged/alloc_sleep_millisecs"), > - .scan_sleep_millisecs = > - read_num("khugepaged/scan_sleep_millisecs"), > - .max_ptes_none = read_num("khugepaged/max_ptes_none"), > - .max_ptes_swap = read_num("khugepaged/max_ptes_swap"), > - .max_ptes_shared = read_num("khugepaged/max_ptes_shared"), > - .pages_to_scan = read_num("khugepaged/pages_to_scan"), > - }; > if (file_ops && finfo.type == VMA_FILE) > - saved_settings.read_ahead_kb = > - _read_num(finfo.dev_queue_read_ahead_path); > + thp_set_read_ahead_path(finfo.dev_queue_read_ahead_path); > + thp_save_settings(); > > success("OK"); > > @@ -798,7 +496,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, > struct mem_ops *ops, bool expect) > { > int ret; > - struct settings settings = *current_settings(); > + struct thp_settings settings = *thp_current_settings(); > > printf("%s...", msg); > > @@ -808,7 +506,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, > */ > settings.thp_enabled = THP_NEVER; > settings.shmem_enabled = SHMEM_NEVER; > - push_settings(&settings); > + thp_push_settings(&settings); > > /* Clear VM_NOHUGEPAGE */ > madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); > @@ -820,7 +518,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, > else > success("OK"); > > - pop_settings(); > + thp_pop_settings(); > } > > static void madvise_collapse(const char *msg, char *p, int nr_hpages, > @@ -850,13 +548,13 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages, > madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); > > /* Wait until the second full_scan completed */ > - full_scans = read_num("khugepaged/full_scans") + 2; > + full_scans = thp_read_num("khugepaged/full_scans") + 2; > > printf("%s...", msg); > while (timeout--) { > if (ops->check_huge(p, nr_hpages)) > break; > - if (read_num("khugepaged/full_scans") >= full_scans) > + if (thp_read_num("khugepaged/full_scans") >= full_scans) > break; > printf("."); > usleep(TICK); > @@ -911,11 +609,11 @@ static bool is_tmpfs(struct mem_ops *ops) > > static void alloc_at_fault(void) > { > - struct settings settings = *current_settings(); > + struct thp_settings settings = *thp_current_settings(); > char *p; > > settings.thp_enabled = THP_ALWAYS; > - push_settings(&settings); > + thp_push_settings(&settings); > > p = alloc_mapping(1); > *p = 1; > @@ -925,7 +623,7 @@ static void alloc_at_fault(void) > else > fail("Fail"); > > - pop_settings(); > + thp_pop_settings(); > > madvise(p, page_size, MADV_DONTNEED); > printf("Split huge PMD on MADV_DONTNEED..."); > @@ -973,11 +671,11 @@ static void collapse_single_pte_entry(struct collapse_context *c, struct mem_ops > static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *ops) > { > int max_ptes_none = hpage_pmd_nr / 2; > - struct settings settings = *current_settings(); > + struct thp_settings settings = *thp_current_settings(); > void *p; > > settings.khugepaged.max_ptes_none = max_ptes_none; > - push_settings(&settings); > + thp_push_settings(&settings); > > p = ops->setup_area(1); > > @@ -1002,7 +700,7 @@ static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *o > } > skip: > ops->cleanup_area(p, hpage_pmd_size); > - pop_settings(); > + thp_pop_settings(); > } > > static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_ops *ops) > @@ -1033,7 +731,7 @@ static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_op > > static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *ops) > { > - int max_ptes_swap = read_num("khugepaged/max_ptes_swap"); > + int max_ptes_swap = thp_read_num("khugepaged/max_ptes_swap"); > void *p; > > p = ops->setup_area(1); > @@ -1250,11 +948,11 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o > fail("Fail"); > ops->fault(p, 0, page_size); > > - write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); > + thp_write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); > c->collapse("Collapse PTE table full of compound pages in child", > p, 1, ops, true); > - write_num("khugepaged/max_ptes_shared", > - current_settings()->khugepaged.max_ptes_shared); > + thp_write_num("khugepaged/max_ptes_shared", > + thp_current_settings()->khugepaged.max_ptes_shared); > > validate_memory(p, 0, hpage_pmd_size); > ops->cleanup_area(p, hpage_pmd_size); > @@ -1275,7 +973,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o > > static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops *ops) > { > - int max_ptes_shared = read_num("khugepaged/max_ptes_shared"); > + int max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"); > int wstatus; > void *p; > > @@ -1443,7 +1141,7 @@ static void parse_test_type(int argc, const char **argv) > > int main(int argc, const char **argv) > { > - struct settings default_settings = { > + struct thp_settings default_settings = { > .thp_enabled = THP_MADVISE, > .thp_defrag = THP_DEFRAG_ALWAYS, > .shmem_enabled = SHMEM_ADVISE, > @@ -1484,7 +1182,7 @@ int main(int argc, const char **argv) > default_settings.khugepaged.pages_to_scan = hpage_pmd_nr * 8; > > save_settings(); > - push_settings(&default_settings); > + thp_push_settings(&default_settings); > > alloc_at_fault(); > > diff --git a/tools/testing/selftests/mm/thp_settings.c b/tools/testing/selftests/mm/thp_settings.c > new file mode 100644 > index 000000000000..5e8ec792cac7 > --- /dev/null > +++ b/tools/testing/selftests/mm/thp_settings.c > @@ -0,0 +1,296 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <fcntl.h> > +#include <limits.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > + > +#include "thp_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 const char * const thp_enabled_strings[] = { > + "always", > + "madvise", > + "never", > + NULL > +}; > + > +static const char * const thp_defrag_strings[] = { > + "always", > + "defer", > + "defer+madvise", > + "madvise", > + "never", > + NULL > +}; > + > +static const char * const shmem_enabled_strings[] = { > + "always", > + "within_size", > + "advise", > + "never", > + "deny", > + "force", > + NULL > +}; > + > +int read_file(const char *path, char *buf, size_t buflen) > +{ > + int fd; > + ssize_t numread; > + > + fd = open(path, O_RDONLY); > + if (fd == -1) > + return 0; > + > + numread = read(fd, buf, buflen - 1); > + if (numread < 1) { > + close(fd); > + return 0; > + } > + > + buf[numread] = '\0'; > + close(fd); > + > + return (unsigned int) numread; > +} > + > +int write_file(const char *path, const char *buf, size_t buflen) > +{ > + int fd; > + ssize_t numwritten; > + > + fd = open(path, O_WRONLY); > + if (fd == -1) { > + printf("open(%s)\n", path); > + exit(EXIT_FAILURE); > + return 0; > + } > + > + numwritten = write(fd, buf, buflen - 1); > + close(fd); > + if (numwritten < 1) { > + printf("write(%s)\n", buf); > + exit(EXIT_FAILURE); > + return 0; > + } > + > + return (unsigned int) numwritten; > +} > + > +const unsigned long read_num(const char *path) > +{ > + char buf[21]; > + > + if (read_file(path, buf, sizeof(buf)) < 0) { > + perror("read_file()"); > + exit(EXIT_FAILURE); > + } > + > + return strtoul(buf, NULL, 10); > +} > + > +void write_num(const char *path, unsigned long num) > +{ > + char buf[21]; > + > + sprintf(buf, "%ld", num); > + if (!write_file(path, buf, strlen(buf) + 1)) { > + perror(path); > + exit(EXIT_FAILURE); > + } > +} > + > +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); > + } > + > + if (!write_file(path, val, strlen(val) + 1)) { > + perror(path); > + exit(EXIT_FAILURE); > + } > +} > + > +const 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) > +{ > + *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); > +} > + > +void thp_write_settings(struct thp_settings *settings) > +{ > + struct khugepaged_settings *khugepaged = &settings->khugepaged; > + > + 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); > +} > + > +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) > +{ > + thp_write_settings(&saved_settings); > +} > + > +void thp_save_settings(void) > +{ > + thp_read_settings(&saved_settings); > +} > + > +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'; > +} > diff --git a/tools/testing/selftests/mm/thp_settings.h b/tools/testing/selftests/mm/thp_settings.h > new file mode 100644 > index 000000000000..ff3d98c30617 > --- /dev/null > +++ b/tools/testing/selftests/mm/thp_settings.h > @@ -0,0 +1,71 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef __THP_SETTINGS_H__ > +#define __THP_SETTINGS_H__ > + > +#include <stdbool.h> > +#include <stddef.h> > +#include <stdint.h> > + > +enum thp_enabled { > + THP_ALWAYS, > + THP_MADVISE, > + THP_NEVER, > +}; > + > +enum thp_defrag { > + THP_DEFRAG_ALWAYS, > + THP_DEFRAG_DEFER, > + THP_DEFRAG_DEFER_MADVISE, > + THP_DEFRAG_MADVISE, > + THP_DEFRAG_NEVER, > +}; > + > +enum shmem_enabled { > + SHMEM_ALWAYS, > + SHMEM_WITHIN_SIZE, > + SHMEM_ADVISE, > + SHMEM_NEVER, > + SHMEM_DENY, > + SHMEM_FORCE, > +}; > + > +struct khugepaged_settings { > + bool defrag; > + unsigned int alloc_sleep_millisecs; > + unsigned int scan_sleep_millisecs; > + unsigned int max_ptes_none; > + unsigned int max_ptes_swap; > + unsigned int max_ptes_shared; > + unsigned long pages_to_scan; > +}; > + > +struct thp_settings { > + enum thp_enabled thp_enabled; > + enum thp_defrag thp_defrag; > + enum shmem_enabled shmem_enabled; > + bool use_zero_page; > + struct khugepaged_settings khugepaged; > + unsigned long read_ahead_kb; > +}; > + > +int read_file(const char *path, char *buf, size_t buflen); > +int write_file(const char *path, const char *buf, size_t buflen); > +const unsigned long read_num(const char *path); > +void write_num(const char *path, unsigned long num); > + > +int thp_read_string(const char *name, const char * const strings[]); > +void thp_write_string(const char *name, const char *val); > +const unsigned long thp_read_num(const char *name); > +void thp_write_num(const char *name, unsigned long num); > + > +void thp_write_settings(struct thp_settings *settings); > +void thp_read_settings(struct thp_settings *settings); > +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); > + > +void thp_set_read_ahead_path(char *path); > + > +#endif /* __THP_SETTINGS_H__ */
On 23/11/2023 06:07, Alistair Popple wrote: > > Ryan Roberts <ryan.roberts@arm.com> writes: > >> The khugepaged test has a useful framework for save/restore/pop/push of >> all thp settings via the sysfs interface. This will be useful to >> explicitly control small-sized THP settings in other tests, so let's >> move it out of khugepaged and into its own thp_settings.[c|h] utility. >> >> Signed-off-by: Ryan Roberts <ryan.roberts@arm.com> > > I've only glanced at the code as I assume it is a straight forward > cut-and-paste with no behavioural change. At least I didn't observe any > change running the khugepage test on my x86_64 development machine so > feel free to add: > > Tested-by: Alistair Popple <apopple@nvidia.com> Thanks! Its pretty much just a cut-and-paste; I did prefix the function names with "thp_" since they are now in global namespace. And I moved some khugepaged test-specific logging out of them and into the higher level test code. And I added thp_set_read_ahead_path() to decouple from the test code. But intended to all be equivalent. > >> --- >> tools/testing/selftests/mm/Makefile | 4 +- >> tools/testing/selftests/mm/khugepaged.c | 346 ++-------------------- >> tools/testing/selftests/mm/thp_settings.c | 296 ++++++++++++++++++ >> tools/testing/selftests/mm/thp_settings.h | 71 +++++ >> 4 files changed, 391 insertions(+), 326 deletions(-) >> create mode 100644 tools/testing/selftests/mm/thp_settings.c >> create mode 100644 tools/testing/selftests/mm/thp_settings.h >> >> diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile >> index 78dfec8bc676..a3eb20c186e7 100644 >> --- a/tools/testing/selftests/mm/Makefile >> +++ b/tools/testing/selftests/mm/Makefile >> @@ -117,8 +117,8 @@ TEST_FILES += va_high_addr_switch.sh >> >> include ../lib.mk >> >> -$(TEST_GEN_PROGS): vm_util.c >> -$(TEST_GEN_FILES): vm_util.c >> +$(TEST_GEN_PROGS): vm_util.c thp_settings.c >> +$(TEST_GEN_FILES): vm_util.c thp_settings.c >> >> $(OUTPUT)/uffd-stress: uffd-common.c >> $(OUTPUT)/uffd-unit-tests: uffd-common.c >> diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c >> index fc47a1c4944c..b15e7fd70176 100644 >> --- a/tools/testing/selftests/mm/khugepaged.c >> +++ b/tools/testing/selftests/mm/khugepaged.c >> @@ -22,13 +22,13 @@ >> #include "linux/magic.h" >> >> #include "vm_util.h" >> +#include "thp_settings.h" >> >> #define BASE_ADDR ((void *)(1UL << 30)) >> static unsigned long hpage_pmd_size; >> static unsigned long page_size; >> static int hpage_pmd_nr; >> >> -#define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/" >> #define PID_SMAPS "/proc/self/smaps" >> #define TEST_FILE "collapse_test_file" >> >> @@ -71,78 +71,7 @@ struct file_info { >> }; >> >> static struct file_info finfo; >> - >> -enum thp_enabled { >> - THP_ALWAYS, >> - THP_MADVISE, >> - THP_NEVER, >> -}; >> - >> -static const char *thp_enabled_strings[] = { >> - "always", >> - "madvise", >> - "never", >> - NULL >> -}; >> - >> -enum thp_defrag { >> - THP_DEFRAG_ALWAYS, >> - THP_DEFRAG_DEFER, >> - THP_DEFRAG_DEFER_MADVISE, >> - THP_DEFRAG_MADVISE, >> - THP_DEFRAG_NEVER, >> -}; >> - >> -static const char *thp_defrag_strings[] = { >> - "always", >> - "defer", >> - "defer+madvise", >> - "madvise", >> - "never", >> - NULL >> -}; >> - >> -enum shmem_enabled { >> - SHMEM_ALWAYS, >> - SHMEM_WITHIN_SIZE, >> - SHMEM_ADVISE, >> - SHMEM_NEVER, >> - SHMEM_DENY, >> - SHMEM_FORCE, >> -}; >> - >> -static const char *shmem_enabled_strings[] = { >> - "always", >> - "within_size", >> - "advise", >> - "never", >> - "deny", >> - "force", >> - NULL >> -}; >> - >> -struct khugepaged_settings { >> - bool defrag; >> - unsigned int alloc_sleep_millisecs; >> - unsigned int scan_sleep_millisecs; >> - unsigned int max_ptes_none; >> - unsigned int max_ptes_swap; >> - unsigned int max_ptes_shared; >> - unsigned long pages_to_scan; >> -}; >> - >> -struct settings { >> - enum thp_enabled thp_enabled; >> - enum thp_defrag thp_defrag; >> - enum shmem_enabled shmem_enabled; >> - bool use_zero_page; >> - struct khugepaged_settings khugepaged; >> - unsigned long read_ahead_kb; >> -}; >> - >> -static struct settings saved_settings; >> static bool skip_settings_restore; >> - >> static int exit_status; >> >> static void success(const char *msg) >> @@ -161,226 +90,13 @@ static void skip(const char *msg) >> printf(" \e[33m%s\e[0m\n", msg); >> } >> >> -static int read_file(const char *path, char *buf, size_t buflen) >> -{ >> - int fd; >> - ssize_t numread; >> - >> - fd = open(path, O_RDONLY); >> - if (fd == -1) >> - return 0; >> - >> - numread = read(fd, buf, buflen - 1); >> - if (numread < 1) { >> - close(fd); >> - return 0; >> - } >> - >> - buf[numread] = '\0'; >> - close(fd); >> - >> - return (unsigned int) numread; >> -} >> - >> -static int write_file(const char *path, const char *buf, size_t buflen) >> -{ >> - int fd; >> - ssize_t numwritten; >> - >> - fd = open(path, O_WRONLY); >> - if (fd == -1) { >> - printf("open(%s)\n", path); >> - exit(EXIT_FAILURE); >> - return 0; >> - } >> - >> - numwritten = write(fd, buf, buflen - 1); >> - close(fd); >> - if (numwritten < 1) { >> - printf("write(%s)\n", buf); >> - exit(EXIT_FAILURE); >> - return 0; >> - } >> - >> - return (unsigned int) numwritten; >> -} >> - >> -static int read_string(const char *name, const char *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); >> -} >> - >> -static void 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); >> - } >> - >> - if (!write_file(path, val, strlen(val) + 1)) { >> - perror(path); >> - exit(EXIT_FAILURE); >> - } >> -} >> - >> -static const unsigned long _read_num(const char *path) >> -{ >> - char buf[21]; >> - >> - if (read_file(path, buf, sizeof(buf)) < 0) { >> - perror("read_file(read_num)"); >> - exit(EXIT_FAILURE); >> - } >> - >> - return strtoul(buf, NULL, 10); >> -} >> - >> -static const unsigned long 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); >> -} >> - >> -static void _write_num(const char *path, unsigned long num) >> -{ >> - char buf[21]; >> - >> - sprintf(buf, "%ld", num); >> - if (!write_file(path, buf, strlen(buf) + 1)) { >> - perror(path); >> - exit(EXIT_FAILURE); >> - } >> -} >> - >> -static void 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); >> -} >> - >> -static void write_settings(struct settings *settings) >> -{ >> - struct khugepaged_settings *khugepaged = &settings->khugepaged; >> - >> - write_string("enabled", thp_enabled_strings[settings->thp_enabled]); >> - write_string("defrag", thp_defrag_strings[settings->thp_defrag]); >> - write_string("shmem_enabled", >> - shmem_enabled_strings[settings->shmem_enabled]); >> - write_num("use_zero_page", settings->use_zero_page); >> - >> - write_num("khugepaged/defrag", khugepaged->defrag); >> - write_num("khugepaged/alloc_sleep_millisecs", >> - khugepaged->alloc_sleep_millisecs); >> - write_num("khugepaged/scan_sleep_millisecs", >> - khugepaged->scan_sleep_millisecs); >> - write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none); >> - write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap); >> - write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared); >> - write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan); >> - >> - if (file_ops && finfo.type == VMA_FILE) >> - _write_num(finfo.dev_queue_read_ahead_path, >> - settings->read_ahead_kb); >> -} >> - >> -#define MAX_SETTINGS_DEPTH 4 >> -static struct settings settings_stack[MAX_SETTINGS_DEPTH]; >> -static int settings_index; >> - >> -static struct settings *current_settings(void) >> -{ >> - if (!settings_index) { >> - printf("Fail: No settings set"); >> - exit(EXIT_FAILURE); >> - } >> - return settings_stack + settings_index - 1; >> -} >> - >> -static void push_settings(struct settings *settings) >> -{ >> - if (settings_index >= MAX_SETTINGS_DEPTH) { >> - printf("Fail: Settings stack exceeded"); >> - exit(EXIT_FAILURE); >> - } >> - settings_stack[settings_index++] = *settings; >> - write_settings(current_settings()); >> -} >> - >> -static void pop_settings(void) >> -{ >> - if (settings_index <= 0) { >> - printf("Fail: Settings stack empty"); >> - exit(EXIT_FAILURE); >> - } >> - --settings_index; >> - write_settings(current_settings()); >> -} >> - >> static void restore_settings_atexit(void) >> { >> if (skip_settings_restore) >> return; >> >> printf("Restore THP and khugepaged settings..."); >> - write_settings(&saved_settings); >> + thp_restore_settings(); >> success("OK"); >> >> skip_settings_restore = true; >> @@ -395,27 +111,9 @@ static void restore_settings(int sig) >> static void save_settings(void) >> { >> printf("Save THP and khugepaged settings..."); >> - saved_settings = (struct settings) { >> - .thp_enabled = read_string("enabled", thp_enabled_strings), >> - .thp_defrag = read_string("defrag", thp_defrag_strings), >> - .shmem_enabled = >> - read_string("shmem_enabled", shmem_enabled_strings), >> - .use_zero_page = read_num("use_zero_page"), >> - }; >> - saved_settings.khugepaged = (struct khugepaged_settings) { >> - .defrag = read_num("khugepaged/defrag"), >> - .alloc_sleep_millisecs = >> - read_num("khugepaged/alloc_sleep_millisecs"), >> - .scan_sleep_millisecs = >> - read_num("khugepaged/scan_sleep_millisecs"), >> - .max_ptes_none = read_num("khugepaged/max_ptes_none"), >> - .max_ptes_swap = read_num("khugepaged/max_ptes_swap"), >> - .max_ptes_shared = read_num("khugepaged/max_ptes_shared"), >> - .pages_to_scan = read_num("khugepaged/pages_to_scan"), >> - }; >> if (file_ops && finfo.type == VMA_FILE) >> - saved_settings.read_ahead_kb = >> - _read_num(finfo.dev_queue_read_ahead_path); >> + thp_set_read_ahead_path(finfo.dev_queue_read_ahead_path); >> + thp_save_settings(); >> >> success("OK"); >> >> @@ -798,7 +496,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, >> struct mem_ops *ops, bool expect) >> { >> int ret; >> - struct settings settings = *current_settings(); >> + struct thp_settings settings = *thp_current_settings(); >> >> printf("%s...", msg); >> >> @@ -808,7 +506,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, >> */ >> settings.thp_enabled = THP_NEVER; >> settings.shmem_enabled = SHMEM_NEVER; >> - push_settings(&settings); >> + thp_push_settings(&settings); >> >> /* Clear VM_NOHUGEPAGE */ >> madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); >> @@ -820,7 +518,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, >> else >> success("OK"); >> >> - pop_settings(); >> + thp_pop_settings(); >> } >> >> static void madvise_collapse(const char *msg, char *p, int nr_hpages, >> @@ -850,13 +548,13 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages, >> madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); >> >> /* Wait until the second full_scan completed */ >> - full_scans = read_num("khugepaged/full_scans") + 2; >> + full_scans = thp_read_num("khugepaged/full_scans") + 2; >> >> printf("%s...", msg); >> while (timeout--) { >> if (ops->check_huge(p, nr_hpages)) >> break; >> - if (read_num("khugepaged/full_scans") >= full_scans) >> + if (thp_read_num("khugepaged/full_scans") >= full_scans) >> break; >> printf("."); >> usleep(TICK); >> @@ -911,11 +609,11 @@ static bool is_tmpfs(struct mem_ops *ops) >> >> static void alloc_at_fault(void) >> { >> - struct settings settings = *current_settings(); >> + struct thp_settings settings = *thp_current_settings(); >> char *p; >> >> settings.thp_enabled = THP_ALWAYS; >> - push_settings(&settings); >> + thp_push_settings(&settings); >> >> p = alloc_mapping(1); >> *p = 1; >> @@ -925,7 +623,7 @@ static void alloc_at_fault(void) >> else >> fail("Fail"); >> >> - pop_settings(); >> + thp_pop_settings(); >> >> madvise(p, page_size, MADV_DONTNEED); >> printf("Split huge PMD on MADV_DONTNEED..."); >> @@ -973,11 +671,11 @@ static void collapse_single_pte_entry(struct collapse_context *c, struct mem_ops >> static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *ops) >> { >> int max_ptes_none = hpage_pmd_nr / 2; >> - struct settings settings = *current_settings(); >> + struct thp_settings settings = *thp_current_settings(); >> void *p; >> >> settings.khugepaged.max_ptes_none = max_ptes_none; >> - push_settings(&settings); >> + thp_push_settings(&settings); >> >> p = ops->setup_area(1); >> >> @@ -1002,7 +700,7 @@ static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *o >> } >> skip: >> ops->cleanup_area(p, hpage_pmd_size); >> - pop_settings(); >> + thp_pop_settings(); >> } >> >> static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_ops *ops) >> @@ -1033,7 +731,7 @@ static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_op >> >> static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *ops) >> { >> - int max_ptes_swap = read_num("khugepaged/max_ptes_swap"); >> + int max_ptes_swap = thp_read_num("khugepaged/max_ptes_swap"); >> void *p; >> >> p = ops->setup_area(1); >> @@ -1250,11 +948,11 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o >> fail("Fail"); >> ops->fault(p, 0, page_size); >> >> - write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); >> + thp_write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); >> c->collapse("Collapse PTE table full of compound pages in child", >> p, 1, ops, true); >> - write_num("khugepaged/max_ptes_shared", >> - current_settings()->khugepaged.max_ptes_shared); >> + thp_write_num("khugepaged/max_ptes_shared", >> + thp_current_settings()->khugepaged.max_ptes_shared); >> >> validate_memory(p, 0, hpage_pmd_size); >> ops->cleanup_area(p, hpage_pmd_size); >> @@ -1275,7 +973,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o >> >> static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops *ops) >> { >> - int max_ptes_shared = read_num("khugepaged/max_ptes_shared"); >> + int max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"); >> int wstatus; >> void *p; >> >> @@ -1443,7 +1141,7 @@ static void parse_test_type(int argc, const char **argv) >> >> int main(int argc, const char **argv) >> { >> - struct settings default_settings = { >> + struct thp_settings default_settings = { >> .thp_enabled = THP_MADVISE, >> .thp_defrag = THP_DEFRAG_ALWAYS, >> .shmem_enabled = SHMEM_ADVISE, >> @@ -1484,7 +1182,7 @@ int main(int argc, const char **argv) >> default_settings.khugepaged.pages_to_scan = hpage_pmd_nr * 8; >> >> save_settings(); >> - push_settings(&default_settings); >> + thp_push_settings(&default_settings); >> >> alloc_at_fault(); >> >> diff --git a/tools/testing/selftests/mm/thp_settings.c b/tools/testing/selftests/mm/thp_settings.c >> new file mode 100644 >> index 000000000000..5e8ec792cac7 >> --- /dev/null >> +++ b/tools/testing/selftests/mm/thp_settings.c >> @@ -0,0 +1,296 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +#include <fcntl.h> >> +#include <limits.h> >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <unistd.h> >> + >> +#include "thp_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 const char * const thp_enabled_strings[] = { >> + "always", >> + "madvise", >> + "never", >> + NULL >> +}; >> + >> +static const char * const thp_defrag_strings[] = { >> + "always", >> + "defer", >> + "defer+madvise", >> + "madvise", >> + "never", >> + NULL >> +}; >> + >> +static const char * const shmem_enabled_strings[] = { >> + "always", >> + "within_size", >> + "advise", >> + "never", >> + "deny", >> + "force", >> + NULL >> +}; >> + >> +int read_file(const char *path, char *buf, size_t buflen) >> +{ >> + int fd; >> + ssize_t numread; >> + >> + fd = open(path, O_RDONLY); >> + if (fd == -1) >> + return 0; >> + >> + numread = read(fd, buf, buflen - 1); >> + if (numread < 1) { >> + close(fd); >> + return 0; >> + } >> + >> + buf[numread] = '\0'; >> + close(fd); >> + >> + return (unsigned int) numread; >> +} >> + >> +int write_file(const char *path, const char *buf, size_t buflen) >> +{ >> + int fd; >> + ssize_t numwritten; >> + >> + fd = open(path, O_WRONLY); >> + if (fd == -1) { >> + printf("open(%s)\n", path); >> + exit(EXIT_FAILURE); >> + return 0; >> + } >> + >> + numwritten = write(fd, buf, buflen - 1); >> + close(fd); >> + if (numwritten < 1) { >> + printf("write(%s)\n", buf); >> + exit(EXIT_FAILURE); >> + return 0; >> + } >> + >> + return (unsigned int) numwritten; >> +} >> + >> +const unsigned long read_num(const char *path) >> +{ >> + char buf[21]; >> + >> + if (read_file(path, buf, sizeof(buf)) < 0) { >> + perror("read_file()"); >> + exit(EXIT_FAILURE); >> + } >> + >> + return strtoul(buf, NULL, 10); >> +} >> + >> +void write_num(const char *path, unsigned long num) >> +{ >> + char buf[21]; >> + >> + sprintf(buf, "%ld", num); >> + if (!write_file(path, buf, strlen(buf) + 1)) { >> + perror(path); >> + exit(EXIT_FAILURE); >> + } >> +} >> + >> +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); >> + } >> + >> + if (!write_file(path, val, strlen(val) + 1)) { >> + perror(path); >> + exit(EXIT_FAILURE); >> + } >> +} >> + >> +const 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) >> +{ >> + *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); >> +} >> + >> +void thp_write_settings(struct thp_settings *settings) >> +{ >> + struct khugepaged_settings *khugepaged = &settings->khugepaged; >> + >> + 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); >> +} >> + >> +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) >> +{ >> + thp_write_settings(&saved_settings); >> +} >> + >> +void thp_save_settings(void) >> +{ >> + thp_read_settings(&saved_settings); >> +} >> + >> +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'; >> +} >> diff --git a/tools/testing/selftests/mm/thp_settings.h b/tools/testing/selftests/mm/thp_settings.h >> new file mode 100644 >> index 000000000000..ff3d98c30617 >> --- /dev/null >> +++ b/tools/testing/selftests/mm/thp_settings.h >> @@ -0,0 +1,71 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> +#ifndef __THP_SETTINGS_H__ >> +#define __THP_SETTINGS_H__ >> + >> +#include <stdbool.h> >> +#include <stddef.h> >> +#include <stdint.h> >> + >> +enum thp_enabled { >> + THP_ALWAYS, >> + THP_MADVISE, >> + THP_NEVER, >> +}; >> + >> +enum thp_defrag { >> + THP_DEFRAG_ALWAYS, >> + THP_DEFRAG_DEFER, >> + THP_DEFRAG_DEFER_MADVISE, >> + THP_DEFRAG_MADVISE, >> + THP_DEFRAG_NEVER, >> +}; >> + >> +enum shmem_enabled { >> + SHMEM_ALWAYS, >> + SHMEM_WITHIN_SIZE, >> + SHMEM_ADVISE, >> + SHMEM_NEVER, >> + SHMEM_DENY, >> + SHMEM_FORCE, >> +}; >> + >> +struct khugepaged_settings { >> + bool defrag; >> + unsigned int alloc_sleep_millisecs; >> + unsigned int scan_sleep_millisecs; >> + unsigned int max_ptes_none; >> + unsigned int max_ptes_swap; >> + unsigned int max_ptes_shared; >> + unsigned long pages_to_scan; >> +}; >> + >> +struct thp_settings { >> + enum thp_enabled thp_enabled; >> + enum thp_defrag thp_defrag; >> + enum shmem_enabled shmem_enabled; >> + bool use_zero_page; >> + struct khugepaged_settings khugepaged; >> + unsigned long read_ahead_kb; >> +}; >> + >> +int read_file(const char *path, char *buf, size_t buflen); >> +int write_file(const char *path, const char *buf, size_t buflen); >> +const unsigned long read_num(const char *path); >> +void write_num(const char *path, unsigned long num); >> + >> +int thp_read_string(const char *name, const char * const strings[]); >> +void thp_write_string(const char *name, const char *val); >> +const unsigned long thp_read_num(const char *name); >> +void thp_write_num(const char *name, unsigned long num); >> + >> +void thp_write_settings(struct thp_settings *settings); >> +void thp_read_settings(struct thp_settings *settings); >> +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); >> + >> +void thp_set_read_ahead_path(char *path); >> + >> +#endif /* __THP_SETTINGS_H__ */ >
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile index 78dfec8bc676..a3eb20c186e7 100644 --- a/tools/testing/selftests/mm/Makefile +++ b/tools/testing/selftests/mm/Makefile @@ -117,8 +117,8 @@ TEST_FILES += va_high_addr_switch.sh include ../lib.mk -$(TEST_GEN_PROGS): vm_util.c -$(TEST_GEN_FILES): vm_util.c +$(TEST_GEN_PROGS): vm_util.c thp_settings.c +$(TEST_GEN_FILES): vm_util.c thp_settings.c $(OUTPUT)/uffd-stress: uffd-common.c $(OUTPUT)/uffd-unit-tests: uffd-common.c diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c index fc47a1c4944c..b15e7fd70176 100644 --- a/tools/testing/selftests/mm/khugepaged.c +++ b/tools/testing/selftests/mm/khugepaged.c @@ -22,13 +22,13 @@ #include "linux/magic.h" #include "vm_util.h" +#include "thp_settings.h" #define BASE_ADDR ((void *)(1UL << 30)) static unsigned long hpage_pmd_size; static unsigned long page_size; static int hpage_pmd_nr; -#define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/" #define PID_SMAPS "/proc/self/smaps" #define TEST_FILE "collapse_test_file" @@ -71,78 +71,7 @@ struct file_info { }; static struct file_info finfo; - -enum thp_enabled { - THP_ALWAYS, - THP_MADVISE, - THP_NEVER, -}; - -static const char *thp_enabled_strings[] = { - "always", - "madvise", - "never", - NULL -}; - -enum thp_defrag { - THP_DEFRAG_ALWAYS, - THP_DEFRAG_DEFER, - THP_DEFRAG_DEFER_MADVISE, - THP_DEFRAG_MADVISE, - THP_DEFRAG_NEVER, -}; - -static const char *thp_defrag_strings[] = { - "always", - "defer", - "defer+madvise", - "madvise", - "never", - NULL -}; - -enum shmem_enabled { - SHMEM_ALWAYS, - SHMEM_WITHIN_SIZE, - SHMEM_ADVISE, - SHMEM_NEVER, - SHMEM_DENY, - SHMEM_FORCE, -}; - -static const char *shmem_enabled_strings[] = { - "always", - "within_size", - "advise", - "never", - "deny", - "force", - NULL -}; - -struct khugepaged_settings { - bool defrag; - unsigned int alloc_sleep_millisecs; - unsigned int scan_sleep_millisecs; - unsigned int max_ptes_none; - unsigned int max_ptes_swap; - unsigned int max_ptes_shared; - unsigned long pages_to_scan; -}; - -struct settings { - enum thp_enabled thp_enabled; - enum thp_defrag thp_defrag; - enum shmem_enabled shmem_enabled; - bool use_zero_page; - struct khugepaged_settings khugepaged; - unsigned long read_ahead_kb; -}; - -static struct settings saved_settings; static bool skip_settings_restore; - static int exit_status; static void success(const char *msg) @@ -161,226 +90,13 @@ static void skip(const char *msg) printf(" \e[33m%s\e[0m\n", msg); } -static int read_file(const char *path, char *buf, size_t buflen) -{ - int fd; - ssize_t numread; - - fd = open(path, O_RDONLY); - if (fd == -1) - return 0; - - numread = read(fd, buf, buflen - 1); - if (numread < 1) { - close(fd); - return 0; - } - - buf[numread] = '\0'; - close(fd); - - return (unsigned int) numread; -} - -static int write_file(const char *path, const char *buf, size_t buflen) -{ - int fd; - ssize_t numwritten; - - fd = open(path, O_WRONLY); - if (fd == -1) { - printf("open(%s)\n", path); - exit(EXIT_FAILURE); - return 0; - } - - numwritten = write(fd, buf, buflen - 1); - close(fd); - if (numwritten < 1) { - printf("write(%s)\n", buf); - exit(EXIT_FAILURE); - return 0; - } - - return (unsigned int) numwritten; -} - -static int read_string(const char *name, const char *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); -} - -static void 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); - } - - if (!write_file(path, val, strlen(val) + 1)) { - perror(path); - exit(EXIT_FAILURE); - } -} - -static const unsigned long _read_num(const char *path) -{ - char buf[21]; - - if (read_file(path, buf, sizeof(buf)) < 0) { - perror("read_file(read_num)"); - exit(EXIT_FAILURE); - } - - return strtoul(buf, NULL, 10); -} - -static const unsigned long 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); -} - -static void _write_num(const char *path, unsigned long num) -{ - char buf[21]; - - sprintf(buf, "%ld", num); - if (!write_file(path, buf, strlen(buf) + 1)) { - perror(path); - exit(EXIT_FAILURE); - } -} - -static void 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); -} - -static void write_settings(struct settings *settings) -{ - struct khugepaged_settings *khugepaged = &settings->khugepaged; - - write_string("enabled", thp_enabled_strings[settings->thp_enabled]); - write_string("defrag", thp_defrag_strings[settings->thp_defrag]); - write_string("shmem_enabled", - shmem_enabled_strings[settings->shmem_enabled]); - write_num("use_zero_page", settings->use_zero_page); - - write_num("khugepaged/defrag", khugepaged->defrag); - write_num("khugepaged/alloc_sleep_millisecs", - khugepaged->alloc_sleep_millisecs); - write_num("khugepaged/scan_sleep_millisecs", - khugepaged->scan_sleep_millisecs); - write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none); - write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap); - write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared); - write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan); - - if (file_ops && finfo.type == VMA_FILE) - _write_num(finfo.dev_queue_read_ahead_path, - settings->read_ahead_kb); -} - -#define MAX_SETTINGS_DEPTH 4 -static struct settings settings_stack[MAX_SETTINGS_DEPTH]; -static int settings_index; - -static struct settings *current_settings(void) -{ - if (!settings_index) { - printf("Fail: No settings set"); - exit(EXIT_FAILURE); - } - return settings_stack + settings_index - 1; -} - -static void push_settings(struct settings *settings) -{ - if (settings_index >= MAX_SETTINGS_DEPTH) { - printf("Fail: Settings stack exceeded"); - exit(EXIT_FAILURE); - } - settings_stack[settings_index++] = *settings; - write_settings(current_settings()); -} - -static void pop_settings(void) -{ - if (settings_index <= 0) { - printf("Fail: Settings stack empty"); - exit(EXIT_FAILURE); - } - --settings_index; - write_settings(current_settings()); -} - static void restore_settings_atexit(void) { if (skip_settings_restore) return; printf("Restore THP and khugepaged settings..."); - write_settings(&saved_settings); + thp_restore_settings(); success("OK"); skip_settings_restore = true; @@ -395,27 +111,9 @@ static void restore_settings(int sig) static void save_settings(void) { printf("Save THP and khugepaged settings..."); - saved_settings = (struct settings) { - .thp_enabled = read_string("enabled", thp_enabled_strings), - .thp_defrag = read_string("defrag", thp_defrag_strings), - .shmem_enabled = - read_string("shmem_enabled", shmem_enabled_strings), - .use_zero_page = read_num("use_zero_page"), - }; - saved_settings.khugepaged = (struct khugepaged_settings) { - .defrag = read_num("khugepaged/defrag"), - .alloc_sleep_millisecs = - read_num("khugepaged/alloc_sleep_millisecs"), - .scan_sleep_millisecs = - read_num("khugepaged/scan_sleep_millisecs"), - .max_ptes_none = read_num("khugepaged/max_ptes_none"), - .max_ptes_swap = read_num("khugepaged/max_ptes_swap"), - .max_ptes_shared = read_num("khugepaged/max_ptes_shared"), - .pages_to_scan = read_num("khugepaged/pages_to_scan"), - }; if (file_ops && finfo.type == VMA_FILE) - saved_settings.read_ahead_kb = - _read_num(finfo.dev_queue_read_ahead_path); + thp_set_read_ahead_path(finfo.dev_queue_read_ahead_path); + thp_save_settings(); success("OK"); @@ -798,7 +496,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, struct mem_ops *ops, bool expect) { int ret; - struct settings settings = *current_settings(); + struct thp_settings settings = *thp_current_settings(); printf("%s...", msg); @@ -808,7 +506,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, */ settings.thp_enabled = THP_NEVER; settings.shmem_enabled = SHMEM_NEVER; - push_settings(&settings); + thp_push_settings(&settings); /* Clear VM_NOHUGEPAGE */ madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); @@ -820,7 +518,7 @@ static void __madvise_collapse(const char *msg, char *p, int nr_hpages, else success("OK"); - pop_settings(); + thp_pop_settings(); } static void madvise_collapse(const char *msg, char *p, int nr_hpages, @@ -850,13 +548,13 @@ static bool wait_for_scan(const char *msg, char *p, int nr_hpages, madvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE); /* Wait until the second full_scan completed */ - full_scans = read_num("khugepaged/full_scans") + 2; + full_scans = thp_read_num("khugepaged/full_scans") + 2; printf("%s...", msg); while (timeout--) { if (ops->check_huge(p, nr_hpages)) break; - if (read_num("khugepaged/full_scans") >= full_scans) + if (thp_read_num("khugepaged/full_scans") >= full_scans) break; printf("."); usleep(TICK); @@ -911,11 +609,11 @@ static bool is_tmpfs(struct mem_ops *ops) static void alloc_at_fault(void) { - struct settings settings = *current_settings(); + struct thp_settings settings = *thp_current_settings(); char *p; settings.thp_enabled = THP_ALWAYS; - push_settings(&settings); + thp_push_settings(&settings); p = alloc_mapping(1); *p = 1; @@ -925,7 +623,7 @@ static void alloc_at_fault(void) else fail("Fail"); - pop_settings(); + thp_pop_settings(); madvise(p, page_size, MADV_DONTNEED); printf("Split huge PMD on MADV_DONTNEED..."); @@ -973,11 +671,11 @@ static void collapse_single_pte_entry(struct collapse_context *c, struct mem_ops static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *ops) { int max_ptes_none = hpage_pmd_nr / 2; - struct settings settings = *current_settings(); + struct thp_settings settings = *thp_current_settings(); void *p; settings.khugepaged.max_ptes_none = max_ptes_none; - push_settings(&settings); + thp_push_settings(&settings); p = ops->setup_area(1); @@ -1002,7 +700,7 @@ static void collapse_max_ptes_none(struct collapse_context *c, struct mem_ops *o } skip: ops->cleanup_area(p, hpage_pmd_size); - pop_settings(); + thp_pop_settings(); } static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_ops *ops) @@ -1033,7 +731,7 @@ static void collapse_swapin_single_pte(struct collapse_context *c, struct mem_op static void collapse_max_ptes_swap(struct collapse_context *c, struct mem_ops *ops) { - int max_ptes_swap = read_num("khugepaged/max_ptes_swap"); + int max_ptes_swap = thp_read_num("khugepaged/max_ptes_swap"); void *p; p = ops->setup_area(1); @@ -1250,11 +948,11 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o fail("Fail"); ops->fault(p, 0, page_size); - write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); + thp_write_num("khugepaged/max_ptes_shared", hpage_pmd_nr - 1); c->collapse("Collapse PTE table full of compound pages in child", p, 1, ops, true); - write_num("khugepaged/max_ptes_shared", - current_settings()->khugepaged.max_ptes_shared); + thp_write_num("khugepaged/max_ptes_shared", + thp_current_settings()->khugepaged.max_ptes_shared); validate_memory(p, 0, hpage_pmd_size); ops->cleanup_area(p, hpage_pmd_size); @@ -1275,7 +973,7 @@ static void collapse_fork_compound(struct collapse_context *c, struct mem_ops *o static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops *ops) { - int max_ptes_shared = read_num("khugepaged/max_ptes_shared"); + int max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"); int wstatus; void *p; @@ -1443,7 +1141,7 @@ static void parse_test_type(int argc, const char **argv) int main(int argc, const char **argv) { - struct settings default_settings = { + struct thp_settings default_settings = { .thp_enabled = THP_MADVISE, .thp_defrag = THP_DEFRAG_ALWAYS, .shmem_enabled = SHMEM_ADVISE, @@ -1484,7 +1182,7 @@ int main(int argc, const char **argv) default_settings.khugepaged.pages_to_scan = hpage_pmd_nr * 8; save_settings(); - push_settings(&default_settings); + thp_push_settings(&default_settings); alloc_at_fault(); diff --git a/tools/testing/selftests/mm/thp_settings.c b/tools/testing/selftests/mm/thp_settings.c new file mode 100644 index 000000000000..5e8ec792cac7 --- /dev/null +++ b/tools/testing/selftests/mm/thp_settings.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "thp_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 const char * const thp_enabled_strings[] = { + "always", + "madvise", + "never", + NULL +}; + +static const char * const thp_defrag_strings[] = { + "always", + "defer", + "defer+madvise", + "madvise", + "never", + NULL +}; + +static const char * const shmem_enabled_strings[] = { + "always", + "within_size", + "advise", + "never", + "deny", + "force", + NULL +}; + +int read_file(const char *path, char *buf, size_t buflen) +{ + int fd; + ssize_t numread; + + fd = open(path, O_RDONLY); + if (fd == -1) + return 0; + + numread = read(fd, buf, buflen - 1); + if (numread < 1) { + close(fd); + return 0; + } + + buf[numread] = '\0'; + close(fd); + + return (unsigned int) numread; +} + +int write_file(const char *path, const char *buf, size_t buflen) +{ + int fd; + ssize_t numwritten; + + fd = open(path, O_WRONLY); + if (fd == -1) { + printf("open(%s)\n", path); + exit(EXIT_FAILURE); + return 0; + } + + numwritten = write(fd, buf, buflen - 1); + close(fd); + if (numwritten < 1) { + printf("write(%s)\n", buf); + exit(EXIT_FAILURE); + return 0; + } + + return (unsigned int) numwritten; +} + +const unsigned long read_num(const char *path) +{ + char buf[21]; + + if (read_file(path, buf, sizeof(buf)) < 0) { + perror("read_file()"); + exit(EXIT_FAILURE); + } + + return strtoul(buf, NULL, 10); +} + +void write_num(const char *path, unsigned long num) +{ + char buf[21]; + + sprintf(buf, "%ld", num); + if (!write_file(path, buf, strlen(buf) + 1)) { + perror(path); + exit(EXIT_FAILURE); + } +} + +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); + } + + if (!write_file(path, val, strlen(val) + 1)) { + perror(path); + exit(EXIT_FAILURE); + } +} + +const 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) +{ + *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); +} + +void thp_write_settings(struct thp_settings *settings) +{ + struct khugepaged_settings *khugepaged = &settings->khugepaged; + + 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); +} + +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) +{ + thp_write_settings(&saved_settings); +} + +void thp_save_settings(void) +{ + thp_read_settings(&saved_settings); +} + +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'; +} diff --git a/tools/testing/selftests/mm/thp_settings.h b/tools/testing/selftests/mm/thp_settings.h new file mode 100644 index 000000000000..ff3d98c30617 --- /dev/null +++ b/tools/testing/selftests/mm/thp_settings.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __THP_SETTINGS_H__ +#define __THP_SETTINGS_H__ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +enum thp_enabled { + THP_ALWAYS, + THP_MADVISE, + THP_NEVER, +}; + +enum thp_defrag { + THP_DEFRAG_ALWAYS, + THP_DEFRAG_DEFER, + THP_DEFRAG_DEFER_MADVISE, + THP_DEFRAG_MADVISE, + THP_DEFRAG_NEVER, +}; + +enum shmem_enabled { + SHMEM_ALWAYS, + SHMEM_WITHIN_SIZE, + SHMEM_ADVISE, + SHMEM_NEVER, + SHMEM_DENY, + SHMEM_FORCE, +}; + +struct khugepaged_settings { + bool defrag; + unsigned int alloc_sleep_millisecs; + unsigned int scan_sleep_millisecs; + unsigned int max_ptes_none; + unsigned int max_ptes_swap; + unsigned int max_ptes_shared; + unsigned long pages_to_scan; +}; + +struct thp_settings { + enum thp_enabled thp_enabled; + enum thp_defrag thp_defrag; + enum shmem_enabled shmem_enabled; + bool use_zero_page; + struct khugepaged_settings khugepaged; + unsigned long read_ahead_kb; +}; + +int read_file(const char *path, char *buf, size_t buflen); +int write_file(const char *path, const char *buf, size_t buflen); +const unsigned long read_num(const char *path); +void write_num(const char *path, unsigned long num); + +int thp_read_string(const char *name, const char * const strings[]); +void thp_write_string(const char *name, const char *val); +const unsigned long thp_read_num(const char *name); +void thp_write_num(const char *name, unsigned long num); + +void thp_write_settings(struct thp_settings *settings); +void thp_read_settings(struct thp_settings *settings); +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); + +void thp_set_read_ahead_path(char *path); + +#endif /* __THP_SETTINGS_H__ */
The khugepaged test has a useful framework for save/restore/pop/push of all thp settings via the sysfs interface. This will be useful to explicitly control small-sized THP settings in other tests, so let's move it out of khugepaged and into its own thp_settings.[c|h] utility. Signed-off-by: Ryan Roberts <ryan.roberts@arm.com> --- tools/testing/selftests/mm/Makefile | 4 +- tools/testing/selftests/mm/khugepaged.c | 346 ++-------------------- tools/testing/selftests/mm/thp_settings.c | 296 ++++++++++++++++++ tools/testing/selftests/mm/thp_settings.h | 71 +++++ 4 files changed, 391 insertions(+), 326 deletions(-) create mode 100644 tools/testing/selftests/mm/thp_settings.c create mode 100644 tools/testing/selftests/mm/thp_settings.h -- 2.25.1