soft-dirty.c 8.9 KB

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