split_huge_page_test.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * A test of splitting PMD THPs and PTE-mapped THPs from a specified virtual
  4. * address range in a process via <debugfs>/split_huge_pages interface.
  5. */
  6. #define _GNU_SOURCE
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <stdarg.h>
  10. #include <unistd.h>
  11. #include <inttypes.h>
  12. #include <string.h>
  13. #include <fcntl.h>
  14. #include <sys/mman.h>
  15. #include <sys/mount.h>
  16. #include <malloc.h>
  17. #include <stdbool.h>
  18. #include <time.h>
  19. #include "vm_util.h"
  20. #include "../kselftest.h"
  21. uint64_t pagesize;
  22. unsigned int pageshift;
  23. uint64_t pmd_pagesize;
  24. #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
  25. #define SMAP_PATH "/proc/self/smaps"
  26. #define INPUT_MAX 80
  27. #define PID_FMT "%d,0x%lx,0x%lx,%d"
  28. #define PATH_FMT "%s,0x%lx,0x%lx,%d"
  29. #define PFN_MASK ((1UL<<55)-1)
  30. #define KPF_THP (1UL<<22)
  31. int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
  32. {
  33. uint64_t paddr;
  34. uint64_t page_flags;
  35. if (pagemap_file) {
  36. pread(pagemap_file, &paddr, sizeof(paddr),
  37. ((long)vaddr >> pageshift) * sizeof(paddr));
  38. if (kpageflags_file) {
  39. pread(kpageflags_file, &page_flags, sizeof(page_flags),
  40. (paddr & PFN_MASK) * sizeof(page_flags));
  41. return !!(page_flags & KPF_THP);
  42. }
  43. }
  44. return 0;
  45. }
  46. static void write_file(const char *path, const char *buf, size_t buflen)
  47. {
  48. int fd;
  49. ssize_t numwritten;
  50. fd = open(path, O_WRONLY);
  51. if (fd == -1)
  52. ksft_exit_fail_msg("%s open failed: %s\n", path, strerror(errno));
  53. numwritten = write(fd, buf, buflen - 1);
  54. close(fd);
  55. if (numwritten < 1)
  56. ksft_exit_fail_msg("Write failed\n");
  57. }
  58. static void write_debugfs(const char *fmt, ...)
  59. {
  60. char input[INPUT_MAX];
  61. int ret;
  62. va_list argp;
  63. va_start(argp, fmt);
  64. ret = vsnprintf(input, INPUT_MAX, fmt, argp);
  65. va_end(argp);
  66. if (ret >= INPUT_MAX)
  67. ksft_exit_fail_msg("%s: Debugfs input is too long\n", __func__);
  68. write_file(SPLIT_DEBUGFS, input, ret + 1);
  69. }
  70. static char *allocate_zero_filled_hugepage(size_t len)
  71. {
  72. char *result;
  73. size_t i;
  74. result = memalign(pmd_pagesize, len);
  75. if (!result) {
  76. printf("Fail to allocate memory\n");
  77. exit(EXIT_FAILURE);
  78. }
  79. madvise(result, len, MADV_HUGEPAGE);
  80. for (i = 0; i < len; i++)
  81. result[i] = (char)0;
  82. return result;
  83. }
  84. static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hpages, size_t len)
  85. {
  86. unsigned long rss_anon_before, rss_anon_after;
  87. size_t i;
  88. if (!check_huge_anon(one_page, 4, pmd_pagesize)) {
  89. printf("No THP is allocated\n");
  90. exit(EXIT_FAILURE);
  91. }
  92. rss_anon_before = rss_anon();
  93. if (!rss_anon_before) {
  94. printf("No RssAnon is allocated before split\n");
  95. exit(EXIT_FAILURE);
  96. }
  97. /* split all THPs */
  98. write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
  99. (uint64_t)one_page + len, 0);
  100. for (i = 0; i < len; i++)
  101. if (one_page[i] != (char)0) {
  102. printf("%ld byte corrupted\n", i);
  103. exit(EXIT_FAILURE);
  104. }
  105. if (!check_huge_anon(one_page, 0, pmd_pagesize)) {
  106. printf("Still AnonHugePages not split\n");
  107. exit(EXIT_FAILURE);
  108. }
  109. rss_anon_after = rss_anon();
  110. if (rss_anon_after >= rss_anon_before) {
  111. printf("Incorrect RssAnon value. Before: %ld After: %ld\n",
  112. rss_anon_before, rss_anon_after);
  113. exit(EXIT_FAILURE);
  114. }
  115. }
  116. void split_pmd_zero_pages(void)
  117. {
  118. char *one_page;
  119. int nr_hpages = 4;
  120. size_t len = nr_hpages * pmd_pagesize;
  121. one_page = allocate_zero_filled_hugepage(len);
  122. verify_rss_anon_split_huge_page_all_zeroes(one_page, nr_hpages, len);
  123. printf("Split zero filled huge pages successful\n");
  124. free(one_page);
  125. }
  126. void split_pmd_thp(void)
  127. {
  128. char *one_page;
  129. size_t len = 4 * pmd_pagesize;
  130. size_t i;
  131. one_page = memalign(pmd_pagesize, len);
  132. if (!one_page)
  133. ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
  134. madvise(one_page, len, MADV_HUGEPAGE);
  135. for (i = 0; i < len; i++)
  136. one_page[i] = (char)i;
  137. if (!check_huge_anon(one_page, 4, pmd_pagesize))
  138. ksft_exit_fail_msg("No THP is allocated\n");
  139. /* split all THPs */
  140. write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
  141. (uint64_t)one_page + len, 0);
  142. for (i = 0; i < len; i++)
  143. if (one_page[i] != (char)i)
  144. ksft_exit_fail_msg("%ld byte corrupted\n", i);
  145. if (!check_huge_anon(one_page, 0, pmd_pagesize))
  146. ksft_exit_fail_msg("Still AnonHugePages not split\n");
  147. ksft_test_result_pass("Split huge pages successful\n");
  148. free(one_page);
  149. }
  150. void split_pte_mapped_thp(void)
  151. {
  152. char *one_page, *pte_mapped, *pte_mapped2;
  153. size_t len = 4 * pmd_pagesize;
  154. uint64_t thp_size;
  155. size_t i;
  156. const char *pagemap_template = "/proc/%d/pagemap";
  157. const char *kpageflags_proc = "/proc/kpageflags";
  158. char pagemap_proc[255];
  159. int pagemap_fd;
  160. int kpageflags_fd;
  161. if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0)
  162. ksft_exit_fail_msg("get pagemap proc error: %s\n", strerror(errno));
  163. pagemap_fd = open(pagemap_proc, O_RDONLY);
  164. if (pagemap_fd == -1)
  165. ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
  166. kpageflags_fd = open(kpageflags_proc, O_RDONLY);
  167. if (kpageflags_fd == -1)
  168. ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
  169. one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
  170. MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
  171. if (one_page == MAP_FAILED)
  172. ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
  173. madvise(one_page, len, MADV_HUGEPAGE);
  174. for (i = 0; i < len; i++)
  175. one_page[i] = (char)i;
  176. if (!check_huge_anon(one_page, 4, pmd_pagesize))
  177. ksft_exit_fail_msg("No THP is allocated\n");
  178. /* remap the first pagesize of first THP */
  179. pte_mapped = mremap(one_page, pagesize, pagesize, MREMAP_MAYMOVE);
  180. /* remap the Nth pagesize of Nth THP */
  181. for (i = 1; i < 4; i++) {
  182. pte_mapped2 = mremap(one_page + pmd_pagesize * i + pagesize * i,
  183. pagesize, pagesize,
  184. MREMAP_MAYMOVE|MREMAP_FIXED,
  185. pte_mapped + pagesize * i);
  186. if (pte_mapped2 == MAP_FAILED)
  187. ksft_exit_fail_msg("mremap failed: %s\n", strerror(errno));
  188. }
  189. /* smap does not show THPs after mremap, use kpageflags instead */
  190. thp_size = 0;
  191. for (i = 0; i < pagesize * 4; i++)
  192. if (i % pagesize == 0 &&
  193. is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
  194. thp_size++;
  195. if (thp_size != 4)
  196. ksft_exit_fail_msg("Some THPs are missing during mremap\n");
  197. /* split all remapped THPs */
  198. write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped,
  199. (uint64_t)pte_mapped + pagesize * 4, 0);
  200. /* smap does not show THPs after mremap, use kpageflags instead */
  201. thp_size = 0;
  202. for (i = 0; i < pagesize * 4; i++) {
  203. if (pte_mapped[i] != (char)i)
  204. ksft_exit_fail_msg("%ld byte corrupted\n", i);
  205. if (i % pagesize == 0 &&
  206. is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
  207. thp_size++;
  208. }
  209. if (thp_size)
  210. ksft_exit_fail_msg("Still %ld THPs not split\n", thp_size);
  211. ksft_test_result_pass("Split PTE-mapped huge pages successful\n");
  212. munmap(one_page, len);
  213. close(pagemap_fd);
  214. close(kpageflags_fd);
  215. }
  216. void split_file_backed_thp(void)
  217. {
  218. int status;
  219. int fd;
  220. ssize_t num_written;
  221. char tmpfs_template[] = "/tmp/thp_split_XXXXXX";
  222. const char *tmpfs_loc = mkdtemp(tmpfs_template);
  223. char testfile[INPUT_MAX];
  224. uint64_t pgoff_start = 0, pgoff_end = 1024;
  225. ksft_print_msg("Please enable pr_debug in split_huge_pages_in_file() for more info.\n");
  226. status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m");
  227. if (status)
  228. ksft_exit_fail_msg("Unable to create a tmpfs for testing\n");
  229. status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc);
  230. if (status >= INPUT_MAX) {
  231. ksft_exit_fail_msg("Fail to create file-backed THP split testing file\n");
  232. }
  233. fd = open(testfile, O_CREAT|O_WRONLY, 0664);
  234. if (fd == -1) {
  235. ksft_perror("Cannot open testing file");
  236. goto cleanup;
  237. }
  238. /* write something to the file, so a file-backed THP can be allocated */
  239. num_written = write(fd, tmpfs_loc, strlen(tmpfs_loc) + 1);
  240. close(fd);
  241. if (num_written < 1) {
  242. ksft_perror("Fail to write data to testing file");
  243. goto cleanup;
  244. }
  245. /* split the file-backed THP */
  246. write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end, 0);
  247. status = unlink(testfile);
  248. if (status) {
  249. ksft_perror("Cannot remove testing file");
  250. goto cleanup;
  251. }
  252. status = umount(tmpfs_loc);
  253. if (status) {
  254. rmdir(tmpfs_loc);
  255. ksft_exit_fail_msg("Unable to umount %s\n", tmpfs_loc);
  256. }
  257. status = rmdir(tmpfs_loc);
  258. if (status)
  259. ksft_exit_fail_msg("cannot remove tmp dir: %s\n", strerror(errno));
  260. ksft_print_msg("Please check dmesg for more information\n");
  261. ksft_test_result_pass("File-backed THP split test done\n");
  262. return;
  263. cleanup:
  264. umount(tmpfs_loc);
  265. rmdir(tmpfs_loc);
  266. ksft_exit_fail_msg("Error occurred\n");
  267. }
  268. bool prepare_thp_fs(const char *xfs_path, char *thp_fs_template,
  269. const char **thp_fs_loc)
  270. {
  271. if (xfs_path) {
  272. *thp_fs_loc = xfs_path;
  273. return false;
  274. }
  275. *thp_fs_loc = mkdtemp(thp_fs_template);
  276. if (!*thp_fs_loc)
  277. ksft_exit_fail_msg("cannot create temp folder\n");
  278. return true;
  279. }
  280. void cleanup_thp_fs(const char *thp_fs_loc, bool created_tmp)
  281. {
  282. int status;
  283. if (!created_tmp)
  284. return;
  285. status = rmdir(thp_fs_loc);
  286. if (status)
  287. ksft_exit_fail_msg("cannot remove tmp dir: %s\n",
  288. strerror(errno));
  289. }
  290. int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size, int *fd,
  291. char **addr)
  292. {
  293. size_t i;
  294. int dummy = 0;
  295. srand(time(NULL));
  296. *fd = open(testfile, O_CREAT | O_RDWR, 0664);
  297. if (*fd == -1)
  298. ksft_exit_fail_msg("Failed to create a file at %s\n", testfile);
  299. for (i = 0; i < fd_size; i++) {
  300. unsigned char byte = (unsigned char)i;
  301. write(*fd, &byte, sizeof(byte));
  302. }
  303. close(*fd);
  304. sync();
  305. *fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
  306. if (*fd == -1) {
  307. ksft_perror("open drop_caches");
  308. goto err_out_unlink;
  309. }
  310. if (write(*fd, "3", 1) != 1) {
  311. ksft_perror("write to drop_caches");
  312. goto err_out_unlink;
  313. }
  314. close(*fd);
  315. *fd = open(testfile, O_RDWR);
  316. if (*fd == -1) {
  317. ksft_perror("Failed to open testfile\n");
  318. goto err_out_unlink;
  319. }
  320. *addr = mmap(NULL, fd_size, PROT_READ|PROT_WRITE, MAP_SHARED, *fd, 0);
  321. if (*addr == (char *)-1) {
  322. ksft_perror("cannot mmap");
  323. goto err_out_close;
  324. }
  325. madvise(*addr, fd_size, MADV_HUGEPAGE);
  326. for (size_t i = 0; i < fd_size; i++)
  327. dummy += *(*addr + i);
  328. asm volatile("" : "+r" (dummy));
  329. if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) {
  330. ksft_print_msg("No large pagecache folio generated, please provide a filesystem supporting large folio\n");
  331. munmap(*addr, fd_size);
  332. close(*fd);
  333. unlink(testfile);
  334. ksft_test_result_skip("Pagecache folio split skipped\n");
  335. return -2;
  336. }
  337. return 0;
  338. err_out_close:
  339. close(*fd);
  340. err_out_unlink:
  341. unlink(testfile);
  342. ksft_exit_fail_msg("Failed to create large pagecache folios\n");
  343. return -1;
  344. }
  345. void split_thp_in_pagecache_to_order(size_t fd_size, int order, const char *fs_loc)
  346. {
  347. int fd;
  348. char *addr;
  349. size_t i;
  350. char testfile[INPUT_MAX];
  351. int err = 0;
  352. err = snprintf(testfile, INPUT_MAX, "%s/test", fs_loc);
  353. if (err < 0)
  354. ksft_exit_fail_msg("cannot generate right test file name\n");
  355. err = create_pagecache_thp_and_fd(testfile, fd_size, &fd, &addr);
  356. if (err)
  357. return;
  358. err = 0;
  359. write_debugfs(PID_FMT, getpid(), (uint64_t)addr, (uint64_t)addr + fd_size, order);
  360. for (i = 0; i < fd_size; i++)
  361. if (*(addr + i) != (char)i) {
  362. ksft_print_msg("%lu byte corrupted in the file\n", i);
  363. err = EXIT_FAILURE;
  364. goto out;
  365. }
  366. if (!check_huge_file(addr, 0, pmd_pagesize)) {
  367. ksft_print_msg("Still FilePmdMapped not split\n");
  368. err = EXIT_FAILURE;
  369. goto out;
  370. }
  371. out:
  372. munmap(addr, fd_size);
  373. close(fd);
  374. unlink(testfile);
  375. if (err)
  376. ksft_exit_fail_msg("Split PMD-mapped pagecache folio to order %d failed\n", order);
  377. ksft_test_result_pass("Split PMD-mapped pagecache folio to order %d passed\n", order);
  378. }
  379. int main(int argc, char **argv)
  380. {
  381. int i;
  382. size_t fd_size;
  383. char *optional_xfs_path = NULL;
  384. char fs_loc_template[] = "/tmp/thp_fs_XXXXXX";
  385. const char *fs_loc;
  386. bool created_tmp;
  387. ksft_print_header();
  388. if (geteuid() != 0) {
  389. ksft_print_msg("Please run the benchmark as root\n");
  390. ksft_finished();
  391. }
  392. if (argc > 1)
  393. optional_xfs_path = argv[1];
  394. ksft_set_plan(3+9);
  395. pagesize = getpagesize();
  396. pageshift = ffs(pagesize) - 1;
  397. pmd_pagesize = read_pmd_pagesize();
  398. if (!pmd_pagesize)
  399. ksft_exit_fail_msg("Reading PMD pagesize failed\n");
  400. fd_size = 2 * pmd_pagesize;
  401. split_pmd_zero_pages();
  402. split_pmd_thp();
  403. split_pte_mapped_thp();
  404. split_file_backed_thp();
  405. created_tmp = prepare_thp_fs(optional_xfs_path, fs_loc_template,
  406. &fs_loc);
  407. for (i = 8; i >= 0; i--)
  408. split_thp_in_pagecache_to_order(fd_size, i, fs_loc);
  409. cleanup_thp_fs(fs_loc, created_tmp);
  410. ksft_finished();
  411. return 0;
  412. }