xref: /linux/tools/testing/selftests/mm/soft-dirty.c (revision 32449ec2cc57bd5dc827d4e94ad9f0456e0db935)
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 
13 #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
14 #define TEST_ITERATIONS 10000
15 
16 static void test_simple(int pagemap_fd, int pagesize)
17 {
18 	int i;
19 	char *map;
20 
21 	map = aligned_alloc(pagesize, pagesize);
22 	if (!map)
23 		ksft_exit_fail_msg("mmap failed\n");
24 
25 	clear_softdirty();
26 
27 	for (i = 0 ; i < TEST_ITERATIONS; i++) {
28 		if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
29 			ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
30 			break;
31 		}
32 
33 		clear_softdirty();
34 		// Write something to the page to get the dirty bit enabled on the page
35 		map[0]++;
36 
37 		if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
38 			ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
39 			break;
40 		}
41 
42 		clear_softdirty();
43 	}
44 	free(map);
45 
46 	ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
47 }
48 
49 static void test_vma_reuse(int pagemap_fd, int pagesize)
50 {
51 	char *map, *map2;
52 
53 	map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
54 	if (map == MAP_FAILED)
55 		ksft_exit_fail_msg("mmap failed");
56 
57 	// The kernel always marks new regions as soft dirty
58 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
59 			 "Test %s dirty bit of allocated page\n", __func__);
60 
61 	clear_softdirty();
62 	munmap(map, pagesize);
63 
64 	map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
65 	if (map2 == MAP_FAILED)
66 		ksft_exit_fail_msg("mmap failed");
67 
68 	// Dirty bit is set for new regions even if they are reused
69 	if (map == map2)
70 		ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
71 				 "Test %s dirty bit of reused address page\n", __func__);
72 	else
73 		ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
74 
75 	munmap(map2, pagesize);
76 }
77 
78 static void test_hugepage(int pagemap_fd, int pagesize)
79 {
80 	char *map;
81 	int i, ret;
82 
83 	if (!thp_is_enabled()) {
84 		ksft_print_msg("Transparent Hugepages not available\n");
85 		ksft_test_result_skip("Test %s huge page allocation\n", __func__);
86 		ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
87 		return;
88 	}
89 
90 	size_t hpage_len = read_pmd_pagesize();
91 	if (!hpage_len)
92 		ksft_exit_fail_msg("Reading PMD pagesize failed");
93 
94 	map = memalign(hpage_len, hpage_len);
95 	if (!map)
96 		ksft_exit_fail_msg("memalign failed\n");
97 
98 	ret = madvise(map, hpage_len, MADV_HUGEPAGE);
99 	if (ret)
100 		ksft_exit_fail_msg("madvise failed %d\n", ret);
101 
102 	for (i = 0; i < hpage_len; i++)
103 		map[i] = (char)i;
104 
105 	if (check_huge_anon(map, hpage_len, 1, hpage_len)) {
106 		ksft_test_result_pass("Test %s huge page allocation\n", __func__);
107 
108 		clear_softdirty();
109 		for (i = 0 ; i < TEST_ITERATIONS ; i++) {
110 			if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
111 				ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
112 				break;
113 			}
114 
115 			clear_softdirty();
116 			// Write something to the page to get the dirty bit enabled on the page
117 			map[0]++;
118 
119 			if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
120 				ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
121 				break;
122 			}
123 			clear_softdirty();
124 		}
125 
126 		ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
127 	} else {
128 		// hugepage allocation failed. skip these tests
129 		ksft_test_result_skip("Test %s huge page allocation\n", __func__);
130 		ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
131 	}
132 	free(map);
133 }
134 
135 static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
136 {
137 	const char *type[] = {"file", "anon"};
138 	const char *fname = "./soft-dirty-test-file";
139 	int test_fd = 0;
140 	char *map;
141 
142 	if (anon) {
143 		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
144 			   MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
145 		if (map == MAP_FAILED)
146 			ksft_exit_fail_msg("anon mmap failed\n");
147 	} else {
148 		test_fd = open(fname, O_RDWR | O_CREAT, 0664);
149 		if (test_fd < 0) {
150 			ksft_test_result_skip("Test %s open() file failed\n", __func__);
151 			return;
152 		}
153 		unlink(fname);
154 		if (ftruncate(test_fd, pagesize) != 0)
155 			ksft_exit_fail_msg("ftruncate failed\n");
156 		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
157 			   MAP_SHARED, test_fd, 0);
158 		if (map == MAP_FAILED)
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 
189 static 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 
313 static void test_mprotect_anon(int pagemap_fd, int pagesize)
314 {
315 	test_mprotect(pagemap_fd, pagesize, true);
316 }
317 
318 static void test_mprotect_file(int pagemap_fd, int pagesize)
319 {
320 	test_mprotect(pagemap_fd, pagesize, false);
321 }
322 
323 int 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 }
351