1 /*
2 * Copyright (C) 2012-2017 Red Hat, Inc.
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
12 * the GNU General Public License for more details.
13 *
14 * Description:
15 *
16 * The program is designed to test max_map_count tunable file
17 *
18 * The kernel Documentation say that:
19 * /proc/sys/vm/max_map_count contains the maximum number of memory map
20 * areas a process may have. Memory map areas are used as a side-effect
21 * of calling malloc, directly by mmap and mprotect, and also when
22 * loading shared libraries.
23 *
24 * Each process has his own maps file: /proc/[pid]/maps, and each line
25 * indicates a map entry, so it can caculate the amount of maps by reading
26 * the file lines' number to check the tunable performance.
27 *
28 * The program tries to invoke mmap() endlessly until it triggers MAP_FAILED,
29 * then reads the process's maps file /proc/[pid]/maps, save the line number to
30 * map_count variable, and compare it with /proc/sys/vm/max_map_count,
31 * map_count should be greater than max_map_count by 1;
32 *
33 * Note: On some architectures there is a special vma VSYSCALL, which
34 * is allocated without incrementing mm->map_count variable. On these
35 * architectures each /proc/<pid>/maps has at the end:
36 * ...
37 * ...
38 * ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
39 *
40 * so we ignore this line during /proc/[pid]/maps reading.
41 */
42
43 #define _GNU_SOURCE
44 #include <sys/wait.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <sys/utsname.h>
51 #include "mem.h"
52
53 #define MAP_COUNT_DEFAULT 1024
54 #define MAX_MAP_COUNT 65536L
55
56 static long old_max_map_count = -1;
57 static long old_overcommit = -1;
58
setup(void)59 static void setup(void)
60 {
61 if (access(PATH_SYSVM "max_map_count", F_OK) != 0)
62 tst_brk(TBROK | TERRNO,
63 "Can't support to test max_map_count");
64
65 old_max_map_count = get_sys_tune("max_map_count");
66 old_overcommit = get_sys_tune("overcommit_memory");
67 set_sys_tune("overcommit_memory", 0, 1);
68 }
69
cleanup(void)70 static void cleanup(void)
71 {
72 if (old_overcommit != -1)
73 set_sys_tune("overcommit_memory", old_overcommit, 0);
74 if (old_max_map_count != -1)
75 set_sys_tune("max_map_count", old_max_map_count, 0);
76 }
77
78 /* This is a filter to exclude map entries which aren't accounted
79 * for in the vm_area_struct's map_count.
80 */
filter_map(const char * line)81 static bool filter_map(const char *line)
82 {
83 char buf[BUFSIZ];
84 int ret;
85
86 ret = sscanf(line, "%*p-%*p %*4s %*p %*2d:%*2d %*d %s", buf);
87 if (ret != 1)
88 return false;
89
90 switch (tst_arch.type) {
91 case TST_X86:
92 case TST_X86_64:
93 /* On x86, there's an old compat vsyscall page */
94 if (!strcmp(buf, "[vsyscall]"))
95 return true;
96 break;
97 case TST_IA64:
98 /* On ia64, the vdso is not a proper mapping */
99 if (!strcmp(buf, "[vdso]"))
100 return true;
101 break;
102 case TST_ARM:
103 /* Skip it when run it in aarch64 */
104 if (tst_kernel_bits() == 64)
105 return false;
106
107 /* Older arm kernels didn't label their vdso maps */
108 if (!strncmp(line, "ffff0000-ffff1000", 17))
109 return true;
110 break;
111 default:
112 break;
113 };
114
115 return false;
116 }
117
count_maps(pid_t pid)118 static long count_maps(pid_t pid)
119 {
120 FILE *fp;
121 size_t len;
122 char *line = NULL;
123 char buf[BUFSIZ];
124 long map_count = 0;
125
126 snprintf(buf, BUFSIZ, "/proc/%d/maps", pid);
127 fp = fopen(buf, "r");
128 if (fp == NULL)
129 tst_brk(TBROK | TERRNO, "fopen %s", buf);
130 while (getline(&line, &len, fp) != -1) {
131 /* exclude vdso and vsyscall */
132 if (filter_map(line))
133 continue;
134 map_count++;
135 }
136 fclose(fp);
137
138 return map_count;
139 }
140
max_map_count_test(void)141 static void max_map_count_test(void)
142 {
143 int status;
144 pid_t pid;
145 long max_maps;
146 long map_count;
147 long max_iters;
148 long memfree;
149
150 /*
151 * XXX Due to a possible kernel bug, oom-killer can be easily
152 * triggered when doing small piece mmaps in huge amount even if
153 * enough free memory available. Also it has been observed that
154 * oom-killer often kill wrong victims in this situation, we
155 * decided to do following steps to make sure no oom happen:
156 * 1) use a safe maximum max_map_count value as upper-bound,
157 * we set it 65536 in this case, i.e., we don't test too big
158 * value;
159 * 2) make sure total mapping isn't larger than
160 * CommitLimit - Committed_AS
161 */
162 memfree = SAFE_READ_MEMINFO("CommitLimit:") - SAFE_READ_MEMINFO("Committed_AS:");
163 /* 64 used as a bias to make sure no overflow happen */
164 max_iters = memfree / sysconf(_SC_PAGESIZE) * 1024 - 64;
165 if (max_iters > MAX_MAP_COUNT)
166 max_iters = MAX_MAP_COUNT;
167
168 max_maps = MAP_COUNT_DEFAULT;
169 if (max_iters < max_maps)
170 tst_brk(TCONF, "test requires more free memory");
171
172 while (max_maps <= max_iters) {
173 set_sys_tune("max_map_count", max_maps, 1);
174
175 switch (pid = SAFE_FORK()) {
176 case 0:
177 while (mmap(NULL, 1, PROT_READ,
178 MAP_SHARED | MAP_ANONYMOUS, -1, 0)
179 != MAP_FAILED) ;
180 if (raise(SIGSTOP) != 0)
181 tst_brk(TBROK | TERRNO, "raise");
182 exit(0);
183 default:
184 break;
185 }
186 /* wait child done mmap and stop */
187 SAFE_WAITPID(pid, &status, WUNTRACED);
188 if (!WIFSTOPPED(status))
189 tst_brk(TBROK, "child did not stopped");
190
191 map_count = count_maps(pid);
192 /* Note max_maps will be exceeded by one for
193 * the sysctl setting of max_map_count. This
194 * is the mm failure point at the time of
195 * writing this COMMENT!
196 */
197 if (map_count == (max_maps + 1))
198 tst_res(TPASS, "%ld map entries in total "
199 "as expected.", max_maps);
200 else
201 tst_res(TFAIL, "%ld map entries in total, but "
202 "expected %ld entries", map_count, max_maps);
203
204 /* make child continue to exit */
205 SAFE_KILL(pid, SIGCONT);
206 SAFE_WAITPID(pid, &status, 0);
207
208 max_maps = max_maps << 1;
209 }
210 }
211
212 static struct tst_test test = {
213 .needs_root = 1,
214 .forks_child = 1,
215 .setup = setup,
216 .cleanup = cleanup,
217 .test_all = max_map_count_test,
218 };
219