1 // SPDX-License-Identifier: GPL-2.0
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.
16 #include <sys/mount.h>
22 unsigned int pageshift;
23 uint64_t pmd_pagesize;
25 #define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages"
28 #define PID_FMT "%d,0x%lx,0x%lx"
29 #define PATH_FMT "%s,0x%lx,0x%lx"
31 #define PFN_MASK ((1UL<<55)-1)
32 #define KPF_THP (1UL<<22)
34 int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file)
40 pread(pagemap_file, &paddr, sizeof(paddr),
41 ((long)vaddr >> pageshift) * sizeof(paddr));
43 if (kpageflags_file) {
44 pread(kpageflags_file, &page_flags, sizeof(page_flags),
45 (paddr & PFN_MASK) * sizeof(page_flags));
47 return !!(page_flags & KPF_THP);
53 static int write_file(const char *path, const char *buf, size_t buflen)
58 fd = open(path, O_WRONLY);
62 numwritten = write(fd, buf, buflen - 1);
67 return (unsigned int) numwritten;
70 static void write_debugfs(const char *fmt, ...)
72 char input[INPUT_MAX];
77 ret = vsnprintf(input, INPUT_MAX, fmt, argp);
80 if (ret >= INPUT_MAX) {
81 printf("%s: Debugfs input is too long\n", __func__);
85 if (!write_file(SPLIT_DEBUGFS, input, ret + 1)) {
86 perror(SPLIT_DEBUGFS);
91 void split_pmd_thp(void)
94 size_t len = 4 * pmd_pagesize;
97 one_page = memalign(pmd_pagesize, len);
100 printf("Fail to allocate memory\n");
104 madvise(one_page, len, MADV_HUGEPAGE);
106 for (i = 0; i < len; i++)
107 one_page[i] = (char)i;
109 if (!check_huge_anon(one_page, 1, pmd_pagesize)) {
110 printf("No THP is allocated\n");
115 write_debugfs(PID_FMT, getpid(), (uint64_t)one_page,
116 (uint64_t)one_page + len);
118 for (i = 0; i < len; i++)
119 if (one_page[i] != (char)i) {
120 printf("%ld byte corrupted\n", i);
125 if (check_huge_anon(one_page, 0, pmd_pagesize)) {
126 printf("Still AnonHugePages not split\n");
130 printf("Split huge pages successful\n");
134 void split_pte_mapped_thp(void)
136 char *one_page, *pte_mapped, *pte_mapped2;
137 size_t len = 4 * pmd_pagesize;
140 const char *pagemap_template = "/proc/%d/pagemap";
141 const char *kpageflags_proc = "/proc/kpageflags";
142 char pagemap_proc[255];
146 if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0) {
147 perror("get pagemap proc error");
150 pagemap_fd = open(pagemap_proc, O_RDONLY);
152 if (pagemap_fd == -1) {
153 perror("read pagemap:");
157 kpageflags_fd = open(kpageflags_proc, O_RDONLY);
159 if (kpageflags_fd == -1) {
160 perror("read kpageflags:");
164 one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE,
165 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
167 madvise(one_page, len, MADV_HUGEPAGE);
169 for (i = 0; i < len; i++)
170 one_page[i] = (char)i;
172 if (!check_huge_anon(one_page, 1, pmd_pagesize)) {
173 printf("No THP is allocated\n");
177 /* remap the first pagesize of first THP */
178 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,
184 MREMAP_MAYMOVE|MREMAP_FIXED,
185 pte_mapped + pagesize * i);
186 if (pte_mapped2 == (char *)-1) {
187 perror("mremap failed");
192 /* smap does not show THPs after mremap, use kpageflags instead */
194 for (i = 0; i < pagesize * 4; i++)
195 if (i % pagesize == 0 &&
196 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
200 printf("Some THPs are missing during mremap\n");
204 /* split all remapped THPs */
205 write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped,
206 (uint64_t)pte_mapped + pagesize * 4);
208 /* smap does not show THPs after mremap, use kpageflags instead */
210 for (i = 0; i < pagesize * 4; i++) {
211 if (pte_mapped[i] != (char)i) {
212 printf("%ld byte corrupted\n", i);
215 if (i % pagesize == 0 &&
216 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd))
221 printf("Still %ld THPs not split\n", thp_size);
225 printf("Split PTE-mapped huge pages successful\n");
226 munmap(one_page, len);
228 close(kpageflags_fd);
231 void split_file_backed_thp(void)
236 char tmpfs_template[] = "/tmp/thp_split_XXXXXX";
237 const char *tmpfs_loc = mkdtemp(tmpfs_template);
238 char testfile[INPUT_MAX];
239 uint64_t pgoff_start = 0, pgoff_end = 1024;
241 printf("Please enable pr_debug in split_huge_pages_in_file() if you need more info.\n");
243 status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m");
246 printf("Unable to create a tmpfs for testing\n");
250 status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc);
251 if (status >= INPUT_MAX) {
252 printf("Fail to create file-backed THP split testing file\n");
256 fd = open(testfile, O_CREAT|O_WRONLY);
258 perror("Cannot open testing file\n");
262 /* write something to the file, so a file-backed THP can be allocated */
263 num_written = write(fd, tmpfs_loc, strlen(tmpfs_loc) + 1);
266 if (num_written < 1) {
267 printf("Fail to write data to testing file\n");
271 /* split the file-backed THP */
272 write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end);
274 status = unlink(testfile);
276 perror("Cannot remove testing file\n");
279 status = umount(tmpfs_loc);
281 printf("Unable to umount %s\n", tmpfs_loc);
284 status = rmdir(tmpfs_loc);
286 perror("cannot remove tmp dir");
290 printf("file-backed THP split test done, please check dmesg for more information\n");
293 int main(int argc, char **argv)
295 if (geteuid() != 0) {
296 printf("Please run the benchmark as root\n");
300 pagesize = getpagesize();
301 pageshift = ffs(pagesize) - 1;
302 pmd_pagesize = read_pmd_pagesize();
305 split_pte_mapped_thp();
306 split_file_backed_thp();