// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2017 Cyril Hrubis */ /* * Check that memory marked with MADV_FREE is freed on memory pressure. * * o Fork a child and move it into a memory cgroup * * o Allocate pages and fill them with a pattern * * o Madvise pages with MADV_FREE * * o Check that madvised pages were not freed immediately * * o Write to some of the madvised pages again, these must not be freed * * o Set memory limits * - limit_in_bytes = 8MB * - memsw.limit_in_bytes = 16MB * * The reason for doubling the limit_in_bytes is to have safe margin * for forking the memory hungy child etc. And the reason to setting * memsw.limit_in_bytes to twice of that is to give the system chance * to try to free some memory before cgroup OOM kicks in and kills * the memory hungry child. * * o Run a memory hungry child that allocates memory in loop until it's * killed by cgroup OOM * * o Once the child is killed the MADV_FREE pages that were not written to * should be freed, the test passes if there is at least one */ #include #include #include #include #include #include #include #include #include "tst_test.h" #include "lapi/mmap.h" #define MEMCG_PATH "/sys/fs/cgroup/memory/" static char cgroup_path[PATH_MAX]; static char tasks_path[PATH_MAX]; static char limit_in_bytes_path[PATH_MAX]; static char memsw_limit_in_bytes_path[PATH_MAX]; static size_t page_size; static int sleep_between_faults; static int swap_accounting_enabled; #define PAGES 128 #define TOUCHED_PAGE1 0 #define TOUCHED_PAGE2 10 static void memory_pressure_child(void) { size_t i, page_size = getpagesize(); char *ptr; for (;;) { ptr = mmap(NULL, 500 * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); for (i = 0; i < 500; i++) { ptr[i * page_size] = i % 100; usleep(sleep_between_faults); } /* If swap accounting is disabled exit after process swapped out 100MB */ if (!swap_accounting_enabled) { int swapped; SAFE_FILE_LINES_SCANF("/proc/self/status", "VmSwap: %d", &swapped); if (swapped > 100 * 1024) exit(0); } } abort(); } static void setup_cgroup_paths(int pid) { snprintf(cgroup_path, sizeof(cgroup_path), MEMCG_PATH "ltp_madvise09_%i/", pid); snprintf(tasks_path, sizeof(tasks_path), "%s/tasks", cgroup_path); snprintf(limit_in_bytes_path, sizeof(limit_in_bytes_path), "%s/memory.limit_in_bytes", cgroup_path); snprintf(memsw_limit_in_bytes_path, sizeof(memsw_limit_in_bytes_path), "%s/memory.memsw.limit_in_bytes", cgroup_path); } static int count_freed(char *ptr) { int i, ret = 0; for (i = 0; i < PAGES; i++) { if (!ptr[i * page_size]) ret++; } return ret; } static int check_page_baaa(char *ptr) { unsigned int i; if (ptr[0] != 'b') { tst_res(TINFO, "%p unexpected %c (%i) at 0 expected 'b'", ptr, isprint(ptr[0]) ? ptr[0] : ' ', ptr[0]); return 1; } for (i = 1; i < page_size; i++) { if (ptr[i] != 'a') { tst_res(TINFO, "%p unexpected %c (%i) at %i expected 'a'", ptr, isprint(ptr[i]) ? ptr[i] : ' ', ptr[i], i); return 1; } } return 0; } static int check_page(char *ptr, char val) { unsigned int i; for (i = 0; i < page_size; i++) { if (ptr[i] != val) { tst_res(TINFO, "%p unexpected %c (%i) at %i expected %c (%i)", ptr, isprint(ptr[i]) ? ptr[i] : ' ', ptr[i], i, isprint(val) ? val : ' ', val); return 1; } } return 0; } static void child(void) { size_t i; char *ptr; unsigned int usage, old_limit, old_memsw_limit; int status, pid, retries = 0; SAFE_MKDIR(cgroup_path, 0777); SAFE_FILE_PRINTF(tasks_path, "%i", getpid()); ptr = SAFE_MMAP(NULL, PAGES * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); for (i = 0; i < PAGES * page_size; i++) ptr[i] = 'a'; if (madvise(ptr, PAGES * page_size, MADV_FREE)) { if (errno == EINVAL) tst_brk(TCONF | TERRNO, "MADV_FREE is not supported"); tst_brk(TBROK | TERRNO, "MADV_FREE failed"); } if (ptr[page_size] != 'a') tst_res(TFAIL, "MADV_FREE pages were freed immediately"); else tst_res(TPASS, "MADV_FREE pages were not freed immediately"); ptr[TOUCHED_PAGE1 * page_size] = 'b'; ptr[TOUCHED_PAGE2 * page_size] = 'b'; usage = 8 * 1024 * 1024; tst_res(TINFO, "Setting memory limits to %u %u", usage, 2 * usage); SAFE_FILE_SCANF(limit_in_bytes_path, "%u", &old_limit); if (swap_accounting_enabled) SAFE_FILE_SCANF(memsw_limit_in_bytes_path, "%u", &old_memsw_limit); SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", usage); if (swap_accounting_enabled) SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", 2 * usage); do { sleep_between_faults++; pid = SAFE_FORK(); if (!pid) memory_pressure_child(); tst_res(TINFO, "Memory hungry child %i started, try %i", pid, retries); SAFE_WAIT(&status); } while (retries++ < 10 && count_freed(ptr) == 0); char map[PAGES+1]; unsigned int freed = 0; unsigned int corrupted = 0; for (i = 0; i < PAGES; i++) { char exp_val; if (ptr[i * page_size]) { exp_val = 'a'; map[i] = 'p'; } else { exp_val = 0; map[i] = '_'; freed++; } if (i != TOUCHED_PAGE1 && i != TOUCHED_PAGE2) { if (check_page(ptr + i * page_size, exp_val)) { map[i] = '?'; corrupted++; } } else { if (check_page_baaa(ptr + i * page_size)) { map[i] = '?'; corrupted++; } } } map[PAGES] = '\0'; tst_res(TINFO, "Memory map: %s", map); if (freed) tst_res(TPASS, "Pages MADV_FREE were freed on low memory"); else tst_res(TFAIL, "No MADV_FREE page was freed on low memory"); if (corrupted) tst_res(TFAIL, "Found corrupted page"); else tst_res(TPASS, "All pages have expected content"); if (swap_accounting_enabled) SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", old_memsw_limit); SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", old_limit); SAFE_MUNMAP(ptr, PAGES); exit(0); } static void cleanup(void) { if (cgroup_path[0] && !access(cgroup_path, F_OK)) rmdir(cgroup_path); } static void run(void) { pid_t pid; int status; retry: pid = SAFE_FORK(); if (!pid) { setup_cgroup_paths(getpid()); child(); } setup_cgroup_paths(pid); SAFE_WAIT(&status); cleanup(); /* * Rarely cgroup OOM kills both children not only the one that allocates * memory in loop, hence we retry here if that happens. */ if (WIFSIGNALED(status)) { tst_res(TINFO, "Both children killed, retrying..."); goto retry; } if (WIFEXITED(status) && WEXITSTATUS(status) == TCONF) tst_brk(TCONF, "MADV_FREE is not supported"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) tst_brk(TBROK, "Child %s", tst_strstatus(status)); } static void setup(void) { long int swap_total; if (access(MEMCG_PATH, F_OK)) { tst_brk(TCONF, "'" MEMCG_PATH "' not present, CONFIG_MEMCG missing?"); } if (!access(MEMCG_PATH "memory.memsw.limit_in_bytes", F_OK)) swap_accounting_enabled = 1; else tst_res(TINFO, "Swap accounting is disabled"); SAFE_FILE_LINES_SCANF("/proc/meminfo", "SwapTotal: %ld", &swap_total); if (swap_total <= 0) tst_brk(TCONF, "MADV_FREE does not work without swap"); page_size = getpagesize(); } static struct tst_test test = { .setup = setup, .cleanup = cleanup, .test_all = run, .needs_root = 1, .forks_child = 1, };