1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
2f08c3bdfSopenharmony_ci/*
3f08c3bdfSopenharmony_ci * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz>
4f08c3bdfSopenharmony_ci */
5f08c3bdfSopenharmony_ci
6f08c3bdfSopenharmony_ci/*
7f08c3bdfSopenharmony_ci * Check that memory marked with MADV_FREE is freed on memory pressure.
8f08c3bdfSopenharmony_ci *
9f08c3bdfSopenharmony_ci * o Fork a child and move it into a memory cgroup
10f08c3bdfSopenharmony_ci *
11f08c3bdfSopenharmony_ci * o Allocate pages and fill them with a pattern
12f08c3bdfSopenharmony_ci *
13f08c3bdfSopenharmony_ci * o Madvise pages with MADV_FREE
14f08c3bdfSopenharmony_ci *
15f08c3bdfSopenharmony_ci * o Check that madvised pages were not freed immediately
16f08c3bdfSopenharmony_ci *
17f08c3bdfSopenharmony_ci * o Write to some of the madvised pages again, these must not be freed
18f08c3bdfSopenharmony_ci *
19f08c3bdfSopenharmony_ci * o Set memory limits
20f08c3bdfSopenharmony_ci *   - limit_in_bytes = 8MB
21f08c3bdfSopenharmony_ci *   - memsw.limit_in_bytes = 16MB
22f08c3bdfSopenharmony_ci *
23f08c3bdfSopenharmony_ci *   The reason for doubling the limit_in_bytes is to have safe margin
24f08c3bdfSopenharmony_ci *   for forking the memory hungy child etc. And the reason to setting
25f08c3bdfSopenharmony_ci *   memsw.limit_in_bytes to twice of that is to give the system chance
26f08c3bdfSopenharmony_ci *   to try to free some memory before cgroup OOM kicks in and kills
27f08c3bdfSopenharmony_ci *   the memory hungry child.
28f08c3bdfSopenharmony_ci *
29f08c3bdfSopenharmony_ci * o Run a memory hungry child that allocates memory in loop until it's
30f08c3bdfSopenharmony_ci *   killed by cgroup OOM
31f08c3bdfSopenharmony_ci *
32f08c3bdfSopenharmony_ci * o Once the child is killed the MADV_FREE pages that were not written to
33f08c3bdfSopenharmony_ci *   should be freed, the test passes if there is at least one
34f08c3bdfSopenharmony_ci */
35f08c3bdfSopenharmony_ci
36f08c3bdfSopenharmony_ci#include <stdlib.h>
37f08c3bdfSopenharmony_ci#include <sys/wait.h>
38f08c3bdfSopenharmony_ci#include <fcntl.h>
39f08c3bdfSopenharmony_ci#include <unistd.h>
40f08c3bdfSopenharmony_ci#include <signal.h>
41f08c3bdfSopenharmony_ci#include <errno.h>
42f08c3bdfSopenharmony_ci#include <stdio.h>
43f08c3bdfSopenharmony_ci#include <ctype.h>
44f08c3bdfSopenharmony_ci
45f08c3bdfSopenharmony_ci#include "tst_test.h"
46f08c3bdfSopenharmony_ci#include "lapi/mmap.h"
47f08c3bdfSopenharmony_ci
48f08c3bdfSopenharmony_ci#define MEMCG_PATH "/sys/fs/cgroup/memory/"
49f08c3bdfSopenharmony_ci
50f08c3bdfSopenharmony_cistatic char cgroup_path[PATH_MAX];
51f08c3bdfSopenharmony_cistatic char tasks_path[PATH_MAX];
52f08c3bdfSopenharmony_cistatic char limit_in_bytes_path[PATH_MAX];
53f08c3bdfSopenharmony_cistatic char memsw_limit_in_bytes_path[PATH_MAX];
54f08c3bdfSopenharmony_ci
55f08c3bdfSopenharmony_cistatic size_t page_size;
56f08c3bdfSopenharmony_cistatic int sleep_between_faults;
57f08c3bdfSopenharmony_ci
58f08c3bdfSopenharmony_cistatic int swap_accounting_enabled;
59f08c3bdfSopenharmony_ci
60f08c3bdfSopenharmony_ci#define PAGES 128
61f08c3bdfSopenharmony_ci#define TOUCHED_PAGE1 0
62f08c3bdfSopenharmony_ci#define TOUCHED_PAGE2 10
63f08c3bdfSopenharmony_ci
64f08c3bdfSopenharmony_cistatic void memory_pressure_child(void)
65f08c3bdfSopenharmony_ci{
66f08c3bdfSopenharmony_ci	size_t i, page_size = getpagesize();
67f08c3bdfSopenharmony_ci	char *ptr;
68f08c3bdfSopenharmony_ci
69f08c3bdfSopenharmony_ci	for (;;) {
70f08c3bdfSopenharmony_ci		ptr = mmap(NULL, 500 * page_size, PROT_READ | PROT_WRITE,
71f08c3bdfSopenharmony_ci			   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
72f08c3bdfSopenharmony_ci
73f08c3bdfSopenharmony_ci		for (i = 0; i < 500; i++) {
74f08c3bdfSopenharmony_ci			ptr[i * page_size] = i % 100;
75f08c3bdfSopenharmony_ci			usleep(sleep_between_faults);
76f08c3bdfSopenharmony_ci		}
77f08c3bdfSopenharmony_ci
78f08c3bdfSopenharmony_ci		/* If swap accounting is disabled exit after process swapped out 100MB */
79f08c3bdfSopenharmony_ci		if (!swap_accounting_enabled) {
80f08c3bdfSopenharmony_ci			int swapped;
81f08c3bdfSopenharmony_ci
82f08c3bdfSopenharmony_ci			SAFE_FILE_LINES_SCANF("/proc/self/status", "VmSwap: %d", &swapped);
83f08c3bdfSopenharmony_ci
84f08c3bdfSopenharmony_ci			if (swapped > 100 * 1024)
85f08c3bdfSopenharmony_ci				exit(0);
86f08c3bdfSopenharmony_ci		}
87f08c3bdfSopenharmony_ci
88f08c3bdfSopenharmony_ci	}
89f08c3bdfSopenharmony_ci
90f08c3bdfSopenharmony_ci	abort();
91f08c3bdfSopenharmony_ci}
92f08c3bdfSopenharmony_ci
93f08c3bdfSopenharmony_cistatic void setup_cgroup_paths(int pid)
94f08c3bdfSopenharmony_ci{
95f08c3bdfSopenharmony_ci	snprintf(cgroup_path, sizeof(cgroup_path),
96f08c3bdfSopenharmony_ci		 MEMCG_PATH "ltp_madvise09_%i/", pid);
97f08c3bdfSopenharmony_ci	snprintf(tasks_path, sizeof(tasks_path), "%s/tasks", cgroup_path);
98f08c3bdfSopenharmony_ci	snprintf(limit_in_bytes_path, sizeof(limit_in_bytes_path),
99f08c3bdfSopenharmony_ci		 "%s/memory.limit_in_bytes", cgroup_path);
100f08c3bdfSopenharmony_ci	snprintf(memsw_limit_in_bytes_path, sizeof(memsw_limit_in_bytes_path),
101f08c3bdfSopenharmony_ci		 "%s/memory.memsw.limit_in_bytes", cgroup_path);
102f08c3bdfSopenharmony_ci}
103f08c3bdfSopenharmony_ci
104f08c3bdfSopenharmony_cistatic int count_freed(char *ptr)
105f08c3bdfSopenharmony_ci{
106f08c3bdfSopenharmony_ci	int i, ret = 0;
107f08c3bdfSopenharmony_ci
108f08c3bdfSopenharmony_ci	for (i = 0; i < PAGES; i++) {
109f08c3bdfSopenharmony_ci		if (!ptr[i * page_size])
110f08c3bdfSopenharmony_ci			ret++;
111f08c3bdfSopenharmony_ci	}
112f08c3bdfSopenharmony_ci
113f08c3bdfSopenharmony_ci	return ret;
114f08c3bdfSopenharmony_ci}
115f08c3bdfSopenharmony_ci
116f08c3bdfSopenharmony_cistatic int check_page_baaa(char *ptr)
117f08c3bdfSopenharmony_ci{
118f08c3bdfSopenharmony_ci	unsigned int i;
119f08c3bdfSopenharmony_ci
120f08c3bdfSopenharmony_ci	if (ptr[0] != 'b') {
121f08c3bdfSopenharmony_ci		tst_res(TINFO, "%p unexpected %c (%i) at 0 expected 'b'",
122f08c3bdfSopenharmony_ci			ptr, isprint(ptr[0]) ? ptr[0] : ' ', ptr[0]);
123f08c3bdfSopenharmony_ci		return 1;
124f08c3bdfSopenharmony_ci	}
125f08c3bdfSopenharmony_ci
126f08c3bdfSopenharmony_ci	for (i = 1; i < page_size; i++) {
127f08c3bdfSopenharmony_ci		if (ptr[i] != 'a') {
128f08c3bdfSopenharmony_ci			tst_res(TINFO,
129f08c3bdfSopenharmony_ci				"%p unexpected %c (%i) at %i expected 'a'",
130f08c3bdfSopenharmony_ci				ptr, isprint(ptr[i]) ? ptr[i] : ' ',
131f08c3bdfSopenharmony_ci				ptr[i], i);
132f08c3bdfSopenharmony_ci			return 1;
133f08c3bdfSopenharmony_ci		}
134f08c3bdfSopenharmony_ci	}
135f08c3bdfSopenharmony_ci
136f08c3bdfSopenharmony_ci	return 0;
137f08c3bdfSopenharmony_ci}
138f08c3bdfSopenharmony_ci
139f08c3bdfSopenharmony_cistatic int check_page(char *ptr, char val)
140f08c3bdfSopenharmony_ci{
141f08c3bdfSopenharmony_ci	unsigned int i;
142f08c3bdfSopenharmony_ci
143f08c3bdfSopenharmony_ci	for (i = 0; i < page_size; i++) {
144f08c3bdfSopenharmony_ci		if (ptr[i] != val) {
145f08c3bdfSopenharmony_ci			tst_res(TINFO,
146f08c3bdfSopenharmony_ci				"%p unexpected %c (%i) at %i expected %c (%i)",
147f08c3bdfSopenharmony_ci				ptr, isprint(ptr[i]) ? ptr[i] : ' ', ptr[i], i,
148f08c3bdfSopenharmony_ci				isprint(val) ? val : ' ', val);
149f08c3bdfSopenharmony_ci			return 1;
150f08c3bdfSopenharmony_ci		}
151f08c3bdfSopenharmony_ci	}
152f08c3bdfSopenharmony_ci
153f08c3bdfSopenharmony_ci	return 0;
154f08c3bdfSopenharmony_ci}
155f08c3bdfSopenharmony_ci
156f08c3bdfSopenharmony_cistatic void child(void)
157f08c3bdfSopenharmony_ci{
158f08c3bdfSopenharmony_ci	size_t i;
159f08c3bdfSopenharmony_ci	char *ptr;
160f08c3bdfSopenharmony_ci	unsigned int usage, old_limit, old_memsw_limit;
161f08c3bdfSopenharmony_ci	int status, pid, retries = 0;
162f08c3bdfSopenharmony_ci
163f08c3bdfSopenharmony_ci	SAFE_MKDIR(cgroup_path, 0777);
164f08c3bdfSopenharmony_ci	SAFE_FILE_PRINTF(tasks_path, "%i", getpid());
165f08c3bdfSopenharmony_ci
166f08c3bdfSopenharmony_ci	ptr = SAFE_MMAP(NULL, PAGES * page_size, PROT_READ | PROT_WRITE,
167f08c3bdfSopenharmony_ci	                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
168f08c3bdfSopenharmony_ci
169f08c3bdfSopenharmony_ci	for (i = 0; i < PAGES * page_size; i++)
170f08c3bdfSopenharmony_ci		ptr[i] = 'a';
171f08c3bdfSopenharmony_ci
172f08c3bdfSopenharmony_ci	if (madvise(ptr, PAGES * page_size, MADV_FREE)) {
173f08c3bdfSopenharmony_ci		if (errno == EINVAL)
174f08c3bdfSopenharmony_ci			tst_brk(TCONF | TERRNO, "MADV_FREE is not supported");
175f08c3bdfSopenharmony_ci
176f08c3bdfSopenharmony_ci		tst_brk(TBROK | TERRNO, "MADV_FREE failed");
177f08c3bdfSopenharmony_ci	}
178f08c3bdfSopenharmony_ci
179f08c3bdfSopenharmony_ci	if (ptr[page_size] != 'a')
180f08c3bdfSopenharmony_ci		tst_res(TFAIL, "MADV_FREE pages were freed immediately");
181f08c3bdfSopenharmony_ci	else
182f08c3bdfSopenharmony_ci		tst_res(TPASS, "MADV_FREE pages were not freed immediately");
183f08c3bdfSopenharmony_ci
184f08c3bdfSopenharmony_ci	ptr[TOUCHED_PAGE1 * page_size] = 'b';
185f08c3bdfSopenharmony_ci	ptr[TOUCHED_PAGE2 * page_size] = 'b';
186f08c3bdfSopenharmony_ci
187f08c3bdfSopenharmony_ci	usage = 8 * 1024 * 1024;
188f08c3bdfSopenharmony_ci	tst_res(TINFO, "Setting memory limits to %u %u", usage, 2 * usage);
189f08c3bdfSopenharmony_ci
190f08c3bdfSopenharmony_ci	SAFE_FILE_SCANF(limit_in_bytes_path, "%u", &old_limit);
191f08c3bdfSopenharmony_ci
192f08c3bdfSopenharmony_ci	if (swap_accounting_enabled)
193f08c3bdfSopenharmony_ci		SAFE_FILE_SCANF(memsw_limit_in_bytes_path, "%u", &old_memsw_limit);
194f08c3bdfSopenharmony_ci
195f08c3bdfSopenharmony_ci	SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", usage);
196f08c3bdfSopenharmony_ci
197f08c3bdfSopenharmony_ci	if (swap_accounting_enabled)
198f08c3bdfSopenharmony_ci		SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", 2 * usage);
199f08c3bdfSopenharmony_ci
200f08c3bdfSopenharmony_ci	do {
201f08c3bdfSopenharmony_ci		sleep_between_faults++;
202f08c3bdfSopenharmony_ci
203f08c3bdfSopenharmony_ci		pid = SAFE_FORK();
204f08c3bdfSopenharmony_ci		if (!pid)
205f08c3bdfSopenharmony_ci			memory_pressure_child();
206f08c3bdfSopenharmony_ci
207f08c3bdfSopenharmony_ci		tst_res(TINFO, "Memory hungry child %i started, try %i", pid, retries);
208f08c3bdfSopenharmony_ci
209f08c3bdfSopenharmony_ci		SAFE_WAIT(&status);
210f08c3bdfSopenharmony_ci	} while (retries++ < 10 && count_freed(ptr) == 0);
211f08c3bdfSopenharmony_ci
212f08c3bdfSopenharmony_ci	char map[PAGES+1];
213f08c3bdfSopenharmony_ci	unsigned int freed = 0;
214f08c3bdfSopenharmony_ci	unsigned int corrupted = 0;
215f08c3bdfSopenharmony_ci
216f08c3bdfSopenharmony_ci	for (i = 0; i < PAGES; i++) {
217f08c3bdfSopenharmony_ci		char exp_val;
218f08c3bdfSopenharmony_ci
219f08c3bdfSopenharmony_ci		if (ptr[i * page_size]) {
220f08c3bdfSopenharmony_ci			exp_val = 'a';
221f08c3bdfSopenharmony_ci			map[i] = 'p';
222f08c3bdfSopenharmony_ci		} else {
223f08c3bdfSopenharmony_ci			exp_val = 0;
224f08c3bdfSopenharmony_ci			map[i] = '_';
225f08c3bdfSopenharmony_ci			freed++;
226f08c3bdfSopenharmony_ci		}
227f08c3bdfSopenharmony_ci
228f08c3bdfSopenharmony_ci		if (i != TOUCHED_PAGE1 && i != TOUCHED_PAGE2) {
229f08c3bdfSopenharmony_ci			if (check_page(ptr + i * page_size, exp_val)) {
230f08c3bdfSopenharmony_ci				map[i] = '?';
231f08c3bdfSopenharmony_ci				corrupted++;
232f08c3bdfSopenharmony_ci			}
233f08c3bdfSopenharmony_ci		} else {
234f08c3bdfSopenharmony_ci			if (check_page_baaa(ptr + i * page_size)) {
235f08c3bdfSopenharmony_ci				map[i] = '?';
236f08c3bdfSopenharmony_ci				corrupted++;
237f08c3bdfSopenharmony_ci			}
238f08c3bdfSopenharmony_ci		}
239f08c3bdfSopenharmony_ci	}
240f08c3bdfSopenharmony_ci	map[PAGES] = '\0';
241f08c3bdfSopenharmony_ci
242f08c3bdfSopenharmony_ci	tst_res(TINFO, "Memory map: %s", map);
243f08c3bdfSopenharmony_ci
244f08c3bdfSopenharmony_ci	if (freed)
245f08c3bdfSopenharmony_ci		tst_res(TPASS, "Pages MADV_FREE were freed on low memory");
246f08c3bdfSopenharmony_ci	else
247f08c3bdfSopenharmony_ci		tst_res(TFAIL, "No MADV_FREE page was freed on low memory");
248f08c3bdfSopenharmony_ci
249f08c3bdfSopenharmony_ci	if (corrupted)
250f08c3bdfSopenharmony_ci		tst_res(TFAIL, "Found corrupted page");
251f08c3bdfSopenharmony_ci	else
252f08c3bdfSopenharmony_ci		tst_res(TPASS, "All pages have expected content");
253f08c3bdfSopenharmony_ci
254f08c3bdfSopenharmony_ci	if (swap_accounting_enabled)
255f08c3bdfSopenharmony_ci		SAFE_FILE_PRINTF(memsw_limit_in_bytes_path, "%u", old_memsw_limit);
256f08c3bdfSopenharmony_ci
257f08c3bdfSopenharmony_ci	SAFE_FILE_PRINTF(limit_in_bytes_path, "%u", old_limit);
258f08c3bdfSopenharmony_ci
259f08c3bdfSopenharmony_ci	SAFE_MUNMAP(ptr, PAGES);
260f08c3bdfSopenharmony_ci
261f08c3bdfSopenharmony_ci	exit(0);
262f08c3bdfSopenharmony_ci}
263f08c3bdfSopenharmony_ci
264f08c3bdfSopenharmony_cistatic void cleanup(void)
265f08c3bdfSopenharmony_ci{
266f08c3bdfSopenharmony_ci	if (cgroup_path[0] && !access(cgroup_path, F_OK))
267f08c3bdfSopenharmony_ci		rmdir(cgroup_path);
268f08c3bdfSopenharmony_ci}
269f08c3bdfSopenharmony_ci
270f08c3bdfSopenharmony_cistatic void run(void)
271f08c3bdfSopenharmony_ci{
272f08c3bdfSopenharmony_ci	pid_t pid;
273f08c3bdfSopenharmony_ci	int status;
274f08c3bdfSopenharmony_ci
275f08c3bdfSopenharmony_ciretry:
276f08c3bdfSopenharmony_ci	pid = SAFE_FORK();
277f08c3bdfSopenharmony_ci
278f08c3bdfSopenharmony_ci	if (!pid) {
279f08c3bdfSopenharmony_ci		setup_cgroup_paths(getpid());
280f08c3bdfSopenharmony_ci		child();
281f08c3bdfSopenharmony_ci	}
282f08c3bdfSopenharmony_ci
283f08c3bdfSopenharmony_ci	setup_cgroup_paths(pid);
284f08c3bdfSopenharmony_ci	SAFE_WAIT(&status);
285f08c3bdfSopenharmony_ci	cleanup();
286f08c3bdfSopenharmony_ci
287f08c3bdfSopenharmony_ci	/*
288f08c3bdfSopenharmony_ci	 * Rarely cgroup OOM kills both children not only the one that allocates
289f08c3bdfSopenharmony_ci	 * memory in loop, hence we retry here if that happens.
290f08c3bdfSopenharmony_ci	 */
291f08c3bdfSopenharmony_ci	if (WIFSIGNALED(status)) {
292f08c3bdfSopenharmony_ci		tst_res(TINFO, "Both children killed, retrying...");
293f08c3bdfSopenharmony_ci		goto retry;
294f08c3bdfSopenharmony_ci	}
295f08c3bdfSopenharmony_ci
296f08c3bdfSopenharmony_ci	if (WIFEXITED(status) && WEXITSTATUS(status) == TCONF)
297f08c3bdfSopenharmony_ci		tst_brk(TCONF, "MADV_FREE is not supported");
298f08c3bdfSopenharmony_ci
299f08c3bdfSopenharmony_ci	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
300f08c3bdfSopenharmony_ci		tst_brk(TBROK, "Child %s", tst_strstatus(status));
301f08c3bdfSopenharmony_ci}
302f08c3bdfSopenharmony_ci
303f08c3bdfSopenharmony_cistatic void setup(void)
304f08c3bdfSopenharmony_ci{
305f08c3bdfSopenharmony_ci	long int swap_total;
306f08c3bdfSopenharmony_ci
307f08c3bdfSopenharmony_ci	if (access(MEMCG_PATH, F_OK)) {
308f08c3bdfSopenharmony_ci		tst_brk(TCONF, "'" MEMCG_PATH
309f08c3bdfSopenharmony_ci			"' not present, CONFIG_MEMCG missing?");
310f08c3bdfSopenharmony_ci	}
311f08c3bdfSopenharmony_ci
312f08c3bdfSopenharmony_ci	if (!access(MEMCG_PATH "memory.memsw.limit_in_bytes", F_OK))
313f08c3bdfSopenharmony_ci		swap_accounting_enabled = 1;
314f08c3bdfSopenharmony_ci	else
315f08c3bdfSopenharmony_ci		tst_res(TINFO, "Swap accounting is disabled");
316f08c3bdfSopenharmony_ci
317f08c3bdfSopenharmony_ci	SAFE_FILE_LINES_SCANF("/proc/meminfo", "SwapTotal: %ld", &swap_total);
318f08c3bdfSopenharmony_ci	if (swap_total <= 0)
319f08c3bdfSopenharmony_ci		tst_brk(TCONF, "MADV_FREE does not work without swap");
320f08c3bdfSopenharmony_ci
321f08c3bdfSopenharmony_ci	page_size = getpagesize();
322f08c3bdfSopenharmony_ci}
323f08c3bdfSopenharmony_ci
324f08c3bdfSopenharmony_cistatic struct tst_test test = {
325f08c3bdfSopenharmony_ci	.setup = setup,
326f08c3bdfSopenharmony_ci	.cleanup = cleanup,
327f08c3bdfSopenharmony_ci	.test_all = run,
328f08c3bdfSopenharmony_ci	.needs_root = 1,
329f08c3bdfSopenharmony_ci	.forks_child = 1,
330f08c3bdfSopenharmony_ci};
331