1 /*
2 * The program is designed to test max_map_count tunable file
3 *
4 * The kernel Documentation say that:
5 * /proc/sys/vm/max_map_count contains the maximum number of memory map
6 * areas a process may have. Memory map areas are used as a side-effect
7 * of calling malloc, directly by mmap and mprotect, and also when
8 * loading shared libraries.
9 *
10 * Each process has his own maps file: /proc/[pid]/maps, and each line
11 * indicates a map entry, so it can caculate the amount of maps by reading
12 * the file lines' number to check the tunable performance.
13 *
14 * The program tries to invoke mmap() endlessly until it triggers MAP_FAILED,
15 * then reads the process's maps file /proc/[pid]/maps, save the line number to
16 * map_count variable, and compare it with /proc/sys/vm/max_map_count,
17 * map_count should be greater than max_map_count by 1;
18 *
19 * Note: On some architectures there is a special vma VSYSCALL, which
20 * is allocated without incrementing mm->map_count variable. On these
21 * architectures each /proc/<pid>/maps has at the end:
22 * ...
23 * ...
24 * ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
25 *
26 * so we ignore this line during /proc/[pid]/maps reading.
27 *
28 * ********************************************************************
29 * Copyright (C) 2012 Red Hat, Inc.
30 *
31 * This program is free software; you can redistribute it and/or
32 * modify it under the terms of version 2 of the GNU General Public
33 * License as published by the Free Software Foundation.
34 *
35 * This program is distributed in the hope that it would be useful,
36 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
38 *
39 * Further, this software is distributed without any warranty that it
40 * is free of the rightful claim of any third person regarding
41 * infringement or the like. Any license provided herein, whether
42 * implied or otherwise, applies only to this software file. Patent
43 * licenses, if any, provided herein do not apply to combinations of
44 * this program with other software, or any other product whatsoever.
45 *
46 * You should have received a copy of the GNU General Public License
47 * along with this program; if not, write the Free Software
48 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
49 * 02110-1301, USA.
50 *
51 * ********************************************************************
52 */
53 #define _GNU_SOURCE
54 #include <sys/types.h>
55 #include <sys/mman.h>
56 #include <sys/wait.h>
57 #include <errno.h>
58 #include <fcntl.h>
59 #include <stdbool.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <sys/utsname.h>
63 #include "test.h"
64 #include "mem.h"
65
66 #define MAP_COUNT_DEFAULT 1024
67 #define MAX_MAP_COUNT 65536L
68
69 char *TCID = "max_map_count";
70 int TST_TOTAL = 1;
71
72 static long old_max_map_count;
73 static long old_overcommit;
74 static struct utsname un;
75
76 static long count_maps(pid_t pid);
77 static void max_map_count_test(void);
78
main(int argc,char * argv[])79 int main(int argc, char *argv[])
80 {
81 int lc;
82
83 tst_parse_opts(argc, argv, NULL, NULL);
84
85 setup();
86 for (lc = 0; TEST_LOOPING(lc); lc++) {
87 tst_count = 0;
88 max_map_count_test();
89 }
90
91 cleanup();
92 tst_exit();
93 }
94
setup(void)95 void setup(void)
96 {
97 tst_require_root();
98
99 tst_sig(FORK, DEF_HANDLER, cleanup);
100 TEST_PAUSE;
101
102 if (access(PATH_SYSVM "max_map_count", F_OK) != 0)
103 tst_brkm(TBROK | TERRNO, NULL,
104 "Can't support to test max_map_count");
105
106 old_max_map_count = get_sys_tune("max_map_count");
107 old_overcommit = get_sys_tune("overcommit_memory");
108 set_sys_tune("overcommit_memory", 2, 1);
109
110 if (uname(&un) != 0)
111 tst_brkm(TBROK | TERRNO, NULL, "uname error");
112 }
113
cleanup(void)114 void cleanup(void)
115 {
116 set_sys_tune("overcommit_memory", old_overcommit, 0);
117 set_sys_tune("max_map_count", old_max_map_count, 0);
118 }
119
120 /* This is a filter to exclude map entries which aren't accounted
121 * for in the vm_area_struct's map_count.
122 */
filter_map(const char * line)123 static bool filter_map(const char *line)
124 {
125 char buf[BUFSIZ];
126 int ret;
127
128 ret = sscanf(line, "%*p-%*p %*4s %*p %*2d:%*2d %*d %s", buf);
129 if (ret != 1)
130 return false;
131
132 #if defined(__x86_64__) || defined(__x86__)
133 /* On x86, there's an old compat vsyscall page */
134 if (!strcmp(buf, "[vsyscall]"))
135 return true;
136 #elif defined(__ia64__)
137 /* On ia64, the vdso is not a proper mapping */
138 if (!strcmp(buf, "[vdso]"))
139 return true;
140 #elif defined(__arm__)
141 /* Skip it when run it in aarch64 */
142 if ((!strcmp(un.machine, "aarch64"))
143 || (!strcmp(un.machine, "aarch64_be")))
144 return false;
145
146 /* Older arm kernels didn't label their vdso maps */
147 if (!strncmp(line, "ffff0000-ffff1000", 17))
148 return true;
149 #endif
150
151 return false;
152 }
153
count_maps(pid_t pid)154 static long count_maps(pid_t pid)
155 {
156 FILE *fp;
157 size_t len;
158 char *line = NULL;
159 char buf[BUFSIZ];
160 long map_count = 0;
161
162 snprintf(buf, BUFSIZ, "/proc/%d/maps", pid);
163 fp = fopen(buf, "r");
164 if (fp == NULL)
165 tst_brkm(TBROK | TERRNO, cleanup, "fopen %s", buf);
166 while (getline(&line, &len, fp) != -1) {
167 /* exclude vdso and vsyscall */
168 if (filter_map(line))
169 continue;
170 map_count++;
171 }
172 fclose(fp);
173
174 return map_count;
175 }
176
max_map_count_test(void)177 static void max_map_count_test(void)
178 {
179 int status;
180 pid_t pid;
181 long max_maps;
182 long map_count;
183 long max_iters;
184 long memfree;
185
186 /*
187 * XXX Due to a possible kernel bug, oom-killer can be easily
188 * triggered when doing small piece mmaps in huge amount even if
189 * enough free memory available. Also it has been observed that
190 * oom-killer often kill wrong victims in this situation, we
191 * decided to do following steps to make sure no oom happen:
192 * 1) use a safe maximum max_map_count value as upper-bound,
193 * we set it 65536 in this case, i.e., we don't test too big
194 * value;
195 * 2) make sure total mapping isn't larger tha
196 * CommitLimit - Committed_AS
197 * and set overcommit_memory to 2, this could help mapping
198 * returns ENOMEM instead of triggering oom-killer when
199 * memory is tight. (When there are enough free memory,
200 * step 1) will be used first.
201 * Hope OOM-killer can be more stable oneday.
202 */
203 memfree = read_meminfo("CommitLimit:") - read_meminfo("Committed_AS:");
204 /* 64 used as a bias to make sure no overflow happen */
205 max_iters = memfree / sysconf(_SC_PAGESIZE) * 1024 - 64;
206 if (max_iters > MAX_MAP_COUNT)
207 max_iters = MAX_MAP_COUNT;
208
209 max_maps = MAP_COUNT_DEFAULT;
210 while (max_maps <= max_iters) {
211 set_sys_tune("max_map_count", max_maps, 1);
212
213 switch (pid = tst_fork()) {
214 case -1:
215 tst_brkm(TBROK | TERRNO, cleanup, "fork");
216 case 0:
217 while (mmap(NULL, 1, PROT_READ,
218 MAP_SHARED | MAP_ANONYMOUS, -1, 0)
219 != MAP_FAILED) ;
220 if (raise(SIGSTOP) != 0)
221 tst_brkm(TBROK | TERRNO, tst_exit, "raise");
222 exit(0);
223 default:
224 break;
225 }
226 /* wait child done mmap and stop */
227 if (waitpid(pid, &status, WUNTRACED) == -1)
228 tst_brkm(TBROK | TERRNO, cleanup, "waitpid");
229 if (!WIFSTOPPED(status))
230 tst_brkm(TBROK, cleanup, "child did not stopped");
231
232 map_count = count_maps(pid);
233 /* Note max_maps will be exceeded by one for
234 * the sysctl setting of max_map_count. This
235 * is the mm failure point at the time of
236 * writing this COMMENT!
237 */
238 if (map_count == (max_maps + 1))
239 tst_resm(TPASS, "%ld map entries in total "
240 "as expected.", max_maps);
241 else
242 tst_resm(TFAIL, "%ld map entries in total, but "
243 "expected %ld entries", map_count, max_maps);
244
245 /* make child continue to exit */
246 if (kill(pid, SIGCONT) != 0)
247 tst_brkm(TBROK | TERRNO, cleanup, "kill");
248 if (waitpid(pid, &status, 0) == -1)
249 tst_brkm(TBROK | TERRNO, cleanup, "waitpid");
250
251 max_maps = max_maps << 1;
252 }
253 }
254