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_ratio = -1;
74 static long overcommit_ratio;
75 static long sum_total;
76 static long free_total;
77 static long commit_limit;
78 static long commit_left;
79 static long total_batch_size;
80
81 static int heavy_malloc(long size);
82 static void alloc_and_check(long size, int expect_result);
83 static void update_mem(void);
84 static void update_mem_commit(void);
85 static void calculate_total_batch_size(void);
86
setup(void)87 static 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
overcommit_memory_test(void)125 static 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
heavy_malloc(long size)156 static 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
alloc_and_check(long size,int expect_result)171 static 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
update_mem(void)197 static 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
update_mem_commit(void)206 static 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
calculate_total_batch_size(void)229 static 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
251 static 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