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