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