Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v5.13-rc4 390 lines 8.6 kB view raw
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 20uint64_t pagesize; 21unsigned int pageshift; 22uint64_t pmd_pagesize; 23 24#define PMD_SIZE_PATH "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size" 25#define SPLIT_DEBUGFS "/sys/kernel/debug/split_huge_pages" 26#define SMAP_PATH "/proc/self/smaps" 27#define INPUT_MAX 80 28 29#define PID_FMT "%d,0x%lx,0x%lx" 30#define PATH_FMT "%s,0x%lx,0x%lx" 31 32#define PFN_MASK ((1UL<<55)-1) 33#define KPF_THP (1UL<<22) 34 35int is_backed_by_thp(char *vaddr, int pagemap_file, int kpageflags_file) 36{ 37 uint64_t paddr; 38 uint64_t page_flags; 39 40 if (pagemap_file) { 41 pread(pagemap_file, &paddr, sizeof(paddr), 42 ((long)vaddr >> pageshift) * sizeof(paddr)); 43 44 if (kpageflags_file) { 45 pread(kpageflags_file, &page_flags, sizeof(page_flags), 46 (paddr & PFN_MASK) * sizeof(page_flags)); 47 48 return !!(page_flags & KPF_THP); 49 } 50 } 51 return 0; 52} 53 54 55static uint64_t read_pmd_pagesize(void) 56{ 57 int fd; 58 char buf[20]; 59 ssize_t num_read; 60 61 fd = open(PMD_SIZE_PATH, O_RDONLY); 62 if (fd == -1) { 63 perror("Open hpage_pmd_size failed"); 64 exit(EXIT_FAILURE); 65 } 66 num_read = read(fd, buf, 19); 67 if (num_read < 1) { 68 close(fd); 69 perror("Read hpage_pmd_size failed"); 70 exit(EXIT_FAILURE); 71 } 72 buf[num_read] = '\0'; 73 close(fd); 74 75 return strtoul(buf, NULL, 10); 76} 77 78static int write_file(const char *path, const char *buf, size_t buflen) 79{ 80 int fd; 81 ssize_t numwritten; 82 83 fd = open(path, O_WRONLY); 84 if (fd == -1) 85 return 0; 86 87 numwritten = write(fd, buf, buflen - 1); 88 close(fd); 89 if (numwritten < 1) 90 return 0; 91 92 return (unsigned int) numwritten; 93} 94 95static void write_debugfs(const char *fmt, ...) 96{ 97 char input[INPUT_MAX]; 98 int ret; 99 va_list argp; 100 101 va_start(argp, fmt); 102 ret = vsnprintf(input, INPUT_MAX, fmt, argp); 103 va_end(argp); 104 105 if (ret >= INPUT_MAX) { 106 printf("%s: Debugfs input is too long\n", __func__); 107 exit(EXIT_FAILURE); 108 } 109 110 if (!write_file(SPLIT_DEBUGFS, input, ret + 1)) { 111 perror(SPLIT_DEBUGFS); 112 exit(EXIT_FAILURE); 113 } 114} 115 116#define MAX_LINE_LENGTH 500 117 118static bool check_for_pattern(FILE *fp, const char *pattern, char *buf) 119{ 120 while (fgets(buf, MAX_LINE_LENGTH, fp) != NULL) { 121 if (!strncmp(buf, pattern, strlen(pattern))) 122 return true; 123 } 124 return false; 125} 126 127static uint64_t check_huge(void *addr) 128{ 129 uint64_t thp = 0; 130 int ret; 131 FILE *fp; 132 char buffer[MAX_LINE_LENGTH]; 133 char addr_pattern[MAX_LINE_LENGTH]; 134 135 ret = snprintf(addr_pattern, MAX_LINE_LENGTH, "%08lx-", 136 (unsigned long) addr); 137 if (ret >= MAX_LINE_LENGTH) { 138 printf("%s: Pattern is too long\n", __func__); 139 exit(EXIT_FAILURE); 140 } 141 142 143 fp = fopen(SMAP_PATH, "r"); 144 if (!fp) { 145 printf("%s: Failed to open file %s\n", __func__, SMAP_PATH); 146 exit(EXIT_FAILURE); 147 } 148 if (!check_for_pattern(fp, addr_pattern, buffer)) 149 goto err_out; 150 151 /* 152 * Fetch the AnonHugePages: in the same block and check the number of 153 * hugepages. 154 */ 155 if (!check_for_pattern(fp, "AnonHugePages:", buffer)) 156 goto err_out; 157 158 if (sscanf(buffer, "AnonHugePages:%10ld kB", &thp) != 1) { 159 printf("Reading smap error\n"); 160 exit(EXIT_FAILURE); 161 } 162 163err_out: 164 fclose(fp); 165 return thp; 166} 167 168void split_pmd_thp(void) 169{ 170 char *one_page; 171 size_t len = 4 * pmd_pagesize; 172 uint64_t thp_size; 173 size_t i; 174 175 one_page = memalign(pmd_pagesize, len); 176 177 if (!one_page) { 178 printf("Fail to allocate memory\n"); 179 exit(EXIT_FAILURE); 180 } 181 182 madvise(one_page, len, MADV_HUGEPAGE); 183 184 for (i = 0; i < len; i++) 185 one_page[i] = (char)i; 186 187 thp_size = check_huge(one_page); 188 if (!thp_size) { 189 printf("No THP is allocated\n"); 190 exit(EXIT_FAILURE); 191 } 192 193 /* split all THPs */ 194 write_debugfs(PID_FMT, getpid(), (uint64_t)one_page, 195 (uint64_t)one_page + len); 196 197 for (i = 0; i < len; i++) 198 if (one_page[i] != (char)i) { 199 printf("%ld byte corrupted\n", i); 200 exit(EXIT_FAILURE); 201 } 202 203 204 thp_size = check_huge(one_page); 205 if (thp_size) { 206 printf("Still %ld kB AnonHugePages not split\n", thp_size); 207 exit(EXIT_FAILURE); 208 } 209 210 printf("Split huge pages successful\n"); 211 free(one_page); 212} 213 214void split_pte_mapped_thp(void) 215{ 216 char *one_page, *pte_mapped, *pte_mapped2; 217 size_t len = 4 * pmd_pagesize; 218 uint64_t thp_size; 219 size_t i; 220 const char *pagemap_template = "/proc/%d/pagemap"; 221 const char *kpageflags_proc = "/proc/kpageflags"; 222 char pagemap_proc[255]; 223 int pagemap_fd; 224 int kpageflags_fd; 225 226 if (snprintf(pagemap_proc, 255, pagemap_template, getpid()) < 0) { 227 perror("get pagemap proc error"); 228 exit(EXIT_FAILURE); 229 } 230 pagemap_fd = open(pagemap_proc, O_RDONLY); 231 232 if (pagemap_fd == -1) { 233 perror("read pagemap:"); 234 exit(EXIT_FAILURE); 235 } 236 237 kpageflags_fd = open(kpageflags_proc, O_RDONLY); 238 239 if (kpageflags_fd == -1) { 240 perror("read kpageflags:"); 241 exit(EXIT_FAILURE); 242 } 243 244 one_page = mmap((void *)(1UL << 30), len, PROT_READ | PROT_WRITE, 245 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 246 247 madvise(one_page, len, MADV_HUGEPAGE); 248 249 for (i = 0; i < len; i++) 250 one_page[i] = (char)i; 251 252 thp_size = check_huge(one_page); 253 if (!thp_size) { 254 printf("No THP is allocated\n"); 255 exit(EXIT_FAILURE); 256 } 257 258 /* remap the first pagesize of first THP */ 259 pte_mapped = mremap(one_page, pagesize, pagesize, MREMAP_MAYMOVE); 260 261 /* remap the Nth pagesize of Nth THP */ 262 for (i = 1; i < 4; i++) { 263 pte_mapped2 = mremap(one_page + pmd_pagesize * i + pagesize * i, 264 pagesize, pagesize, 265 MREMAP_MAYMOVE|MREMAP_FIXED, 266 pte_mapped + pagesize * i); 267 if (pte_mapped2 == (char *)-1) { 268 perror("mremap failed"); 269 exit(EXIT_FAILURE); 270 } 271 } 272 273 /* smap does not show THPs after mremap, use kpageflags instead */ 274 thp_size = 0; 275 for (i = 0; i < pagesize * 4; i++) 276 if (i % pagesize == 0 && 277 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd)) 278 thp_size++; 279 280 if (thp_size != 4) { 281 printf("Some THPs are missing during mremap\n"); 282 exit(EXIT_FAILURE); 283 } 284 285 /* split all remapped THPs */ 286 write_debugfs(PID_FMT, getpid(), (uint64_t)pte_mapped, 287 (uint64_t)pte_mapped + pagesize * 4); 288 289 /* smap does not show THPs after mremap, use kpageflags instead */ 290 thp_size = 0; 291 for (i = 0; i < pagesize * 4; i++) { 292 if (pte_mapped[i] != (char)i) { 293 printf("%ld byte corrupted\n", i); 294 exit(EXIT_FAILURE); 295 } 296 if (i % pagesize == 0 && 297 is_backed_by_thp(&pte_mapped[i], pagemap_fd, kpageflags_fd)) 298 thp_size++; 299 } 300 301 if (thp_size) { 302 printf("Still %ld THPs not split\n", thp_size); 303 exit(EXIT_FAILURE); 304 } 305 306 printf("Split PTE-mapped huge pages successful\n"); 307 munmap(one_page, len); 308 close(pagemap_fd); 309 close(kpageflags_fd); 310} 311 312void split_file_backed_thp(void) 313{ 314 int status; 315 int fd; 316 ssize_t num_written; 317 char tmpfs_template[] = "/tmp/thp_split_XXXXXX"; 318 const char *tmpfs_loc = mkdtemp(tmpfs_template); 319 char testfile[INPUT_MAX]; 320 uint64_t pgoff_start = 0, pgoff_end = 1024; 321 322 printf("Please enable pr_debug in split_huge_pages_in_file() if you need more info.\n"); 323 324 status = mount("tmpfs", tmpfs_loc, "tmpfs", 0, "huge=always,size=4m"); 325 326 if (status) { 327 printf("Unable to create a tmpfs for testing\n"); 328 exit(EXIT_FAILURE); 329 } 330 331 status = snprintf(testfile, INPUT_MAX, "%s/thp_file", tmpfs_loc); 332 if (status >= INPUT_MAX) { 333 printf("Fail to create file-backed THP split testing file\n"); 334 goto cleanup; 335 } 336 337 fd = open(testfile, O_CREAT|O_WRONLY); 338 if (fd == -1) { 339 perror("Cannot open testing file\n"); 340 goto cleanup; 341 } 342 343 /* write something to the file, so a file-backed THP can be allocated */ 344 num_written = write(fd, tmpfs_loc, sizeof(tmpfs_loc)); 345 close(fd); 346 347 if (num_written < 1) { 348 printf("Fail to write data to testing file\n"); 349 goto cleanup; 350 } 351 352 /* split the file-backed THP */ 353 write_debugfs(PATH_FMT, testfile, pgoff_start, pgoff_end); 354 355 status = unlink(testfile); 356 if (status) 357 perror("Cannot remove testing file\n"); 358 359cleanup: 360 status = umount(tmpfs_loc); 361 if (status) { 362 printf("Unable to umount %s\n", tmpfs_loc); 363 exit(EXIT_FAILURE); 364 } 365 status = rmdir(tmpfs_loc); 366 if (status) { 367 perror("cannot remove tmp dir"); 368 exit(EXIT_FAILURE); 369 } 370 371 printf("file-backed THP split test done, please check dmesg for more information\n"); 372} 373 374int main(int argc, char **argv) 375{ 376 if (geteuid() != 0) { 377 printf("Please run the benchmark as root\n"); 378 exit(EXIT_FAILURE); 379 } 380 381 pagesize = getpagesize(); 382 pageshift = ffs(pagesize) - 1; 383 pmd_pagesize = read_pmd_pagesize(); 384 385 split_pmd_thp(); 386 split_pte_mapped_thp(); 387 split_file_backed_thp(); 388 389 return 0; 390}