Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2#include <stdio.h>
3#include <string.h>
4#include <stdbool.h>
5#include <fcntl.h>
6#include <stdint.h>
7#include <malloc.h>
8#include <sys/mman.h>
9
10#include "kselftest.h"
11#include "vm_util.h"
12#include "thp_settings.h"
13
14#define PAGEMAP_FILE_PATH "/proc/self/pagemap"
15#define TEST_ITERATIONS 10000
16
17static void test_simple(int pagemap_fd, int pagesize)
18{
19 int i;
20 char *map;
21
22 map = aligned_alloc(pagesize, pagesize);
23 if (!map)
24 ksft_exit_fail_msg("mmap failed\n");
25
26 clear_softdirty();
27
28 for (i = 0 ; i < TEST_ITERATIONS; i++) {
29 if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
30 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
31 break;
32 }
33
34 clear_softdirty();
35 // Write something to the page to get the dirty bit enabled on the page
36 map[0]++;
37
38 if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
39 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
40 break;
41 }
42
43 clear_softdirty();
44 }
45 free(map);
46
47 ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
48}
49
50static void test_vma_reuse(int pagemap_fd, int pagesize)
51{
52 char *map, *map2;
53
54 map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
55 if (map == MAP_FAILED)
56 ksft_exit_fail_msg("mmap failed");
57
58 // The kernel always marks new regions as soft dirty
59 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
60 "Test %s dirty bit of allocated page\n", __func__);
61
62 clear_softdirty();
63 munmap(map, pagesize);
64
65 map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
66 if (map2 == MAP_FAILED)
67 ksft_exit_fail_msg("mmap failed");
68
69 // Dirty bit is set for new regions even if they are reused
70 if (map == map2)
71 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
72 "Test %s dirty bit of reused address page\n", __func__);
73 else
74 ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
75
76 munmap(map2, pagesize);
77}
78
79static void test_hugepage(int pagemap_fd, int pagesize)
80{
81 char *map;
82 int i, ret;
83
84 if (!thp_is_enabled()) {
85 ksft_print_msg("Transparent Hugepages not available\n");
86 ksft_test_result_skip("Test %s huge page allocation\n", __func__);
87 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
88 return;
89 }
90
91 size_t hpage_len = read_pmd_pagesize();
92 if (!hpage_len)
93 ksft_exit_fail_msg("Reading PMD pagesize failed");
94
95 map = memalign(hpage_len, hpage_len);
96 if (!map)
97 ksft_exit_fail_msg("memalign failed\n");
98
99 ret = madvise(map, hpage_len, MADV_HUGEPAGE);
100 if (ret)
101 ksft_exit_fail_msg("madvise failed %d\n", ret);
102
103 for (i = 0; i < hpage_len; i++)
104 map[i] = (char)i;
105
106 if (check_huge_anon(map, 1, hpage_len)) {
107 ksft_test_result_pass("Test %s huge page allocation\n", __func__);
108
109 clear_softdirty();
110 for (i = 0 ; i < TEST_ITERATIONS ; i++) {
111 if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
112 ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
113 break;
114 }
115
116 clear_softdirty();
117 // Write something to the page to get the dirty bit enabled on the page
118 map[0]++;
119
120 if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
121 ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
122 break;
123 }
124 clear_softdirty();
125 }
126
127 ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
128 } else {
129 // hugepage allocation failed. skip these tests
130 ksft_test_result_skip("Test %s huge page allocation\n", __func__);
131 ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
132 }
133 free(map);
134}
135
136static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
137{
138 const char *type[] = {"file", "anon"};
139 const char *fname = "./soft-dirty-test-file";
140 int test_fd = 0;
141 char *map;
142
143 if (anon) {
144 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
145 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
146 if (!map)
147 ksft_exit_fail_msg("anon mmap failed\n");
148 } else {
149 test_fd = open(fname, O_RDWR | O_CREAT, 0664);
150 if (test_fd < 0) {
151 ksft_test_result_skip("Test %s open() file failed\n", __func__);
152 return;
153 }
154 unlink(fname);
155 ftruncate(test_fd, pagesize);
156 map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
157 MAP_SHARED, test_fd, 0);
158 if (!map)
159 ksft_exit_fail_msg("file mmap failed\n");
160 }
161
162 *map = 1;
163 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
164 "Test %s-%s dirty bit of new written page\n",
165 __func__, type[anon]);
166 clear_softdirty();
167 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
168 "Test %s-%s soft-dirty clear after clear_refs\n",
169 __func__, type[anon]);
170 mprotect(map, pagesize, PROT_READ);
171 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
172 "Test %s-%s soft-dirty clear after marking RO\n",
173 __func__, type[anon]);
174 mprotect(map, pagesize, PROT_READ|PROT_WRITE);
175 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
176 "Test %s-%s soft-dirty clear after marking RW\n",
177 __func__, type[anon]);
178 *map = 2;
179 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
180 "Test %s-%s soft-dirty after rewritten\n",
181 __func__, type[anon]);
182
183 munmap(map, pagesize);
184
185 if (!anon)
186 close(test_fd);
187}
188
189static void test_merge(int pagemap_fd, int pagesize)
190{
191 char *reserved, *map, *map2;
192
193 /*
194 * Reserve space for tests:
195 *
196 * ---padding to ---
197 * | avoid adj. |
198 * v merge v
199 * |---|---|---|---|---|
200 * | | 1 | 2 | 3 | |
201 * |---|---|---|---|---|
202 */
203 reserved = mmap(NULL, 5 * pagesize, PROT_NONE,
204 MAP_ANON | MAP_PRIVATE, -1, 0);
205 if (reserved == MAP_FAILED)
206 ksft_exit_fail_msg("mmap failed\n");
207 munmap(reserved, 4 * pagesize);
208
209 /*
210 * Establish initial VMA:
211 *
212 * S/D
213 * |---|---|---|---|---|
214 * | | 1 | | | |
215 * |---|---|---|---|---|
216 */
217 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
218 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
219 if (map == MAP_FAILED)
220 ksft_exit_fail_msg("mmap failed\n");
221
222 /* This will clear VM_SOFTDIRTY too. */
223 clear_softdirty();
224
225 /*
226 * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
227 * map:
228 *
229 * - S/D
230 * |---|---|---|---|---|
231 * | | 1 | | 2 | |
232 * |---|---|---|---|---|
233 */
234 map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE,
235 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
236 if (map2 == MAP_FAILED)
237 ksft_exit_fail_msg("mmap failed\n");
238
239 /*
240 * Now remap it immediately adjacent to map, if the merge correctly
241 * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole
242 * being marked soft-dirty:
243 *
244 * merge
245 * S/D
246 * |---|-------|---|---|
247 * | | 1 | | |
248 * |---|-------|---|---|
249 */
250 map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE,
251 &reserved[2 * pagesize]);
252 if (map2 == MAP_FAILED)
253 ksft_exit_fail_msg("mremap failed\n");
254 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
255 "Test %s-anon soft-dirty after remap merge 1st pg\n",
256 __func__);
257 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
258 "Test %s-anon soft-dirty after remap merge 2nd pg\n",
259 __func__);
260
261 munmap(map, 2 * pagesize);
262
263 /*
264 * Now establish another VMA:
265 *
266 * S/D
267 * |---|---|---|---|---|
268 * | | 1 | | | |
269 * |---|---|---|---|---|
270 */
271 map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
272 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
273 if (map == MAP_FAILED)
274 ksft_exit_fail_msg("mmap failed\n");
275
276 /* Clear VM_SOFTDIRTY... */
277 clear_softdirty();
278 /* ...and establish incompatible adjacent VMA:
279 *
280 * - S/D
281 * |---|---|---|---|---|
282 * | | 1 | 2 | | |
283 * |---|---|---|---|---|
284 */
285 map2 = mmap(&reserved[2 * pagesize], pagesize,
286 PROT_READ | PROT_WRITE | PROT_EXEC,
287 MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
288 if (map2 == MAP_FAILED)
289 ksft_exit_fail_msg("mmap failed\n");
290
291 /*
292 * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges:
293 *
294 * merge
295 * S/D
296 * |---|-------|---|---|
297 * | | 1 | | |
298 * |---|-------|---|---|
299 */
300 if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC))
301 ksft_exit_fail_msg("mprotect failed\n");
302
303 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
304 "Test %s-anon soft-dirty after mprotect merge 1st pg\n",
305 __func__);
306 ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
307 "Test %s-anon soft-dirty after mprotect merge 2nd pg\n",
308 __func__);
309
310 munmap(map, 2 * pagesize);
311}
312
313static void test_mprotect_anon(int pagemap_fd, int pagesize)
314{
315 test_mprotect(pagemap_fd, pagesize, true);
316}
317
318static void test_mprotect_file(int pagemap_fd, int pagesize)
319{
320 test_mprotect(pagemap_fd, pagesize, false);
321}
322
323int main(int argc, char **argv)
324{
325 int pagemap_fd;
326 int pagesize;
327
328 ksft_print_header();
329
330 if (!softdirty_supported())
331 ksft_exit_skip("soft-dirty is not support\n");
332
333 ksft_set_plan(19);
334 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
335 if (pagemap_fd < 0)
336 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
337
338 pagesize = getpagesize();
339
340 test_simple(pagemap_fd, pagesize);
341 test_vma_reuse(pagemap_fd, pagesize);
342 test_hugepage(pagemap_fd, pagesize);
343 test_mprotect_anon(pagemap_fd, pagesize);
344 test_mprotect_file(pagemap_fd, pagesize);
345 test_merge(pagemap_fd, pagesize);
346
347 close(pagemap_fd);
348
349 ksft_finished();
350}