/* * Copyright (C) 2012-2017 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License for more details. * * Description: * * The program is designed to test max_map_count tunable file * * The kernel Documentation say that: * /proc/sys/vm/max_map_count contains the maximum number of memory map * areas a process may have. Memory map areas are used as a side-effect * of calling malloc, directly by mmap and mprotect, and also when * loading shared libraries. * * Each process has his own maps file: /proc/[pid]/maps, and each line * indicates a map entry, so it can caculate the amount of maps by reading * the file lines' number to check the tunable performance. * * The program tries to invoke mmap() endlessly until it triggers MAP_FAILED, * then reads the process's maps file /proc/[pid]/maps, save the line number to * map_count variable, and compare it with /proc/sys/vm/max_map_count, * map_count should be greater than max_map_count by 1; * * Note: On some architectures there is a special vma VSYSCALL, which * is allocated without incrementing mm->map_count variable. On these * architectures each /proc/<pid>/maps has at the end: * ... * ... * ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] * * so we ignore this line during /proc/[pid]/maps reading. */ #define _GNU_SOURCE #include <sys/wait.h> #include <errno.h> #include <fcntl.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <sys/utsname.h> #include "mem.h" #define MAP_COUNT_DEFAULT 1024 #define MAX_MAP_COUNT 65536L static long old_max_map_count = -1; static long old_overcommit = -1; static struct utsname un; static void setup(void) { if (access(PATH_SYSVM "max_map_count", F_OK) != 0) tst_brk(TBROK | TERRNO, "Can't support to test max_map_count"); old_max_map_count = get_sys_tune("max_map_count"); old_overcommit = get_sys_tune("overcommit_memory"); set_sys_tune("overcommit_memory", 2, 1); if (uname(&un) != 0) tst_brk(TBROK | TERRNO, "uname error"); } static void cleanup(void) { if (old_overcommit != -1) set_sys_tune("overcommit_memory", old_overcommit, 0); if (old_max_map_count != -1) set_sys_tune("max_map_count", old_max_map_count, 0); } /* This is a filter to exclude map entries which aren't accounted * for in the vm_area_struct's map_count. */ static bool filter_map(const char *line) { char buf[BUFSIZ]; int ret; ret = sscanf(line, "%*p-%*p %*4s %*p %*2d:%*2d %*d %s", buf); if (ret != 1) return false; #if defined(__x86_64__) || defined(__x86__) /* On x86, there's an old compat vsyscall page */ if (!strcmp(buf, "[vsyscall]")) return true; #elif defined(__ia64__) /* On ia64, the vdso is not a proper mapping */ if (!strcmp(buf, "[vdso]")) return true; #elif defined(__arm__) /* Skip it when run it in aarch64 */ if ((!strcmp(un.machine, "aarch64")) || (!strcmp(un.machine, "aarch64_be"))) return false; /* Older arm kernels didn't label their vdso maps */ if (!strncmp(line, "ffff0000-ffff1000", 17)) return true; #endif return false; } static long count_maps(pid_t pid) { FILE *fp; size_t len; char *line = NULL; char buf[BUFSIZ]; long map_count = 0; snprintf(buf, BUFSIZ, "/proc/%d/maps", pid); fp = fopen(buf, "r"); if (fp == NULL) tst_brk(TBROK | TERRNO, "fopen %s", buf); while (getline(&line, &len, fp) != -1) { /* exclude vdso and vsyscall */ if (filter_map(line)) continue; map_count++; } fclose(fp); return map_count; } static void max_map_count_test(void) { int status; pid_t pid; long max_maps; long map_count; long max_iters; long memfree; /* * XXX Due to a possible kernel bug, oom-killer can be easily * triggered when doing small piece mmaps in huge amount even if * enough free memory available. Also it has been observed that * oom-killer often kill wrong victims in this situation, we * decided to do following steps to make sure no oom happen: * 1) use a safe maximum max_map_count value as upper-bound, * we set it 65536 in this case, i.e., we don't test too big * value; * 2) make sure total mapping isn't larger tha * CommitLimit - Committed_AS * and set overcommit_memory to 2, this could help mapping * returns ENOMEM instead of triggering oom-killer when * memory is tight. (When there are enough free memory, * step 1) will be used first. * Hope OOM-killer can be more stable oneday. */ memfree = SAFE_READ_MEMINFO("CommitLimit:") - SAFE_READ_MEMINFO("Committed_AS:"); /* 64 used as a bias to make sure no overflow happen */ max_iters = memfree / sysconf(_SC_PAGESIZE) * 1024 - 64; if (max_iters > MAX_MAP_COUNT) max_iters = MAX_MAP_COUNT; max_maps = MAP_COUNT_DEFAULT; while (max_maps <= max_iters) { set_sys_tune("max_map_count", max_maps, 1); switch (pid = SAFE_FORK()) { case 0: while (mmap(NULL, 1, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0) != MAP_FAILED) ; if (raise(SIGSTOP) != 0) tst_brk(TBROK | TERRNO, "raise"); exit(0); default: break; } /* wait child done mmap and stop */ SAFE_WAITPID(pid, &status, WUNTRACED); if (!WIFSTOPPED(status)) tst_brk(TBROK, "child did not stopped"); map_count = count_maps(pid); /* Note max_maps will be exceeded by one for * the sysctl setting of max_map_count. This * is the mm failure point at the time of * writing this COMMENT! */ if (map_count == (max_maps + 1)) tst_res(TPASS, "%ld map entries in total " "as expected.", max_maps); else tst_res(TFAIL, "%ld map entries in total, but " "expected %ld entries", map_count, max_maps); /* make child continue to exit */ SAFE_KILL(pid, SIGCONT); SAFE_WAITPID(pid, &status, 0); max_maps = max_maps << 1; } } static struct tst_test test = { .needs_root = 1, .forks_child = 1, .setup = setup, .cleanup = cleanup, .test_all = max_map_count_test, };