• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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