• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2023 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
4  */
5 
6 /*\
7  * [Description]
8  *
9  * Allocate anonymous memory pages inside child and reclaim it with
10  * MADV_PAGEOUT. Then check if memory pages have been swapped out by looking
11  * at smaps information.
12  *
13  * The advice might be ignored for some pages in the range when it is
14  * not applicable, so test passes if swap memory increases after
15  * reclaiming memory with MADV_PAGEOUT.
16  */
17 
18 #define _GNU_SOURCE
19 
20 #include <sys/mman.h>
21 #include "tst_test.h"
22 #include "lapi/mmap.h"
23 #include "lapi/syscalls.h"
24 #include "process_madvise.h"
25 
26 #define MEM_LIMIT   (100 * TST_MB)
27 #define MEMSW_LIMIT (200 * TST_MB)
28 #define MEM_CHILD   (1   * TST_MB)
29 
30 static void **data_ptr;
31 
child_alloc(void)32 static void child_alloc(void)
33 {
34 	char data[MEM_CHILD];
35 	struct addr_mapping map_before;
36 	struct addr_mapping map_after;
37 
38 	memset(data, 'a', MEM_CHILD);
39 
40 	tst_res(TINFO, "Allocate memory: %d bytes", MEM_CHILD);
41 
42 	*data_ptr = SAFE_MMAP(NULL, MEM_CHILD,
43 			PROT_READ | PROT_WRITE,
44 			MAP_SHARED | MAP_ANONYMOUS, -1, 0);
45 
46 	memset(*data_ptr, 'a', MEM_CHILD);
47 
48 	memset(&map_before, 0, sizeof(struct addr_mapping));
49 	read_address_mapping((unsigned long)*data_ptr, &map_before);
50 
51 	TST_CHECKPOINT_WAKE_AND_WAIT(0);
52 
53 	memset(&map_after, 0, sizeof(struct addr_mapping));
54 	read_address_mapping((unsigned long)*data_ptr, &map_after);
55 
56 	if (memcmp(*data_ptr, data, MEM_CHILD) != 0) {
57 		tst_res(TFAIL, "Dirty memory after reclaiming it");
58 		return;
59 	}
60 
61 	SAFE_MUNMAP(*data_ptr, MEM_CHILD);
62 	*data_ptr = NULL;
63 
64 	TST_EXP_EXPR(map_before.swap < map_after.swap,
65 		"Most of the memory has been swapped out: %dkB out of %dkB",
66 		map_after.swap - map_before.swap,
67 		MEM_CHILD / TST_KB);
68 }
69 
setup(void)70 static void setup(void)
71 {
72 	SAFE_CG_PRINTF(tst_cg, "memory.max", "%d", MEM_LIMIT);
73 	if (SAFE_CG_HAS(tst_cg, "memory.swap.max"))
74 		SAFE_CG_PRINTF(tst_cg, "memory.swap.max", "%d", MEMSW_LIMIT);
75 
76 	SAFE_CG_PRINTF(tst_cg, "cgroup.procs", "%d", getpid());
77 
78 	data_ptr = SAFE_MMAP(NULL, sizeof(void *),
79 			PROT_READ | PROT_WRITE,
80 			MAP_SHARED | MAP_ANONYMOUS, -1, 0);
81 }
82 
cleanup(void)83 static void cleanup(void)
84 {
85 	if (*data_ptr)
86 		SAFE_MUNMAP(*data_ptr, MEM_CHILD);
87 
88 	if (data_ptr)
89 		SAFE_MUNMAP(data_ptr, sizeof(void *));
90 }
91 
run(void)92 static void run(void)
93 {
94 	int ret;
95 	int pidfd;
96 	pid_t pid_alloc;
97 	struct iovec vec;
98 
99 	pid_alloc = SAFE_FORK();
100 	if (!pid_alloc) {
101 		child_alloc();
102 		return;
103 	}
104 
105 	TST_CHECKPOINT_WAIT(0);
106 
107 	tst_res(TINFO, "Reclaim memory using MADV_PAGEOUT");
108 
109 	pidfd = SAFE_PIDFD_OPEN(pid_alloc, 0);
110 
111 	vec.iov_base = *data_ptr;
112 	vec.iov_len = MEM_CHILD;
113 
114 	ret = tst_syscall(__NR_process_madvise, pidfd, &vec, 1UL,
115 			MADV_PAGEOUT, 0UL);
116 
117 	if (ret == -1)
118 		tst_brk(TBROK | TERRNO, "process_madvise failed");
119 
120 	if (ret != MEM_CHILD)
121 		tst_brk(TBROK, "process_madvise reclaimed only %d bytes", ret);
122 
123 	TST_CHECKPOINT_WAKE(0);
124 }
125 
126 static struct tst_test test = {
127 	.setup = setup,
128 	.cleanup = cleanup,
129 	.test_all = run,
130 	.forks_child = 1,
131 	.min_kver = "5.10",
132 	.needs_checkpoints = 1,
133 	.needs_root = 1,
134 	.min_mem_avail = 2 * MEM_LIMIT / TST_MB,
135 	.min_swap_avail = 2 * MEM_CHILD / TST_MB,
136 	.needs_cgroup_ctrls = (const char *const []){ "memory", NULL },
137 	.needs_kconfigs = (const char *[]) {
138 		"CONFIG_SWAP=y",
139 		NULL
140 	},
141 };
142