| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * A test of splitting PMD THPs and PTE-mapped THPs from a specified virtual
- * address range in a process via <debugfs>/split_huge_pages interface.
- */
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <unistd.h>
- #include <inttypes.h>
- #include <string.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <sys/mount.h>
- #include <malloc.h>
- #include <stdbool.h>
- #include <time.h>
- #include "vm_util.h"
- #include "../kselftest.h"
- uint64_t pagesize;
- unsigned int pageshift;
- uint64_t pmd_pagesize;
- #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
- #define SMAP_PATH "/proc/self/smaps"
- #define INPUT_MAX 80
- #define PID_FMT "%d,0x%lx,0x%lx,%d"
- #define PATH_FMT "%s,0x%lx,0x%lx,%d"
- #define PFN_MASK ((1UL<<55)-1)
- #define KPF_THP (1UL<<22)
- int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
- {
- uint64_t paddr;
- uint64_t page_flags;
- if (pagemap_file) {
- pread(pagemap_file, &paddr, sizeof(paddr),
- ((long)vaddr >> pageshift) * sizeof(paddr));
- if (kpageflags_file) {
- pread(kpageflags_file, &page_flags, sizeof(page_flags),
- (paddr & PFN_MASK) * sizeof(page_flags));
- return !!(page_flags & KPF_THP);
- }
- }
- return 0;
- }
- static void write_file(const char *path, const char *buf, size_t buflen)
- {
- int fd;
- ssize_t numwritten;
- fd = open(path, O_WRONLY);
- if (fd == -1)
- ksft_exit_fail_msg("%s open failed: %s\n", path, strerror(errno));
- numwritten = write(fd, buf, buflen - 1);
- close(fd);
- if (numwritten < 1)
- ksft_exit_fail_msg("Write failed\n");
- }
- static void write_debugfs(const char *fmt, ...)
- {
- char input[INPUT_MAX];
- int ret;
- va_list argp;
- va_start(argp, fmt);
- ret = vsnprintf(input, INPUT_MAX, fmt, argp);
- va_end(argp);
- if (ret >= INPUT_MAX)
- ksft_exit_fail_msg("%s: Debugfs input is too long\n", __func__);
- write_file(SPLIT_DEBUGFS, input, ret + 1);
- }
- static char *allocate_zero_filled_hugepage(size_t len)
- {
- char *result;
- size_t i;
- result = memalign(pmd_pagesize, len);
- if (!result) {
- printf("Fail to allocate memory\n");
- exit(EXIT_FAILURE);
- }
- madvise(result, len, MADV_HUGEPAGE);
- for (i = 0; i < len; i++)
- result[i] = (char)0;
- return result;
- }
- static void verify_rss_anon_split_huge_page_all_zeroes(char *one_page, int nr_hpages, size_t len)
- {
- unsigned long rss_anon_before, rss_anon_after;
- size_t i;
- if (!check_huge_anon(one_page, 4, pmd_pagesize)) {
- printf("No THP is allocated\n");
- exit(EXIT_FAILURE);
- }
- rss_anon_before = rss_anon();
- if (!rss_anon_before) {
- printf("No RssAnon is allocated before split\n");
- exit(EXIT_FAILURE);
- }
- /* split all THPs */
- write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
- (uint64_t)one_page + len, 0);
- for (i = 0; i < len; i++)
- if (one_page[i] != (char)0) {
- printf("%ld byte corrupted\n", i);
- exit(EXIT_FAILURE);
- }
- if (!check_huge_anon(one_page, 0, pmd_pagesize)) {
- printf("Still AnonHugePages not split\n");
- exit(EXIT_FAILURE);
- }
- rss_anon_after = rss_anon();
- if (rss_anon_after >= rss_anon_before) {
- printf("Incorrect RssAnon value. Before: %ld After: %ld\n",
- rss_anon_before, rss_anon_after);
- exit(EXIT_FAILURE);
- }
- }
- void split_pmd_zero_pages(void)
- {
- char *one_page;
- int nr_hpages = 4;
- size_t len = nr_hpages * pmd_pagesize;
- one_page = allocate_zero_filled_hugepage(len);
- verify_rss_anon_split_huge_page_all_zeroes(one_page, nr_hpages, len);
- printf("Split zero filled huge pages successful\n");
- free(one_page);
- }
- void split_pmd_thp(void)
- {
- char *one_page;
- size_t len = 4 * pmd_pagesize;
- size_t i;
- one_page = memalign(pmd_pagesize, len);
- if (!one_page)
- ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
- madvise(one_page, len, MADV_HUGEPAGE);
- for (i = 0; i < len; i++)
- one_page[i] = (char)i;
- if (!check_huge_anon(one_page, 4, pmd_pagesize))
- ksft_exit_fail_msg("No THP is allocated\n");
- /* split all THPs */
- write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
- (uint64_t)one_page + len, 0);
- for (i = 0; i < len; i++)
- if (one_page[i] != (char)i)
- ksft_exit_fail_msg("%ld byte corrupted\n", i);
- if (!check_huge_anon(one_page, 0, pmd_pagesize))
- ksft_exit_fail_msg("Still AnonHugePages not split\n");
- ksft_test_result_pass("Split huge pages successful\n");
- free(one_page);
- }
- void split_pte_mapped_thp(void)
- {
- char *one_page, *pte_mapped, *pte_mapped2;
- size_t len = 4 * pmd_pagesize;
- uint64_t thp_size;
- size_t i;
- const char *pagemap_template = "/proc/%d/pagemap";
- const char *kpageflags_proc = "/proc/kpageflags";
- char pagemap_proc[255];
- int pagemap_fd;
- int kpageflags_fd;
- if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0)
- ksft_exit_fail_msg("get pagemap proc error: %s\n", strerror(errno));
- pagemap_fd = open(pagemap_proc, O_RDONLY);
- if (pagemap_fd == -1)
- ksft_exit_fail_msg("read pagemap: %s\n", strerror(errno));
- kpageflags_fd = open(kpageflags_proc, O_RDONLY);
- if (kpageflags_fd == -1)
- ksft_exit_fail_msg("read kpageflags: %s\n", strerror(errno));
- one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
- if (one_page == MAP_FAILED)
- ksft_exit_fail_msg("Fail to allocate memory: %s\n", strerror(errno));
- madvise(one_page, len, MADV_HUGEPAGE);
- for (i = 0; i < len; i++)
- one_page[i] = (char)i;
- if (!check_huge_anon(one_page, 4, pmd_pagesize))
- ksft_exit_fail_msg("No THP is allocated\n");
- /* remap the first pagesize of first THP */
- pte_mapped = mremap(one_page, pagesize, pagesize, MREMAP_MAYMOVE);
- /* remap the Nth pagesize of Nth THP */
- for (i = 1; i < 4; i++) {
- pte_mapped2 = mremap(one_page + pmd_pagesize * i + pagesize * i,
- pagesize, pagesize,
- MREMAP_MAYMOVE|MREMAP_FIXED,
- pte_mapped + pagesize * i);
- if (pte_mapped2 == MAP_FAILED)
- ksft_exit_fail_msg("mremap failed: %s\n", strerror(errno));
- }
- /* smap does not show THPs after mremap, use kpageflags instead */
- thp_size = 0;
- for (i = 0; i < pagesize * 4; i++)
- if (i % pagesize == 0 &&
- is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
- thp_size++;
- if (thp_size != 4)
- ksft_exit_fail_msg("Some THPs are missing during mremap\n");
- /* split all remapped THPs */
- write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped,
- (uint64_t)pte_mapped + pagesize * 4, 0);
- /* smap does not show THPs after mremap, use kpageflags instead */
- thp_size = 0;
- for (i = 0; i < pagesize * 4; i++) {
- if (pte_mapped[i] != (char)i)
- ksft_exit_fail_msg("%ld byte corrupted\n", i);
- if (i % pagesize == 0 &&
- is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
- thp_size++;
- }
- if (thp_size)
- ksft_exit_fail_msg("Still %ld THPs not split\n", thp_size);
- ksft_test_result_pass("Split PTE-mapped huge pages successful\n");
- munmap(one_page, len);
- close(pagemap_fd);
- close(kpageflags_fd);
- }
- void split_file_backed_thp(void)
- {
- int status;
- int fd;
- ssize_t num_written;
- char tmpfs_template[] = "/tmp/thp_split_XXXXXX";
- const char *tmpfs_loc = mkdtemp(tmpfs_template);
- char testfile[INPUT_MAX];
- uint64_t pgoff_start = 0, pgoff_end = 1024;
- ksft_print_msg("Please enable pr_debug in split_huge_pages_in_file() for more info.\n");
- status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m");
- if (status)
- ksft_exit_fail_msg("Unable to create a tmpfs for testing\n");
- status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc);
- if (status >= INPUT_MAX) {
- ksft_exit_fail_msg("Fail to create file-backed THP split testing file\n");
- }
- fd = open(testfile, O_CREAT|O_WRONLY, 0664);
- if (fd == -1) {
- ksft_perror("Cannot open testing file");
- goto cleanup;
- }
- /* write something to the file, so a file-backed THP can be allocated */
- num_written = write(fd, tmpfs_loc, strlen(tmpfs_loc) + 1);
- close(fd);
- if (num_written < 1) {
- ksft_perror("Fail to write data to testing file");
- goto cleanup;
- }
- /* split the file-backed THP */
- write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end, 0);
- status = unlink(testfile);
- if (status) {
- ksft_perror("Cannot remove testing file");
- goto cleanup;
- }
- status = umount(tmpfs_loc);
- if (status) {
- rmdir(tmpfs_loc);
- ksft_exit_fail_msg("Unable to umount %s\n", tmpfs_loc);
- }
- status = rmdir(tmpfs_loc);
- if (status)
- ksft_exit_fail_msg("cannot remove tmp dir: %s\n", strerror(errno));
- ksft_print_msg("Please check dmesg for more information\n");
- ksft_test_result_pass("File-backed THP split test done\n");
- return;
- cleanup:
- umount(tmpfs_loc);
- rmdir(tmpfs_loc);
- ksft_exit_fail_msg("Error occurred\n");
- }
- bool prepare_thp_fs(const char *xfs_path, char *thp_fs_template,
- const char **thp_fs_loc)
- {
- if (xfs_path) {
- *thp_fs_loc = xfs_path;
- return false;
- }
- *thp_fs_loc = mkdtemp(thp_fs_template);
- if (!*thp_fs_loc)
- ksft_exit_fail_msg("cannot create temp folder\n");
- return true;
- }
- void cleanup_thp_fs(const char *thp_fs_loc, bool created_tmp)
- {
- int status;
- if (!created_tmp)
- return;
- status = rmdir(thp_fs_loc);
- if (status)
- ksft_exit_fail_msg("cannot remove tmp dir: %s\n",
- strerror(errno));
- }
- int create_pagecache_thp_and_fd(const char *testfile, size_t fd_size, int *fd,
- char **addr)
- {
- size_t i;
- int dummy = 0;
- srand(time(NULL));
- *fd = open(testfile, O_CREAT | O_RDWR, 0664);
- if (*fd == -1)
- ksft_exit_fail_msg("Failed to create a file at %s\n", testfile);
- for (i = 0; i < fd_size; i++) {
- unsigned char byte = (unsigned char)i;
- write(*fd, &byte, sizeof(byte));
- }
- close(*fd);
- sync();
- *fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
- if (*fd == -1) {
- ksft_perror("open drop_caches");
- goto err_out_unlink;
- }
- if (write(*fd, "3", 1) != 1) {
- ksft_perror("write to drop_caches");
- goto err_out_unlink;
- }
- close(*fd);
- *fd = open(testfile, O_RDWR);
- if (*fd == -1) {
- ksft_perror("Failed to open testfile\n");
- goto err_out_unlink;
- }
- *addr = mmap(NULL, fd_size, PROT_READ|PROT_WRITE, MAP_SHARED, *fd, 0);
- if (*addr == (char *)-1) {
- ksft_perror("cannot mmap");
- goto err_out_close;
- }
- madvise(*addr, fd_size, MADV_HUGEPAGE);
- for (size_t i = 0; i < fd_size; i++)
- dummy += *(*addr + i);
- asm volatile("" : "+r" (dummy));
- if (!check_huge_file(*addr, fd_size / pmd_pagesize, pmd_pagesize)) {
- ksft_print_msg("No large pagecache folio generated, please provide a filesystem supporting large folio\n");
- munmap(*addr, fd_size);
- close(*fd);
- unlink(testfile);
- ksft_test_result_skip("Pagecache folio split skipped\n");
- return -2;
- }
- return 0;
- err_out_close:
- close(*fd);
- err_out_unlink:
- unlink(testfile);
- ksft_exit_fail_msg("Failed to create large pagecache folios\n");
- return -1;
- }
- void split_thp_in_pagecache_to_order(size_t fd_size, int order, const char *fs_loc)
- {
- int fd;
- char *addr;
- size_t i;
- char testfile[INPUT_MAX];
- int err = 0;
- err = snprintf(testfile, INPUT_MAX, "%s/test", fs_loc);
- if (err < 0)
- ksft_exit_fail_msg("cannot generate right test file name\n");
- err = create_pagecache_thp_and_fd(testfile, fd_size, &fd, &addr);
- if (err)
- return;
- err = 0;
- write_debugfs(PID_FMT, getpid(), (uint64_t)addr, (uint64_t)addr + fd_size, order);
- for (i = 0; i < fd_size; i++)
- if (*(addr + i) != (char)i) {
- ksft_print_msg("%lu byte corrupted in the file\n", i);
- err = EXIT_FAILURE;
- goto out;
- }
- if (!check_huge_file(addr, 0, pmd_pagesize)) {
- ksft_print_msg("Still FilePmdMapped not split\n");
- err = EXIT_FAILURE;
- goto out;
- }
- out:
- munmap(addr, fd_size);
- close(fd);
- unlink(testfile);
- if (err)
- ksft_exit_fail_msg("Split PMD-mapped pagecache folio to order %d failed\n", order);
- ksft_test_result_pass("Split PMD-mapped pagecache folio to order %d passed\n", order);
- }
- int main(int argc, char **argv)
- {
- int i;
- size_t fd_size;
- char *optional_xfs_path = NULL;
- char fs_loc_template[] = "/tmp/thp_fs_XXXXXX";
- const char *fs_loc;
- bool created_tmp;
- ksft_print_header();
- if (geteuid() != 0) {
- ksft_print_msg("Please run the benchmark as root\n");
- ksft_finished();
- }
- if (argc > 1)
- optional_xfs_path = argv[1];
- ksft_set_plan(3+9);
- pagesize = getpagesize();
- pageshift = ffs(pagesize) - 1;
- pmd_pagesize = read_pmd_pagesize();
- if (!pmd_pagesize)
- ksft_exit_fail_msg("Reading PMD pagesize failed\n");
- fd_size = 2 * pmd_pagesize;
- split_pmd_zero_pages();
- split_pmd_thp();
- split_pte_mapped_thp();
- split_file_backed_thp();
- created_tmp = prepare_thp_fs(optional_xfs_path, fs_loc_template,
- &fs_loc);
- for (i = 8; i >= 0; i--)
- split_thp_in_pagecache_to_order(fd_size, i, fs_loc);
- cleanup_thp_fs(fs_loc, created_tmp);
- ksft_finished();
- return 0;
- }
|