linux/tools/testing/selftests/vm/split_huge_page_test.c
<<
>>
Prefs
   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}
 391