split_huge_page_test.c (7038B)
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 7#define _GNU_SOURCE 8#include <stdio.h> 9#include <stdlib.h> 10#include <stdarg.h> 11#include <unistd.h> 12#include <inttypes.h> 13#include <string.h> 14#include <fcntl.h> 15#include <sys/mman.h> 16#include <sys/mount.h> 17#include <malloc.h> 18#include <stdbool.h> 19#include "vm_util.h" 20 21uint64_t pagesize; 22unsigned int pageshift; 23uint64_t pmd_pagesize; 24 25#define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages" 26#define INPUT_MAX 80 27 28#define PID_FMT "%d,0x%lx,0x%lx" 29#define PATH_FMT "%s,0x%lx,0x%lx" 30 31#define PFN_MASK ((1UL<<55)-1) 32#define KPF_THP (1UL<<22) 33 34int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file) 35{ 36 uint64_t paddr; 37 uint64_t page_flags; 38 39 if (pagemap_file) { 40 pread(pagemap_file, &paddr, sizeof(paddr), 41 ((long)vaddr >> pageshift) * sizeof(paddr)); 42 43 if (kpageflags_file) { 44 pread(kpageflags_file, &page_flags, sizeof(page_flags), 45 (paddr & PFN_MASK) * sizeof(page_flags)); 46 47 return !!(page_flags & KPF_THP); 48 } 49 } 50 return 0; 51} 52 53static int write_file(const char *path, const char *buf, size_t buflen) 54{ 55 int fd; 56 ssize_t numwritten; 57 58 fd = open(path, O_WRONLY); 59 if (fd == -1) 60 return 0; 61 62 numwritten = write(fd, buf, buflen - 1); 63 close(fd); 64 if (numwritten < 1) 65 return 0; 66 67 return (unsigned int) numwritten; 68} 69 70static void write_debugfs(const char *fmt, ...) 71{ 72 char input[INPUT_MAX]; 73 int ret; 74 va_list argp; 75 76 va_start(argp, fmt); 77 ret = vsnprintf(input, INPUT_MAX, fmt, argp); 78 va_end(argp); 79 80 if (ret >= INPUT_MAX) { 81 printf("%s: Debugfs input is too long\n", __func__); 82 exit(EXIT_FAILURE); 83 } 84 85 if (!write_file(SPLIT_DEBUGFS, input, ret + 1)) { 86 perror(SPLIT_DEBUGFS); 87 exit(EXIT_FAILURE); 88 } 89} 90 91void split_pmd_thp(void) 92{ 93 char *one_page; 94 size_t len = 4 * pmd_pagesize; 95 uint64_t thp_size; 96 size_t i; 97 98 one_page = memalign(pmd_pagesize, len); 99 100 if (!one_page) { 101 printf("Fail to allocate memory\n"); 102 exit(EXIT_FAILURE); 103 } 104 105 madvise(one_page, len, MADV_HUGEPAGE); 106 107 for (i = 0; i < len; i++) 108 one_page[i] = (char)i; 109 110 thp_size = check_huge(one_page); 111 if (!thp_size) { 112 printf("No THP is allocated\n"); 113 exit(EXIT_FAILURE); 114 } 115 116 /* split all THPs */ 117 write_debugfs(PID_FMT, getpid(), (uint64_t)one_page, 118 (uint64_t)one_page + len); 119 120 for (i = 0; i < len; i++) 121 if (one_page[i] != (char)i) { 122 printf("%ld byte corrupted\n", i); 123 exit(EXIT_FAILURE); 124 } 125 126 127 thp_size = check_huge(one_page); 128 if (thp_size) { 129 printf("Still %ld kB AnonHugePages not split\n", thp_size); 130 exit(EXIT_FAILURE); 131 } 132 133 printf("Split huge pages successful\n"); 134 free(one_page); 135} 136 137void split_pte_mapped_thp(void) 138{ 139 char *one_page, *pte_mapped, *pte_mapped2; 140 size_t len = 4 * pmd_pagesize; 141 uint64_t thp_size; 142 size_t i; 143 const char *pagemap_template = "/proc/%d/pagemap"; 144 const char *kpageflags_proc = "/proc/kpageflags"; 145 char pagemap_proc[255]; 146 int pagemap_fd; 147 int kpageflags_fd; 148 149 if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0) { 150 perror("get pagemap proc error"); 151 exit(EXIT_FAILURE); 152 } 153 pagemap_fd = open(pagemap_proc, O_RDONLY); 154 155 if (pagemap_fd == -1) { 156 perror("read pagemap:"); 157 exit(EXIT_FAILURE); 158 } 159 160 kpageflags_fd = open(kpageflags_proc, O_RDONLY); 161 162 if (kpageflags_fd == -1) { 163 perror("read kpageflags:"); 164 exit(EXIT_FAILURE); 165 } 166 167 one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE, 168 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 169 170 madvise(one_page, len, MADV_HUGEPAGE); 171 172 for (i = 0; i < len; i++) 173 one_page[i] = (char)i; 174 175 thp_size = check_huge(one_page); 176 if (!thp_size) { 177 printf("No THP is allocated\n"); 178 exit(EXIT_FAILURE); 179 } 180 181 /* remap the first pagesize of first THP */ 182 pte_mapped = mremap(one_page, pagesize, pagesize, MREMAP_MAYMOVE); 183 184 /* remap the Nth pagesize of Nth THP */ 185 for (i = 1; i < 4; i++) { 186 pte_mapped2 = mremap(one_page + pmd_pagesize * i + pagesize * i, 187 pagesize, pagesize, 188 MREMAP_MAYMOVE|MREMAP_FIXED, 189 pte_mapped + pagesize * i); 190 if (pte_mapped2 == (char *)-1) { 191 perror("mremap failed"); 192 exit(EXIT_FAILURE); 193 } 194 } 195 196 /* smap does not show THPs after mremap, use kpageflags instead */ 197 thp_size = 0; 198 for (i = 0; i < pagesize * 4; i++) 199 if (i % pagesize == 0 && 200 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd)) 201 thp_size++; 202 203 if (thp_size != 4) { 204 printf("Some THPs are missing during mremap\n"); 205 exit(EXIT_FAILURE); 206 } 207 208 /* split all remapped THPs */ 209 write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped, 210 (uint64_t)pte_mapped + pagesize * 4); 211 212 /* smap does not show THPs after mremap, use kpageflags instead */ 213 thp_size = 0; 214 for (i = 0; i < pagesize * 4; i++) { 215 if (pte_mapped[i] != (char)i) { 216 printf("%ld byte corrupted\n", i); 217 exit(EXIT_FAILURE); 218 } 219 if (i % pagesize == 0 && 220 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd)) 221 thp_size++; 222 } 223 224 if (thp_size) { 225 printf("Still %ld THPs not split\n", thp_size); 226 exit(EXIT_FAILURE); 227 } 228 229 printf("Split PTE-mapped huge pages successful\n"); 230 munmap(one_page, len); 231 close(pagemap_fd); 232 close(kpageflags_fd); 233} 234 235void split_file_backed_thp(void) 236{ 237 int status; 238 int fd; 239 ssize_t num_written; 240 char tmpfs_template[] = "/tmp/thp_split_XXXXXX"; 241 const char *tmpfs_loc = mkdtemp(tmpfs_template); 242 char testfile[INPUT_MAX]; 243 uint64_t pgoff_start = 0, pgoff_end = 1024; 244 245 printf("Please enable pr_debug in split_huge_pages_in_file() if you need more info.\n"); 246 247 status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m"); 248 249 if (status) { 250 printf("Unable to create a tmpfs for testing\n"); 251 exit(EXIT_FAILURE); 252 } 253 254 status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc); 255 if (status >= INPUT_MAX) { 256 printf("Fail to create file-backed THP split testing file\n"); 257 goto cleanup; 258 } 259 260 fd = open(testfile, O_CREAT|O_WRONLY); 261 if (fd == -1) { 262 perror("Cannot open testing file\n"); 263 goto cleanup; 264 } 265 266 /* write something to the file, so a file-backed THP can be allocated */ 267 num_written = write(fd, tmpfs_loc, strlen(tmpfs_loc) + 1); 268 close(fd); 269 270 if (num_written < 1) { 271 printf("Fail to write data to testing file\n"); 272 goto cleanup; 273 } 274 275 /* split the file-backed THP */ 276 write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end); 277 278 status = unlink(testfile); 279 if (status) 280 perror("Cannot remove testing file\n"); 281 282cleanup: 283 status = umount(tmpfs_loc); 284 if (status) { 285 printf("Unable to umount %s\n", tmpfs_loc); 286 exit(EXIT_FAILURE); 287 } 288 status = rmdir(tmpfs_loc); 289 if (status) { 290 perror("cannot remove tmp dir"); 291 exit(EXIT_FAILURE); 292 } 293 294 printf("file-backed THP split test done, please check dmesg for more information\n"); 295} 296 297int main(int argc, char **argv) 298{ 299 if (geteuid() != 0) { 300 printf("Please run the benchmark as root\n"); 301 exit(EXIT_FAILURE); 302 } 303 304 pagesize = getpagesize(); 305 pageshift = ffs(pagesize) - 1; 306 pmd_pagesize = read_pmd_pagesize(); 307 308 split_pmd_thp(); 309 split_pte_mapped_thp(); 310 split_file_backed_thp(); 311 312 return 0; 313}