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
72static char *R_opt;
73static long old_overcommit_ratio = -1;
74static long overcommit_ratio;
75static long sum_total;
76static long free_total;
77static long commit_limit;
78static long commit_left;
79static long total_batch_size;
80
81static int heavy_malloc(long size);
82static void alloc_and_check(long size, int expect_result);
83static void update_mem(void);
84static void update_mem_commit(void);
85static void calculate_total_batch_size(void);
86
87static void setup(void)
88{
89	long mem_total, swap_total;
90	struct rlimit lim;
91
92	if (R_opt)
93		overcommit_ratio = SAFE_STRTOL(R_opt, 0, LONG_MAX);
94	else
95		overcommit_ratio = DEFAULT_OVER_RATIO;
96
97	old_overcommit_ratio = get_sys_tune("overcommit_ratio");
98
99	mem_total = SAFE_READ_MEMINFO("MemTotal:");
100	tst_res(TINFO, "MemTotal is %ld kB", mem_total);
101	swap_total = SAFE_READ_MEMINFO("SwapTotal:");
102	tst_res(TINFO, "SwapTotal is %ld kB", swap_total);
103	sum_total = mem_total + swap_total;
104
105	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
106	tst_res(TINFO, "CommitLimit is %ld kB", commit_limit);
107
108	SAFE_GETRLIMIT(RLIMIT_AS, &lim);
109
110	if (lim.rlim_cur != RLIM_INFINITY) {
111		lim.rlim_cur = RLIM_INFINITY;
112		lim.rlim_max = RLIM_INFINITY;
113
114		tst_res(TINFO, "Increasing RLIM_AS to INFINITY");
115
116		SAFE_SETRLIMIT(RLIMIT_AS, &lim);
117	}
118
119	set_sys_tune("overcommit_ratio", overcommit_ratio, 1);
120
121	calculate_total_batch_size();
122	tst_res(TINFO, "TotalBatchSize is %ld kB", total_batch_size);
123}
124
125static void overcommit_memory_test(void)
126{
127
128#ifdef TST_ABI32
129	tst_brk(TCONF, "test is not designed for 32-bit system.");
130#endif
131	/* start to test overcommit_memory=2 */
132	set_sys_tune("overcommit_memory", 2, 1);
133
134	update_mem_commit();
135	alloc_and_check(commit_left * 2, EXPECT_FAIL);
136	alloc_and_check(commit_limit + total_batch_size, EXPECT_FAIL);
137	update_mem_commit();
138	alloc_and_check(commit_left / 2, EXPECT_PASS);
139
140	/* start to test overcommit_memory=0 */
141	set_sys_tune("overcommit_memory", 0, 1);
142
143	update_mem();
144	alloc_and_check(free_total / 2, EXPECT_PASS);
145	alloc_and_check(sum_total * 2, EXPECT_FAIL);
146
147	/* start to test overcommit_memory=1 */
148	set_sys_tune("overcommit_memory", 1, 1);
149
150	alloc_and_check(sum_total / 2, EXPECT_PASS);
151	alloc_and_check(sum_total, EXPECT_PASS);
152	alloc_and_check(sum_total * 2, EXPECT_PASS);
153
154}
155
156static int heavy_malloc(long size)
157{
158	char *p;
159
160	p = malloc(size * KB);
161	if (p != NULL) {
162		tst_res(TINFO, "malloc %ld kB successfully", size);
163		free(p);
164		return 0;
165	} else {
166		tst_res(TINFO, "malloc %ld kB failed", size);
167		return 1;
168	}
169}
170
171static void alloc_and_check(long size, int expect_result)
172{
173	int result;
174
175	/* try to alloc size kB memory */
176	result = heavy_malloc(size);
177
178	switch (expect_result) {
179	case EXPECT_PASS:
180		if (result == 0)
181			tst_res(TPASS, "alloc passed as expected");
182		else
183			tst_res(TFAIL, "alloc failed, expected to pass");
184		break;
185	case EXPECT_FAIL:
186		if (result != 0)
187			tst_res(TPASS, "alloc failed as expected");
188		else
189			tst_res(TFAIL, "alloc passed, expected to fail");
190		break;
191	default:
192		tst_brk(TBROK, "Invalid number parameter: %d",
193			 expect_result);
194	}
195}
196
197static void update_mem(void)
198{
199	long mem_free, swap_free;
200
201	mem_free = SAFE_READ_MEMINFO("MemFree:");
202	swap_free = SAFE_READ_MEMINFO("SwapFree:");
203	free_total = mem_free + swap_free;
204}
205
206static void update_mem_commit(void)
207{
208	long committed;
209
210	commit_limit = SAFE_READ_MEMINFO("CommitLimit:");
211	committed = SAFE_READ_MEMINFO("Committed_AS:");
212	commit_left = commit_limit - committed;
213
214	if (commit_left < 0) {
215		tst_res(TINFO, "CommitLimit is %ld, Committed_AS is %ld",
216			commit_limit, committed);
217
218		if (overcommit_ratio > old_overcommit_ratio) {
219			tst_brk(TBROK, "Unexpected error: "
220				"CommitLimit < Committed_AS");
221		}
222
223		tst_brk(TCONF, "Specified overcommit_ratio %ld <= default %ld, "
224			"so it's possible for CommitLimit < Committed_AS and skip test",
225			overcommit_ratio, old_overcommit_ratio);
226	}
227}
228
229static void calculate_total_batch_size(void)
230{
231	struct sysinfo info;
232	long ncpus = tst_ncpus_conf();
233	long pagesize = getpagesize();
234	SAFE_SYSINFO(&info);
235
236	/* see linux source mm/mm_init.c mm_compute_batch() (This is in pages) */
237	long batch_size = MAX(ncpus * 2L,
238	                      MAX(32L,
239	                          MIN((long)INT32_MAX,
240	                              (long)(info.totalram / pagesize) / ncpus / 256
241	                          )
242	                      )
243	                  );
244
245	/* there are ncpu separate counters, that can all grow up to
246	 * batch_size. So the maximum error for __vm_enough_memory is
247	 * batch_size * ncpus. */
248	total_batch_size = (batch_size * ncpus * pagesize) / KB;
249}
250
251static struct tst_test test = {
252	.needs_root = 1,
253	.options = (struct tst_option[]) {
254		{"R:", &R_opt, "Percentage of overcommitting memory"},
255		{}
256	},
257	.setup = setup,
258	.test_all = overcommit_memory_test,
259	.save_restore = (const struct tst_path_val[]) {
260		{"/proc/sys/vm/overcommit_memory", NULL, TST_SR_TBROK},
261		{"/proc/sys/vm/overcommit_ratio", NULL, TST_SR_TBROK},
262		{}
263	},
264};
265