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 trys to invoke mmap() endless until triggering MAP_FAILED,
15 * then read the process's maps file /proc/[pid]/maps, save the line number
16 * to 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 64
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 return false;
144
145 /* Older arm kernels didn't label their vdso maps */
146 if (!strncmp(line, "ffff0000-ffff1000", 17))
147 return true;
148 #endif
149
150 return false;
151 }
152
count_maps(pid_t pid)153 static long count_maps(pid_t pid)
154 {
155 FILE *fp;
156 size_t len;
157 char *line = NULL;
158 char buf[BUFSIZ];
159 long map_count = 0;
160
161 snprintf(buf, BUFSIZ, "/proc/%d/maps", pid);
162 fp = fopen(buf, "r");
163 if (fp == NULL)
164 tst_brkm(TBROK | TERRNO, cleanup, "fopen %s", buf);
165 while (getline(&line, &len, fp) != -1) {
166 /* exclude vdso and vsyscall */
167 if (filter_map(line))
168 continue;
169 map_count++;
170 }
171 fclose(fp);
172
173 return map_count;
174 }
175
max_map_count_test(void)176 static void max_map_count_test(void)
177 {
178 int status;
179 pid_t pid;
180 long max_maps;
181 long map_count;
182 long max_iters;
183 long memfree;
184
185 /*
186 * XXX Due to a possible kernel bug, oom-killer can be easily
187 * triggered when doing small piece mmaps in huge amount even if
188 * enough free memory available. Also it has been observed that
189 * oom-killer often kill wrong victims in this situation, we
190 * decided to do following steps to make sure no oom happen:
191 * 1) use a safe maximum max_map_count value as upper-bound,
192 * we set it 65536 in this case, i.e., we don't test too big
193 * value;
194 * 2) make sure total mapping isn't larger tha
195 * CommitLimit - Committed_AS
196 * and set overcommit_memory to 2, this could help mapping
197 * returns ENOMEM instead of triggering oom-killer when
198 * memory is tight. (When there are enough free memory,
199 * step 1) will be used first.
200 * Hope OOM-killer can be more stable oneday.
201 */
202 memfree = read_meminfo("CommitLimit:") - read_meminfo("Committed_AS:");
203 /* 64 used as a bias to make sure no overflow happen */
204 max_iters = memfree / sysconf(_SC_PAGESIZE) * 1024 - 64;
205 if (max_iters > MAX_MAP_COUNT)
206 max_iters = MAX_MAP_COUNT;
207
208 max_maps = MAP_COUNT_DEFAULT;
209 while (max_maps <= max_iters) {
210 set_sys_tune("max_map_count", max_maps, 1);
211
212 switch (pid = tst_fork()) {
213 case -1:
214 tst_brkm(TBROK | TERRNO, cleanup, "fork");
215 case 0:
216 while (mmap(NULL, 1, PROT_READ,
217 MAP_SHARED | MAP_ANONYMOUS, -1, 0)
218 != MAP_FAILED) ;
219 if (raise(SIGSTOP) != 0)
220 tst_brkm(TBROK | TERRNO, tst_exit, "raise");
221 exit(0);
222 default:
223 break;
224 }
225 /* wait child done mmap and stop */
226 if (waitpid(pid, &status, WUNTRACED) == -1)
227 tst_brkm(TBROK | TERRNO, cleanup, "waitpid");
228 if (!WIFSTOPPED(status))
229 tst_brkm(TBROK, cleanup, "child did not stopped");
230
231 map_count = count_maps(pid);
232 /* Note max_maps will be exceeded by one for
233 * the sysctl setting of max_map_count. This
234 * is the mm failure point at the time of
235 * writing this COMMENT!
236 */
237 if (map_count == (max_maps + 1))
238 tst_resm(TPASS, "%ld map entries in total "
239 "as expected.", max_maps);
240 else
241 tst_resm(TFAIL, "%ld map entries in total, but "
242 "expected %ld entries", map_count, max_maps);
243
244 /* make child continue to exit */
245 if (kill(pid, SIGCONT) != 0)
246 tst_brkm(TBROK | TERRNO, cleanup, "kill");
247 if (waitpid(pid, &status, 0) == -1)
248 tst_brkm(TBROK | TERRNO, cleanup, "waitpid");
249
250 max_maps = max_maps << 2;
251 }
252 }
253