Home | History | Annotate | Download | only in madvise
      1 /*
      2  * Copyright (c) 2017 Cyril Hrubis <chrubis (at) suse.cz>
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 2 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 /*
     19  * Check that memory marked with MADV_FREE is freed on memory pressure.
     20  *
     21  * o Fork a child and move it into a memory cgroup
     22  *
     23  * o Allocate pages and fill them with a pattern
     24  *
     25  * o Madvise pages with MADV_FREE
     26  *
     27  * o Check that madvised pages were not freed immediatelly
     28  *
     29  * o Write to some of the madvised pages again, these must not be freed
     30  *
     31  * o Set memory limits
     32  *   - limit_in_bytes = 8MB
     33  *   - memsw.limit_in_bytes = 16MB
     34  *
     35  *   The reason for doubling the limit_in_bytes is to have safe margin
     36  *   for forking the memory hungy child etc. And the reason to setting
     37  *   memsw.limit_in_bytes to twice of that is to give the system chance
     38  *   to try to free some memory before cgroup OOM kicks in and kills
     39  *   the memory hungry child.
     40  *
     41  * o Run a memory hungry child that allocates memory in loop until it's
     42  *   killed by cgroup OOM
     43  *
     44  * o Once the child is killed the MADV_FREE pages that were not written to
     45  *   should be freed, the test passes if there is at least one
     46  */
     47 
     48 #include <stdlib.h>
     49 #include <sys/wait.h>
     50 #include <fcntl.h>
     51 #include <unistd.h>
     52 #include <signal.h>
     53 #include <errno.h>
     54 #include <stdio.h>
     55 #include <ctype.h>
     56 
     57 #include "tst_test.h"
     58 #include "lapi/mmap.h"
     59 
     60 #define MEMCG_PATH "/sys/fs/cgroup/memory/"
     61 
     62 static char cgroup_path[PATH_MAX];
     63 static char tasks_path[PATH_MAX];
     64 static char limit_in_bytes_path[PATH_MAX];
     65 static char memsw_limit_in_bytes_path[PATH_MAX];
     66 
     67 static size_t page_size;
     68 static int sleep_between_faults;
     69 
     70 static int swap_accounting_enabled;
     71 
     72 #define PAGES 32
     73 #define TOUCHED_PAGE1 0
     74 #define TOUCHED_PAGE2 10
     75 
     76 static void memory_pressure_child(void)
     77 {
     78 	size_t i, page_size = getpagesize();
     79 	char *ptr;
     80 
     81 	for (;;) {
     82 		ptr = mmap(NULL, 500 * page_size, PROT_READ | PROT_WRITE,
     83 			   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     84 
     85 		for (i = 0; i < 500; i++) {
     86 			ptr[i * page_size] = i % 100;
     87 			usleep(sleep_between_faults);
     88 		}
     89 
     90 		/* If swap accounting is disabled exit after process swapped out 100MB */
     91 		if (!swap_accounting_enabled) {
     92 			int swapped;
     93 
     94 			SAFE_FILE_LINES_SCANF("/proc/self/status", "VmSwap: %d", &swapped);
     95 
     96 			if (swapped > 100 * 1024)
     97 				exit(0);
     98 		}
     99 
    100 	}
    101 
    102 	abort();
    103 }
    104 
    105 static void setup_cgroup_paths(int pid)
    106 {
    107 	snprintf(cgroup_path, sizeof(cgroup_path),
    108 		 MEMCG_PATH "ltp_madvise09_%i/", pid);
    109 	snprintf(tasks_path, sizeof(tasks_path), "%s/tasks", cgroup_path);
    110 	snprintf(limit_in_bytes_path, sizeof(limit_in_bytes_path),
    111 		 "%s/memory.limit_in_bytes", cgroup_path);
    112 	snprintf(memsw_limit_in_bytes_path, sizeof(memsw_limit_in_bytes_path),
    113 		 "%s/memory.memsw.limit_in_bytes", cgroup_path);
    114 }
    115 
    116 static int count_freed(char *ptr)
    117 {
    118 	int i, ret = 0;
    119 
    120 	for (i = 0; i < PAGES; i++) {
    121 		if (!ptr[i * page_size])
    122 			ret++;
    123 	}
    124 
    125 	return ret;
    126 }
    127 
    128 static int check_page_baaa(char *ptr)
    129 {
    130 	unsigned int i;
    131 
    132 	if (ptr[0] != 'b') {
    133 		tst_res(TINFO, "%p unexpected %c (%i) at 0 expected 'b'",
    134 			ptr, isprint(ptr[0]) ? ptr[0] : ' ', ptr[0]);
    135 		return 1;
    136 	}
    137 
    138 	for (i = 1; i < page_size; i++) {
    139 		if (ptr[i] != 'a') {
    140 			tst_res(TINFO,
    141 				"%p unexpected %c (%i) at %i expected 'a'",
    142 				ptr, isprint(ptr[i]) ? ptr[i] : ' ',
    143 				ptr[i], i);
    144 			return 1;
    145 		}
    146 	}
    147 
    148 	return 0;
    149 }
    150 
    151 static int check_page(char *ptr, char val)
    152 {
    153 	unsigned int i;
    154 
    155 	for (i = 0; i < page_size; i++) {
    156 		if (ptr[i] != val) {
    157 			tst_res(TINFO,
    158 				"%p unexpected %c (%i) at %i expected %c (%i)",
    159 				ptr, isprint(ptr[i]) ? ptr[i] : ' ', ptr[i], i,
    160 				isprint(val) ? val : ' ', val);
    161 			return 1;
    162 		}
    163 	}
    164 
    165 	return 0;
    166 }
    167 
    168 static void child(void)
    169 {
    170 	size_t i;
    171 	char *ptr;
    172 	unsigned int usage, old_limit, old_memsw_limit;
    173 	int status, pid, retries = 0;
    174 
    175 	SAFE_MKDIR(cgroup_path, 0777);
    176 	SAFE_FILE_PRINTF(tasks_path, "%i", getpid());
    177 
    178 	ptr = SAFE_MMAP(NULL, PAGES * page_size, PROT_READ | PROT_WRITE,
    179 	                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    180 
    181 	for (i = 0; i < PAGES * page_size; i++)
    182 		ptr[i] = 'a';
    183 
    184 	if (madvise(ptr, PAGES * page_size, MADV_FREE)) {
    185 		if (errno == EINVAL)
    186 			tst_brk(TCONF | TERRNO, "MADV_FREE is not supported");
    187 
    188 		tst_brk(TBROK | TERRNO, "MADV_FREE failed");
    189 	}
    190 
    191 	if (ptr[page_size] != 'a')
    192 		tst_res(TFAIL, "MADV_FREE pages were freed immediatelly");
    193 	else
    194 		tst_res(TPASS, "MADV_FREE pages were not freed immediatelly");
    195 
    196 	ptr[TOUCHED_PAGE1 * page_size] = 'b';
    197 	ptr[TOUCHED_PAGE2 * page_size] = 'b';
    198 
    199 	usage = 8 * 1024 * 1024;
    200 	tst_res(TINFO, "Setting memory limits to %u %u", usage, 2 * usage);
    201 
    202 	SAFE_FILE_SCANF(limit_in_bytes_path, "%u", &old_limit);
    203 
    204 	if (swap_accounting_enabled)
    205 		SAFE_FILE_SCANF(memsw_limit_in_bytes_path, "%u", &old_memsw_limit);
    206 
    207 	SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", usage);
    208 
    209 	if (swap_accounting_enabled)
    210 		SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", 2 * usage);
    211 
    212 	do {
    213 		sleep_between_faults++;
    214 
    215 		pid = SAFE_FORK();
    216 		if (!pid)
    217 			memory_pressure_child();
    218 
    219 		tst_res(TINFO, "Memory hungry child %i started, try %i", pid, retries);
    220 
    221 		SAFE_WAIT(&status);
    222 	} while (retries++ < 10 && count_freed(ptr) == 0);
    223 
    224 	char map[PAGES+1];
    225 	unsigned int freed = 0;
    226 	unsigned int corrupted = 0;
    227 
    228 	for (i = 0; i < PAGES; i++) {
    229 		char exp_val;
    230 
    231 		if (ptr[i * page_size]) {
    232 			exp_val = 'a';
    233 			map[i] = 'p';
    234 		} else {
    235 			exp_val = 0;
    236 			map[i] = '_';
    237 			freed++;
    238 		}
    239 
    240 		if (i != TOUCHED_PAGE1 && i != TOUCHED_PAGE2) {
    241 			if (check_page(ptr + i * page_size, exp_val)) {
    242 				map[i] = '?';
    243 				corrupted++;
    244 			}
    245 		} else {
    246 			if (check_page_baaa(ptr + i * page_size)) {
    247 				map[i] = '?';
    248 				corrupted++;
    249 			}
    250 		}
    251 	}
    252 	map[PAGES] = '\0';
    253 
    254 	tst_res(TINFO, "Memory map: %s", map);
    255 
    256 	if (freed)
    257 		tst_res(TPASS, "Pages MADV_FREE were freed on low memory");
    258 	else
    259 		tst_res(TFAIL, "No MADV_FREE page was freed on low memory");
    260 
    261 	if (corrupted)
    262 		tst_res(TFAIL, "Found corrupted page");
    263 	else
    264 		tst_res(TPASS, "All pages have expected content");
    265 
    266 	if (swap_accounting_enabled)
    267 		SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", old_memsw_limit);
    268 
    269 	SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", old_limit);
    270 
    271 	SAFE_MUNMAP(ptr, PAGES);
    272 
    273 	exit(0);
    274 }
    275 
    276 static void cleanup(void)
    277 {
    278 	if (cgroup_path[0] && !access(cgroup_path, F_OK))
    279 		rmdir(cgroup_path);
    280 }
    281 
    282 static void run(void)
    283 {
    284 	pid_t pid;
    285 	int status;
    286 
    287 retry:
    288 	pid = SAFE_FORK();
    289 
    290 	if (!pid) {
    291 		setup_cgroup_paths(getpid());
    292 		child();
    293 	}
    294 
    295 	setup_cgroup_paths(pid);
    296 	SAFE_WAIT(&status);
    297 	cleanup();
    298 
    299 	/*
    300 	 * Rarely cgroup OOM kills both children not only the one that allocates
    301 	 * memory in loop, hence we retry here if that happens.
    302 	 */
    303 	if (WIFSIGNALED(status)) {
    304 		tst_res(TINFO, "Both children killed, retrying...");
    305 		goto retry;
    306 	}
    307 
    308 	if (WIFEXITED(status) && WEXITSTATUS(status) == TCONF)
    309 		tst_brk(TCONF, "MADV_FREE is not supported");
    310 
    311 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
    312 		tst_brk(TBROK, "Child %s", tst_strstatus(status));
    313 }
    314 
    315 static void setup(void)
    316 {
    317 	long int swap_total;
    318 
    319 	if (access(MEMCG_PATH, F_OK)) {
    320 		tst_brk(TCONF, "'" MEMCG_PATH
    321 			"' not present, CONFIG_MEMCG missing?");
    322 	}
    323 
    324 	if (!access(MEMCG_PATH "memory.memsw.limit_in_bytes", F_OK))
    325 		swap_accounting_enabled = 1;
    326 	else
    327 		tst_res(TINFO, "Swap accounting is disabled");
    328 
    329 	SAFE_FILE_LINES_SCANF("/proc/meminfo", "SwapTotal: %ld", &swap_total);
    330 	if (swap_total <= 0)
    331 		tst_brk(TCONF, "MADV_FREE does not work without swap");
    332 
    333 	page_size = getpagesize();
    334 }
    335 
    336 static struct tst_test test = {
    337 	.setup = setup,
    338 	.cleanup = cleanup,
    339 	.test_all = run,
    340 	.needs_root = 1,
    341 	.forks_child = 1,
    342 };
    343