linux/tools/testing/selftests/vm/madv_populate.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * MADV_POPULATE_READ and MADV_POPULATE_WRITE tests
   4 *
   5 * Copyright 2021, Red Hat, Inc.
   6 *
   7 * Author(s): David Hildenbrand <david@redhat.com>
   8 */
   9#define _GNU_SOURCE
  10#include <stdlib.h>
  11#include <string.h>
  12#include <stdbool.h>
  13#include <stdint.h>
  14#include <unistd.h>
  15#include <errno.h>
  16#include <fcntl.h>
  17#include <sys/mman.h>
  18
  19#include "../kselftest.h"
  20
  21#if defined(MADV_POPULATE_READ) && defined(MADV_POPULATE_WRITE)
  22
  23/*
  24 * For now, we're using 2 MiB of private anonymous memory for all tests.
  25 */
  26#define SIZE (2 * 1024 * 1024)
  27
  28static size_t pagesize;
  29
  30static uint64_t pagemap_get_entry(int fd, char *start)
  31{
  32        const unsigned long pfn = (unsigned long)start / pagesize;
  33        uint64_t entry;
  34        int ret;
  35
  36        ret = pread(fd, &entry, sizeof(entry), pfn * sizeof(entry));
  37        if (ret != sizeof(entry))
  38                ksft_exit_fail_msg("reading pagemap failed\n");
  39        return entry;
  40}
  41
  42static bool pagemap_is_populated(int fd, char *start)
  43{
  44        uint64_t entry = pagemap_get_entry(fd, start);
  45
  46        /* Present or swapped. */
  47        return entry & 0xc000000000000000ull;
  48}
  49
  50static bool pagemap_is_softdirty(int fd, char *start)
  51{
  52        uint64_t entry = pagemap_get_entry(fd, start);
  53
  54        return entry & 0x0080000000000000ull;
  55}
  56
  57static void sense_support(void)
  58{
  59        char *addr;
  60        int ret;
  61
  62        addr = mmap(0, pagesize, PROT_READ | PROT_WRITE,
  63                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
  64        if (!addr)
  65                ksft_exit_fail_msg("mmap failed\n");
  66
  67        ret = madvise(addr, pagesize, MADV_POPULATE_READ);
  68        if (ret)
  69                ksft_exit_skip("MADV_POPULATE_READ is not available\n");
  70
  71        ret = madvise(addr, pagesize, MADV_POPULATE_WRITE);
  72        if (ret)
  73                ksft_exit_skip("MADV_POPULATE_WRITE is not available\n");
  74
  75        munmap(addr, pagesize);
  76}
  77
  78static void test_prot_read(void)
  79{
  80        char *addr;
  81        int ret;
  82
  83        ksft_print_msg("[RUN] %s\n", __func__);
  84
  85        addr = mmap(0, SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
  86        if (addr == MAP_FAILED)
  87                ksft_exit_fail_msg("mmap failed\n");
  88
  89        ret = madvise(addr, SIZE, MADV_POPULATE_READ);
  90        ksft_test_result(!ret, "MADV_POPULATE_READ with PROT_READ\n");
  91
  92        ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
  93        ksft_test_result(ret == -1 && errno == EINVAL,
  94                         "MADV_POPULATE_WRITE with PROT_READ\n");
  95
  96        munmap(addr, SIZE);
  97}
  98
  99static void test_prot_write(void)
 100{
 101        char *addr;
 102        int ret;
 103
 104        ksft_print_msg("[RUN] %s\n", __func__);
 105
 106        addr = mmap(0, SIZE, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 107        if (addr == MAP_FAILED)
 108                ksft_exit_fail_msg("mmap failed\n");
 109
 110        ret = madvise(addr, SIZE, MADV_POPULATE_READ);
 111        ksft_test_result(ret == -1 && errno == EINVAL,
 112                         "MADV_POPULATE_READ with PROT_WRITE\n");
 113
 114        ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
 115        ksft_test_result(!ret, "MADV_POPULATE_WRITE with PROT_WRITE\n");
 116
 117        munmap(addr, SIZE);
 118}
 119
 120static void test_holes(void)
 121{
 122        char *addr;
 123        int ret;
 124
 125        ksft_print_msg("[RUN] %s\n", __func__);
 126
 127        addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
 128                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 129        if (addr == MAP_FAILED)
 130                ksft_exit_fail_msg("mmap failed\n");
 131        ret = munmap(addr + pagesize, pagesize);
 132        if (ret)
 133                ksft_exit_fail_msg("munmap failed\n");
 134
 135        /* Hole in the middle */
 136        ret = madvise(addr, SIZE, MADV_POPULATE_READ);
 137        ksft_test_result(ret == -1 && errno == ENOMEM,
 138                         "MADV_POPULATE_READ with holes in the middle\n");
 139        ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
 140        ksft_test_result(ret == -1 && errno == ENOMEM,
 141                         "MADV_POPULATE_WRITE with holes in the middle\n");
 142
 143        /* Hole at end */
 144        ret = madvise(addr, 2 * pagesize, MADV_POPULATE_READ);
 145        ksft_test_result(ret == -1 && errno == ENOMEM,
 146                         "MADV_POPULATE_READ with holes at the end\n");
 147        ret = madvise(addr, 2 * pagesize, MADV_POPULATE_WRITE);
 148        ksft_test_result(ret == -1 && errno == ENOMEM,
 149                         "MADV_POPULATE_WRITE with holes at the end\n");
 150
 151        /* Hole at beginning */
 152        ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_READ);
 153        ksft_test_result(ret == -1 && errno == ENOMEM,
 154                         "MADV_POPULATE_READ with holes at the beginning\n");
 155        ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_WRITE);
 156        ksft_test_result(ret == -1 && errno == ENOMEM,
 157                         "MADV_POPULATE_WRITE with holes at the beginning\n");
 158
 159        munmap(addr, SIZE);
 160}
 161
 162static bool range_is_populated(char *start, ssize_t size)
 163{
 164        int fd = open("/proc/self/pagemap", O_RDONLY);
 165        bool ret = true;
 166
 167        if (fd < 0)
 168                ksft_exit_fail_msg("opening pagemap failed\n");
 169        for (; size > 0 && ret; size -= pagesize, start += pagesize)
 170                if (!pagemap_is_populated(fd, start))
 171                        ret = false;
 172        close(fd);
 173        return ret;
 174}
 175
 176static bool range_is_not_populated(char *start, ssize_t size)
 177{
 178        int fd = open("/proc/self/pagemap", O_RDONLY);
 179        bool ret = true;
 180
 181        if (fd < 0)
 182                ksft_exit_fail_msg("opening pagemap failed\n");
 183        for (; size > 0 && ret; size -= pagesize, start += pagesize)
 184                if (pagemap_is_populated(fd, start))
 185                        ret = false;
 186        close(fd);
 187        return ret;
 188}
 189
 190static void test_populate_read(void)
 191{
 192        char *addr;
 193        int ret;
 194
 195        ksft_print_msg("[RUN] %s\n", __func__);
 196
 197        addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
 198                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 199        if (addr == MAP_FAILED)
 200                ksft_exit_fail_msg("mmap failed\n");
 201        ksft_test_result(range_is_not_populated(addr, SIZE),
 202                         "range initially not populated\n");
 203
 204        ret = madvise(addr, SIZE, MADV_POPULATE_READ);
 205        ksft_test_result(!ret, "MADV_POPULATE_READ\n");
 206        ksft_test_result(range_is_populated(addr, SIZE),
 207                         "range is populated\n");
 208
 209        munmap(addr, SIZE);
 210}
 211
 212static void test_populate_write(void)
 213{
 214        char *addr;
 215        int ret;
 216
 217        ksft_print_msg("[RUN] %s\n", __func__);
 218
 219        addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
 220                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 221        if (addr == MAP_FAILED)
 222                ksft_exit_fail_msg("mmap failed\n");
 223        ksft_test_result(range_is_not_populated(addr, SIZE),
 224                         "range initially not populated\n");
 225
 226        ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
 227        ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
 228        ksft_test_result(range_is_populated(addr, SIZE),
 229                         "range is populated\n");
 230
 231        munmap(addr, SIZE);
 232}
 233
 234static bool range_is_softdirty(char *start, ssize_t size)
 235{
 236        int fd = open("/proc/self/pagemap", O_RDONLY);
 237        bool ret = true;
 238
 239        if (fd < 0)
 240                ksft_exit_fail_msg("opening pagemap failed\n");
 241        for (; size > 0 && ret; size -= pagesize, start += pagesize)
 242                if (!pagemap_is_softdirty(fd, start))
 243                        ret = false;
 244        close(fd);
 245        return ret;
 246}
 247
 248static bool range_is_not_softdirty(char *start, ssize_t size)
 249{
 250        int fd = open("/proc/self/pagemap", O_RDONLY);
 251        bool ret = true;
 252
 253        if (fd < 0)
 254                ksft_exit_fail_msg("opening pagemap failed\n");
 255        for (; size > 0 && ret; size -= pagesize, start += pagesize)
 256                if (pagemap_is_softdirty(fd, start))
 257                        ret = false;
 258        close(fd);
 259        return ret;
 260}
 261
 262static void clear_softdirty(void)
 263{
 264        int fd = open("/proc/self/clear_refs", O_WRONLY);
 265        const char *ctrl = "4";
 266        int ret;
 267
 268        if (fd < 0)
 269                ksft_exit_fail_msg("opening clear_refs failed\n");
 270        ret = write(fd, ctrl, strlen(ctrl));
 271        if (ret != strlen(ctrl))
 272                ksft_exit_fail_msg("writing clear_refs failed\n");
 273        close(fd);
 274}
 275
 276static void test_softdirty(void)
 277{
 278        char *addr;
 279        int ret;
 280
 281        ksft_print_msg("[RUN] %s\n", __func__);
 282
 283        addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
 284                    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 285        if (addr == MAP_FAILED)
 286                ksft_exit_fail_msg("mmap failed\n");
 287
 288        /* Clear any softdirty bits. */
 289        clear_softdirty();
 290        ksft_test_result(range_is_not_softdirty(addr, SIZE),
 291                         "range is not softdirty\n");
 292
 293        /* Populating READ should set softdirty. */
 294        ret = madvise(addr, SIZE, MADV_POPULATE_READ);
 295        ksft_test_result(!ret, "MADV_POPULATE_READ\n");
 296        ksft_test_result(range_is_not_softdirty(addr, SIZE),
 297                         "range is not softdirty\n");
 298
 299        /* Populating WRITE should set softdirty. */
 300        ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
 301        ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
 302        ksft_test_result(range_is_softdirty(addr, SIZE),
 303                         "range is softdirty\n");
 304
 305        munmap(addr, SIZE);
 306}
 307
 308int main(int argc, char **argv)
 309{
 310        int err;
 311
 312        pagesize = getpagesize();
 313
 314        ksft_print_header();
 315        ksft_set_plan(21);
 316
 317        sense_support();
 318        test_prot_read();
 319        test_prot_write();
 320        test_holes();
 321        test_populate_read();
 322        test_populate_write();
 323        test_softdirty();
 324
 325        err = ksft_get_fail_cnt();
 326        if (err)
 327                ksft_exit_fail_msg("%d out of %d tests failed\n",
 328                                   err, ksft_test_num());
 329        return ksft_exit_pass();
 330}
 331
 332#else /* defined(MADV_POPULATE_READ) && defined(MADV_POPULATE_WRITE) */
 333
 334#warning "missing MADV_POPULATE_READ or MADV_POPULATE_WRITE definition"
 335
 336int main(int argc, char **argv)
 337{
 338        ksft_print_header();
 339        ksft_exit_skip("MADV_POPULATE_READ or MADV_POPULATE_WRITE not defined\n");
 340}
 341
 342#endif /* defined(MADV_POPULATE_READ) && defined(MADV_POPULATE_WRITE) */
 343