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 | #include "../kselftest.h" |
10 | #include "vm_util.h" |
11 | |
12 | #define PAGEMAP_FILE_PATH "/proc/self/pagemap" |
13 | #define TEST_ITERATIONS 10000 |
14 | |
15 | static void test_simple(int pagemap_fd, int pagesize) |
16 | { |
17 | int i; |
18 | char *map; |
19 | |
20 | map = aligned_alloc(pagesize, pagesize); |
21 | if (!map) |
22 | ksft_exit_fail_msg(msg: "mmap failed\n" ); |
23 | |
24 | clear_softdirty(); |
25 | |
26 | for (i = 0 ; i < TEST_ITERATIONS; i++) { |
27 | if (pagemap_is_softdirty(pagemap_fd, map) == 1) { |
28 | ksft_print_msg(msg: "dirty bit was 1, but should be 0 (i=%d)\n" , i); |
29 | break; |
30 | } |
31 | |
32 | clear_softdirty(); |
33 | // Write something to the page to get the dirty bit enabled on the page |
34 | map[0]++; |
35 | |
36 | if (pagemap_is_softdirty(pagemap_fd, map) == 0) { |
37 | ksft_print_msg(msg: "dirty bit was 0, but should be 1 (i=%d)\n" , i); |
38 | break; |
39 | } |
40 | |
41 | clear_softdirty(); |
42 | } |
43 | free(map); |
44 | |
45 | ksft_test_result(i == TEST_ITERATIONS, "Test %s\n" , __func__); |
46 | } |
47 | |
48 | static void test_vma_reuse(int pagemap_fd, int pagesize) |
49 | { |
50 | char *map, *map2; |
51 | |
52 | map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); |
53 | if (map == MAP_FAILED) |
54 | ksft_exit_fail_msg(msg: "mmap failed" ); |
55 | |
56 | // The kernel always marks new regions as soft dirty |
57 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, |
58 | "Test %s dirty bit of allocated page\n" , __func__); |
59 | |
60 | clear_softdirty(); |
61 | munmap(map, pagesize); |
62 | |
63 | map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0); |
64 | if (map2 == MAP_FAILED) |
65 | ksft_exit_fail_msg(msg: "mmap failed" ); |
66 | |
67 | // Dirty bit is set for new regions even if they are reused |
68 | if (map == map2) |
69 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, |
70 | "Test %s dirty bit of reused address page\n" , __func__); |
71 | else |
72 | ksft_test_result_skip(msg: "Test %s dirty bit of reused address page\n" , __func__); |
73 | |
74 | munmap(map2, pagesize); |
75 | } |
76 | |
77 | static void test_hugepage(int pagemap_fd, int pagesize) |
78 | { |
79 | char *map; |
80 | int i, ret; |
81 | size_t hpage_len = read_pmd_pagesize(); |
82 | |
83 | if (!hpage_len) |
84 | ksft_exit_fail_msg(msg: "Reading PMD pagesize failed" ); |
85 | |
86 | map = memalign(hpage_len, hpage_len); |
87 | if (!map) |
88 | ksft_exit_fail_msg(msg: "memalign failed\n" ); |
89 | |
90 | ret = madvise(map, hpage_len, MADV_HUGEPAGE); |
91 | if (ret) |
92 | ksft_exit_fail_msg(msg: "madvise failed %d\n" , ret); |
93 | |
94 | for (i = 0; i < hpage_len; i++) |
95 | map[i] = (char)i; |
96 | |
97 | if (check_huge_anon(map, 1, hpage_len)) { |
98 | ksft_test_result_pass(msg: "Test %s huge page allocation\n" , __func__); |
99 | |
100 | clear_softdirty(); |
101 | for (i = 0 ; i < TEST_ITERATIONS ; i++) { |
102 | if (pagemap_is_softdirty(pagemap_fd, map) == 1) { |
103 | ksft_print_msg(msg: "dirty bit was 1, but should be 0 (i=%d)\n" , i); |
104 | break; |
105 | } |
106 | |
107 | clear_softdirty(); |
108 | // Write something to the page to get the dirty bit enabled on the page |
109 | map[0]++; |
110 | |
111 | if (pagemap_is_softdirty(pagemap_fd, map) == 0) { |
112 | ksft_print_msg(msg: "dirty bit was 0, but should be 1 (i=%d)\n" , i); |
113 | break; |
114 | } |
115 | clear_softdirty(); |
116 | } |
117 | |
118 | ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n" , __func__); |
119 | } else { |
120 | // hugepage allocation failed. skip these tests |
121 | ksft_test_result_skip(msg: "Test %s huge page allocation\n" , __func__); |
122 | ksft_test_result_skip(msg: "Test %s huge page dirty bit\n" , __func__); |
123 | } |
124 | free(map); |
125 | } |
126 | |
127 | static void test_mprotect(int pagemap_fd, int pagesize, bool anon) |
128 | { |
129 | const char *type[] = {"file" , "anon" }; |
130 | const char *fname = "./soft-dirty-test-file" ; |
131 | int test_fd; |
132 | char *map; |
133 | |
134 | if (anon) { |
135 | map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, |
136 | MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); |
137 | if (!map) |
138 | ksft_exit_fail_msg(msg: "anon mmap failed\n" ); |
139 | } else { |
140 | test_fd = open(fname, O_RDWR | O_CREAT, 0664); |
141 | if (test_fd < 0) { |
142 | ksft_test_result_skip(msg: "Test %s open() file failed\n" , __func__); |
143 | return; |
144 | } |
145 | unlink(fname); |
146 | ftruncate(test_fd, pagesize); |
147 | map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, |
148 | MAP_SHARED, test_fd, 0); |
149 | if (!map) |
150 | ksft_exit_fail_msg(msg: "file mmap failed\n" ); |
151 | } |
152 | |
153 | *map = 1; |
154 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, |
155 | "Test %s-%s dirty bit of new written page\n" , |
156 | __func__, type[anon]); |
157 | clear_softdirty(); |
158 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, |
159 | "Test %s-%s soft-dirty clear after clear_refs\n" , |
160 | __func__, type[anon]); |
161 | mprotect(map, pagesize, PROT_READ); |
162 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, |
163 | "Test %s-%s soft-dirty clear after marking RO\n" , |
164 | __func__, type[anon]); |
165 | mprotect(map, pagesize, PROT_READ|PROT_WRITE); |
166 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0, |
167 | "Test %s-%s soft-dirty clear after marking RW\n" , |
168 | __func__, type[anon]); |
169 | *map = 2; |
170 | ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, |
171 | "Test %s-%s soft-dirty after rewritten\n" , |
172 | __func__, type[anon]); |
173 | |
174 | munmap(map, pagesize); |
175 | |
176 | if (!anon) |
177 | close(test_fd); |
178 | } |
179 | |
180 | static void test_mprotect_anon(int pagemap_fd, int pagesize) |
181 | { |
182 | test_mprotect(pagemap_fd, pagesize, true); |
183 | } |
184 | |
185 | static void test_mprotect_file(int pagemap_fd, int pagesize) |
186 | { |
187 | test_mprotect(pagemap_fd, pagesize, false); |
188 | } |
189 | |
190 | int main(int argc, char **argv) |
191 | { |
192 | int pagemap_fd; |
193 | int pagesize; |
194 | |
195 | ksft_print_header(); |
196 | ksft_set_plan(plan: 15); |
197 | |
198 | pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); |
199 | if (pagemap_fd < 0) |
200 | ksft_exit_fail_msg(msg: "Failed to open %s\n" , PAGEMAP_FILE_PATH); |
201 | |
202 | pagesize = getpagesize(); |
203 | |
204 | test_simple(pagemap_fd, pagesize); |
205 | test_vma_reuse(pagemap_fd, pagesize); |
206 | test_hugepage(pagemap_fd, pagesize); |
207 | test_mprotect_anon(pagemap_fd, pagesize); |
208 | test_mprotect_file(pagemap_fd, pagesize); |
209 | |
210 | close(pagemap_fd); |
211 | |
212 | return ksft_exit_pass(); |
213 | } |
214 | |