xref: /linux/tools/testing/selftests/mm/soft-dirty.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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 "hugepage_settings.h"
13 
14 #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
15 #define TEST_ITERATIONS 10000
16 
17 static 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 
50 static 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 
79 static 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, hpage_len, 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 
136 static 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 == MAP_FAILED)
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 		if (ftruncate(test_fd, pagesize) != 0)
156 			ksft_exit_fail_msg("ftruncate failed\n");
157 		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
158 			   MAP_SHARED, test_fd, 0);
159 		if (map == MAP_FAILED)
160 			ksft_exit_fail_msg("file mmap failed\n");
161 	}
162 
163 	*map = 1;
164 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
165 			 "Test %s-%s dirty bit of new written page\n",
166 			 __func__, type[anon]);
167 	clear_softdirty();
168 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
169 			 "Test %s-%s soft-dirty clear after clear_refs\n",
170 			 __func__, type[anon]);
171 	mprotect(map, pagesize, PROT_READ);
172 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
173 			 "Test %s-%s soft-dirty clear after marking RO\n",
174 			 __func__, type[anon]);
175 	mprotect(map, pagesize, PROT_READ|PROT_WRITE);
176 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
177 			 "Test %s-%s soft-dirty clear after marking RW\n",
178 			 __func__, type[anon]);
179 	*map = 2;
180 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
181 			 "Test %s-%s soft-dirty after rewritten\n",
182 			 __func__, type[anon]);
183 
184 	munmap(map, pagesize);
185 
186 	if (!anon)
187 		close(test_fd);
188 }
189 
190 static void test_merge(int pagemap_fd, int pagesize)
191 {
192 	char *reserved, *map, *map2;
193 
194 	/*
195 	 * Reserve space for tests:
196 	 *
197 	 *   ---padding to ---
198 	 *   |   avoid adj.  |
199 	 *   v     merge     v
200 	 * |---|---|---|---|---|
201 	 * |   | 1 | 2 | 3 |   |
202 	 * |---|---|---|---|---|
203 	 */
204 	reserved = mmap(NULL, 5 * pagesize, PROT_NONE,
205 			MAP_ANON | MAP_PRIVATE, -1, 0);
206 	if (reserved == MAP_FAILED)
207 		ksft_exit_fail_msg("mmap failed\n");
208 	munmap(reserved, 4 * pagesize);
209 
210 	/*
211 	 * Establish initial VMA:
212 	 *
213 	 *      S/D
214 	 * |---|---|---|---|---|
215 	 * |   | 1 |   |   |   |
216 	 * |---|---|---|---|---|
217 	 */
218 	map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
219 		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
220 	if (map == MAP_FAILED)
221 		ksft_exit_fail_msg("mmap failed\n");
222 
223 	/* This will clear VM_SOFTDIRTY too. */
224 	clear_softdirty();
225 
226 	/*
227 	 * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
228 	 * map:
229 	 *
230 	 *       -      S/D
231 	 * |---|---|---|---|---|
232 	 * |   | 1 |   | 2 |   |
233 	 * |---|---|---|---|---|
234 	 */
235 	map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE,
236 		    MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
237 	if (map2 == MAP_FAILED)
238 		ksft_exit_fail_msg("mmap failed\n");
239 
240 	/*
241 	 * Now remap it immediately adjacent to map, if the merge correctly
242 	 * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole
243 	 * being marked soft-dirty:
244 	 *
245 	 *       merge
246 	 *        S/D
247 	 * |---|-------|---|---|
248 	 * |   |   1   |   |   |
249 	 * |---|-------|---|---|
250 	 */
251 	map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE,
252 		      &reserved[2 * pagesize]);
253 	if (map2 == MAP_FAILED)
254 		ksft_exit_fail_msg("mremap failed\n");
255 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
256 			 "Test %s-anon soft-dirty after remap merge 1st pg\n",
257 			 __func__);
258 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
259 			 "Test %s-anon soft-dirty after remap merge 2nd pg\n",
260 			 __func__);
261 
262 	munmap(map, 2 * pagesize);
263 
264 	/*
265 	 * Now establish another VMA:
266 	 *
267 	 *      S/D
268 	 * |---|---|---|---|---|
269 	 * |   | 1 |   |   |   |
270 	 * |---|---|---|---|---|
271 	 */
272 	map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
273 		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
274 	if (map == MAP_FAILED)
275 		ksft_exit_fail_msg("mmap failed\n");
276 
277 	/* Clear VM_SOFTDIRTY... */
278 	clear_softdirty();
279 	/* ...and establish incompatible adjacent VMA:
280 	 *
281 	 *       -  S/D
282 	 * |---|---|---|---|---|
283 	 * |   | 1 | 2 |   |   |
284 	 * |---|---|---|---|---|
285 	 */
286 	map2 = mmap(&reserved[2 * pagesize], pagesize,
287 	PROT_READ | PROT_WRITE | PROT_EXEC,
288 		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
289 	if (map2 == MAP_FAILED)
290 		ksft_exit_fail_msg("mmap failed\n");
291 
292 	/*
293 	 * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges:
294 	 *
295 	 *       merge
296 	 *        S/D
297 	 * |---|-------|---|---|
298 	 * |   |   1   |   |   |
299 	 * |---|-------|---|---|
300 	 */
301 	if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC))
302 		ksft_exit_fail_msg("mprotect failed\n");
303 
304 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
305 			 "Test %s-anon soft-dirty after mprotect merge 1st pg\n",
306 			 __func__);
307 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
308 			 "Test %s-anon soft-dirty after mprotect merge 2nd pg\n",
309 			 __func__);
310 
311 	munmap(map, 2 * pagesize);
312 }
313 
314 static void test_mprotect_anon(int pagemap_fd, int pagesize)
315 {
316 	test_mprotect(pagemap_fd, pagesize, true);
317 }
318 
319 static void test_mprotect_file(int pagemap_fd, int pagesize)
320 {
321 	test_mprotect(pagemap_fd, pagesize, false);
322 }
323 
324 int main(int argc, char **argv)
325 {
326 	int pagemap_fd;
327 	int pagesize;
328 
329 	ksft_print_header();
330 
331 	if (!softdirty_supported())
332 		ksft_exit_skip("soft-dirty is not support\n");
333 
334 	ksft_set_plan(19);
335 	pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
336 	if (pagemap_fd < 0)
337 		ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
338 
339 	pagesize = getpagesize();
340 
341 	test_simple(pagemap_fd, pagesize);
342 	test_vma_reuse(pagemap_fd, pagesize);
343 	test_hugepage(pagemap_fd, pagesize);
344 	test_mprotect_anon(pagemap_fd, pagesize);
345 	test_mprotect_file(pagemap_fd, pagesize);
346 	test_merge(pagemap_fd, pagesize);
347 
348 	close(pagemap_fd);
349 
350 	ksft_finished();
351 }
352