• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2012-2020 Linux Test Project
4  * Copyright (c) 2012-2017 Red Hat, Inc.
5  *
6  * There are two tunables overcommit_memory and overcommit_ratio under
7  * /proc/sys/vm/, which can control memory overcommitment.
8  *
9  * The overcommit_memory contains a flag that enables memory
10  * overcommitment, it has three values:
11  * - When this flag is 0, the kernel attempts to estimate the amount
12  *   of free memory left when userspace requests more memory.
13  * - When this flag is 1, the kernel pretends there is always enough
14  *   memory until it actually runs out.
15  * - When this flag is 2, the kernel uses a "never overcommit" policy
16  *   that attempts to prevent any overcommit of memory.
17  *
18  * The overcommit_ratio tunable defines the amount by which the kernel
19  * overextends its memory resources in the event that overcommit_memory
20  * is set to the value of 2. The value in this file represents a
21  * percentage added to the amount of actual RAM in a system when
22  * considering whether to grant a particular memory request.
23  * The general formula for this tunable is:
24  * CommitLimit = SwapTotal + MemTotal * overcommit_ratio
25  * CommitLimit, SwapTotal and MemTotal can read from /proc/meminfo.
26  *
27  * The program is designed to test the two tunables:
28  *
29  * When overcommit_memory = 0, allocatable memory can't overextend
30  * the amount of total memory:
31  * a. less than free_total:    free_total / 2, alloc should pass.
32  * b. greater than sum_total:   sum_total * 2, alloc should fail.
33  *
34  * When overcommit_memory = 1, it can alloc enough much memory, I
35  * choose the three cases:
36  * a. less than sum_total:    sum_total / 2, alloc should pass
37  * b. equal to sum_total:     sum_total,     alloc should pass
38  * c. greater than sum_total: sum_total * 2, alloc should pass
39  * *note: sum_total = SwapTotal + MemTotal
40  *
41  * When overcommit_memory = 2, the total virtual address space on
42  * the system is limited to CommitLimit(Swap+RAM*overcommit_ratio)
43  * commit_left(allocatable memory) = CommitLimit - Committed_AS
44  * a. less than commit_left:    commit_left / 2, alloc should pass
45  * b. overcommit limit:         CommitLimit + TotalBatchSize, should fail
46  * c. greater than commit_left: commit_left * 2, alloc should fail
47  * *note: CommitLimit is the current overcommit limit.
48  *        Committed_AS is the amount of memory that system has used.
49  * it couldn't choose 'equal to commit_left' as a case, because
50  * commit_left rely on Committed_AS, but the Committed_AS is not stable.
51  * *note2: TotalBatchSize is the total number of bytes, that can be
52  *         accounted for in the per cpu counters for the vm_committed_as
53  *         counter. Since the check used by malloc only looks at the
54  *         global counter of vm_committed_as, it can overallocate a bit.
55  *
56  * References:
57  * - Documentation/sysctl/vm.txt
58  * - Documentation/vm/overcommit-accounting
59  */
60 
61 #include <errno.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <limits.h>
65 #include "lapi/abisize.h"
66 #include "mem.h"
67 
68 #define DEFAULT_OVER_RATIO	50L
69 #define EXPECT_PASS		0
70 #define EXPECT_FAIL		1
71 
72 static char *R_opt;
73 static long old_overcommit_memory = -1;
74 static long old_overcommit_ratio = -1;
75 static long overcommit_ratio;
76 static long sum_total;
77 static long free_total;
78 static long commit_limit;
79 static long commit_left;
80 static long total_batch_size;
81 
82 static int heavy_malloc(long size);
83 static void alloc_and_check(long size, int expect_result);
84 static void update_mem(void);
85 static void update_mem_commit(void);
86 static void calculate_total_batch_size(void);
87 
setup(void)88 static void setup(void)
89 {
90 	long mem_total, swap_total;
91 	struct rlimit lim;
92 
93 	if (access(PATH_SYSVM "overcommit_memory", F_OK) == -1 ||
94 	    access(PATH_SYSVM "overcommit_ratio", F_OK) == -1)
95 		tst_brk(TCONF, "system doesn't support overcommit_memory");
96 
97 	if (R_opt)
98 		overcommit_ratio = SAFE_STRTOL(R_opt, 0, LONG_MAX);
99 	else
100 		overcommit_ratio = DEFAULT_OVER_RATIO;
101 
102 	old_overcommit_memory = get_sys_tune("overcommit_memory");
103 	old_overcommit_ratio = get_sys_tune("overcommit_ratio");
104 
105 	mem_total = SAFE_READ_MEMINFO("MemTotal:");
106 	tst_res(TINFO, "MemTotal is %ld kB", mem_total);
107 	swap_total = SAFE_READ_MEMINFO("SwapTotal:");
108 	tst_res(TINFO, "SwapTotal is %ld kB", swap_total);
109 	sum_total = mem_total + swap_total;
110 
111 	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
112 	tst_res(TINFO, "CommitLimit is %ld kB", commit_limit);
113 
114 	SAFE_GETRLIMIT(RLIMIT_AS, &lim);
115 
116 	if (lim.rlim_cur != RLIM_INFINITY) {
117 		lim.rlim_cur = RLIM_INFINITY;
118 		lim.rlim_max = RLIM_INFINITY;
119 
120 		tst_res(TINFO, "Increasing RLIM_AS to INFINITY");
121 
122 		SAFE_SETRLIMIT(RLIMIT_AS, &lim);
123 	}
124 
125 	set_sys_tune("overcommit_ratio", overcommit_ratio, 1);
126 
127 	calculate_total_batch_size();
128 	tst_res(TINFO, "TotalBatchSize is %ld kB", total_batch_size);
129 }
130 
cleanup(void)131 static void cleanup(void)
132 {
133 	if (old_overcommit_memory != -1)
134 		set_sys_tune("overcommit_memory", old_overcommit_memory, 0);
135 	if (old_overcommit_ratio != -1)
136 		set_sys_tune("overcommit_ratio", old_overcommit_ratio, 0);
137 }
138 
overcommit_memory_test(void)139 static void overcommit_memory_test(void)
140 {
141 
142 #ifdef TST_ABI32
143 	tst_brk(TCONF, "test is not designed for 32-bit system.");
144 #endif
145 	/* start to test overcommit_memory=2 */
146 	set_sys_tune("overcommit_memory", 2, 1);
147 
148 	update_mem_commit();
149 	alloc_and_check(commit_left * 2, EXPECT_FAIL);
150 	alloc_and_check(commit_limit + total_batch_size, EXPECT_FAIL);
151 	update_mem_commit();
152 	alloc_and_check(commit_left / 2, EXPECT_PASS);
153 
154 	/* start to test overcommit_memory=0 */
155 	set_sys_tune("overcommit_memory", 0, 1);
156 
157 	update_mem();
158 	alloc_and_check(free_total / 2, EXPECT_PASS);
159 	alloc_and_check(sum_total * 2, EXPECT_FAIL);
160 
161 	/* start to test overcommit_memory=1 */
162 	set_sys_tune("overcommit_memory", 1, 1);
163 
164 	alloc_and_check(sum_total / 2, EXPECT_PASS);
165 	alloc_and_check(sum_total, EXPECT_PASS);
166 	alloc_and_check(sum_total * 2, EXPECT_PASS);
167 
168 }
169 
heavy_malloc(long size)170 static int heavy_malloc(long size)
171 {
172 	char *p;
173 
174 	p = malloc(size * KB);
175 	if (p != NULL) {
176 		tst_res(TINFO, "malloc %ld kB successfully", size);
177 		free(p);
178 		return 0;
179 	} else {
180 		tst_res(TINFO, "malloc %ld kB failed", size);
181 		return 1;
182 	}
183 }
184 
alloc_and_check(long size,int expect_result)185 static void alloc_and_check(long size, int expect_result)
186 {
187 	int result;
188 
189 	/* try to alloc size kB memory */
190 	result = heavy_malloc(size);
191 
192 	switch (expect_result) {
193 	case EXPECT_PASS:
194 		if (result == 0)
195 			tst_res(TPASS, "alloc passed as expected");
196 		else
197 			tst_res(TFAIL, "alloc failed, expected to pass");
198 		break;
199 	case EXPECT_FAIL:
200 		if (result != 0)
201 			tst_res(TPASS, "alloc failed as expected");
202 		else
203 			tst_res(TFAIL, "alloc passed, expected to fail");
204 		break;
205 	default:
206 		tst_brk(TBROK, "Invalid number parameter: %d",
207 			 expect_result);
208 	}
209 }
210 
update_mem(void)211 static void update_mem(void)
212 {
213 	long mem_free, swap_free;
214 
215 	mem_free = SAFE_READ_MEMINFO("MemFree:");
216 	swap_free = SAFE_READ_MEMINFO("SwapFree:");
217 	free_total = mem_free + swap_free;
218 }
219 
update_mem_commit(void)220 static void update_mem_commit(void)
221 {
222 	long committed;
223 
224 	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
225 	committed = SAFE_READ_MEMINFO("Committed_AS:");
226 	commit_left = commit_limit - committed;
227 
228 	if (commit_left < 0) {
229 		tst_res(TINFO, "CommitLimit is %ld, Committed_AS is %ld",
230 			commit_limit, committed);
231 
232 		if (overcommit_ratio > old_overcommit_ratio) {
233 			tst_brk(TBROK, "Unexpected error: "
234 				"CommitLimit < Committed_AS");
235 		}
236 
237 		tst_brk(TCONF, "Specified overcommit_ratio %ld <= default %ld, "
238 			"so it's possible for CommitLimit < Committed_AS and skip test",
239 			overcommit_ratio, old_overcommit_ratio);
240 	}
241 }
242 
calculate_total_batch_size(void)243 static void calculate_total_batch_size(void)
244 {
245 	struct sysinfo info;
246 	long ncpus = tst_ncpus_conf();
247 	long pagesize = getpagesize();
248 	SAFE_SYSINFO(&info);
249 
250 	/* see linux source mm/mm_init.c mm_compute_batch() (This is in pages) */
251 	long batch_size = MAX(ncpus * 2,
252 	                      MAX(32,
253 	                          MIN(INT32_MAX,
254 	                              (long)(info.totalram / pagesize) / ncpus / 256
255 	                          )
256 	                      )
257 	                  );
258 
259 	/* there are ncpu separate counters, that can all grow up to
260 	 * batch_size. So the maximum error for __vm_enough_memory is
261 	 * batch_size * ncpus. */
262 	total_batch_size = (batch_size * ncpus * pagesize) / KB;
263 }
264 
265 static struct tst_test test = {
266 	.needs_root = 1,
267 	.options = (struct tst_option[]) {
268 		{"R:", &R_opt, "Percentage of overcommitting memory"},
269 		{}
270 	},
271 	.setup = setup,
272 	.cleanup = cleanup,
273 	.test_all = overcommit_memory_test,
274 };
275