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