• 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 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