linux/tools/testing/selftests/vm/mlock2-tests.c
<<
>>
Prefs
   1#define _GNU_SOURCE
   2#include <sys/mman.h>
   3#include <stdint.h>
   4#include <stdio.h>
   5#include <stdlib.h>
   6#include <unistd.h>
   7#include <string.h>
   8#include <sys/time.h>
   9#include <sys/resource.h>
  10#include <syscall.h>
  11#include <errno.h>
  12#include <stdbool.h>
  13
  14#ifndef MLOCK_ONFAULT
  15#define MLOCK_ONFAULT 1
  16#endif
  17
  18#ifndef MCL_ONFAULT
  19#define MCL_ONFAULT (MCL_FUTURE << 1)
  20#endif
  21
  22static int mlock2_(void *start, size_t len, int flags)
  23{
  24#ifdef __NR_mlock2
  25        return syscall(__NR_mlock2, start, len, flags);
  26#else
  27        errno = ENOSYS;
  28        return -1;
  29#endif
  30}
  31
  32struct vm_boundaries {
  33        unsigned long start;
  34        unsigned long end;
  35};
  36
  37static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
  38{
  39        FILE *file;
  40        int ret = 1;
  41        char line[1024] = {0};
  42        char *end_addr;
  43        char *stop;
  44        unsigned long start;
  45        unsigned long end;
  46
  47        if (!area)
  48                return ret;
  49
  50        file = fopen("/proc/self/maps", "r");
  51        if (!file) {
  52                perror("fopen");
  53                return ret;
  54        }
  55
  56        memset(area, 0, sizeof(struct vm_boundaries));
  57
  58        while(fgets(line, 1024, file)) {
  59                end_addr = strchr(line, '-');
  60                if (!end_addr) {
  61                        printf("cannot parse /proc/self/maps\n");
  62                        goto out;
  63                }
  64                *end_addr = '\0';
  65                end_addr++;
  66                stop = strchr(end_addr, ' ');
  67                if (!stop) {
  68                        printf("cannot parse /proc/self/maps\n");
  69                        goto out;
  70                }
  71                stop = '\0';
  72
  73                sscanf(line, "%lx", &start);
  74                sscanf(end_addr, "%lx", &end);
  75
  76                if (start <= addr && end > addr) {
  77                        area->start = start;
  78                        area->end = end;
  79                        ret = 0;
  80                        goto out;
  81                }
  82        }
  83out:
  84        fclose(file);
  85        return ret;
  86}
  87
  88static uint64_t get_pageflags(unsigned long addr)
  89{
  90        FILE *file;
  91        uint64_t pfn;
  92        unsigned long offset;
  93
  94        file = fopen("/proc/self/pagemap", "r");
  95        if (!file) {
  96                perror("fopen pagemap");
  97                _exit(1);
  98        }
  99
 100        offset = addr / getpagesize() * sizeof(pfn);
 101
 102        if (fseek(file, offset, SEEK_SET)) {
 103                perror("fseek pagemap");
 104                _exit(1);
 105        }
 106
 107        if (fread(&pfn, sizeof(pfn), 1, file) != 1) {
 108                perror("fread pagemap");
 109                _exit(1);
 110        }
 111
 112        fclose(file);
 113        return pfn;
 114}
 115
 116static uint64_t get_kpageflags(unsigned long pfn)
 117{
 118        uint64_t flags;
 119        FILE *file;
 120
 121        file = fopen("/proc/kpageflags", "r");
 122        if (!file) {
 123                perror("fopen kpageflags");
 124                _exit(1);
 125        }
 126
 127        if (fseek(file, pfn * sizeof(flags), SEEK_SET)) {
 128                perror("fseek kpageflags");
 129                _exit(1);
 130        }
 131
 132        if (fread(&flags, sizeof(flags), 1, file) != 1) {
 133                perror("fread kpageflags");
 134                _exit(1);
 135        }
 136
 137        fclose(file);
 138        return flags;
 139}
 140
 141static FILE *seek_to_smaps_entry(unsigned long addr)
 142{
 143        FILE *file;
 144        char *line = NULL;
 145        size_t size = 0;
 146        unsigned long start, end;
 147        char perms[5];
 148        unsigned long offset;
 149        char dev[32];
 150        unsigned long inode;
 151        char path[BUFSIZ];
 152
 153        file = fopen("/proc/self/smaps", "r");
 154        if (!file) {
 155                perror("fopen smaps");
 156                _exit(1);
 157        }
 158
 159        while (getline(&line, &size, file) > 0) {
 160                if (sscanf(line, "%lx-%lx %s %lx %s %lu %s\n",
 161                           &start, &end, perms, &offset, dev, &inode, path) < 6)
 162                        goto next;
 163
 164                if (start <= addr && addr < end)
 165                        goto out;
 166
 167next:
 168                free(line);
 169                line = NULL;
 170                size = 0;
 171        }
 172
 173        fclose(file);
 174        file = NULL;
 175
 176out:
 177        free(line);
 178        return file;
 179}
 180
 181#define VMFLAGS "VmFlags:"
 182
 183static bool is_vmflag_set(unsigned long addr, const char *vmflag)
 184{
 185        char *line = NULL;
 186        char *flags;
 187        size_t size = 0;
 188        bool ret = false;
 189        FILE *smaps;
 190
 191        smaps = seek_to_smaps_entry(addr);
 192        if (!smaps) {
 193                printf("Unable to parse /proc/self/smaps\n");
 194                goto out;
 195        }
 196
 197        while (getline(&line, &size, smaps) > 0) {
 198                if (!strstr(line, VMFLAGS)) {
 199                        free(line);
 200                        line = NULL;
 201                        size = 0;
 202                        continue;
 203                }
 204
 205                flags = line + strlen(VMFLAGS);
 206                ret = (strstr(flags, vmflag) != NULL);
 207                goto out;
 208        }
 209
 210out:
 211        free(line);
 212        fclose(smaps);
 213        return ret;
 214}
 215
 216#define SIZE "Size:"
 217#define RSS  "Rss:"
 218#define LOCKED "lo"
 219
 220static bool is_vma_lock_on_fault(unsigned long addr)
 221{
 222        bool ret = false;
 223        bool locked;
 224        FILE *smaps = NULL;
 225        unsigned long vma_size, vma_rss;
 226        char *line = NULL;
 227        char *value;
 228        size_t size = 0;
 229
 230        locked = is_vmflag_set(addr, LOCKED);
 231        if (!locked)
 232                goto out;
 233
 234        smaps = seek_to_smaps_entry(addr);
 235        if (!smaps) {
 236                printf("Unable to parse /proc/self/smaps\n");
 237                goto out;
 238        }
 239
 240        while (getline(&line, &size, smaps) > 0) {
 241                if (!strstr(line, SIZE)) {
 242                        free(line);
 243                        line = NULL;
 244                        size = 0;
 245                        continue;
 246                }
 247
 248                value = line + strlen(SIZE);
 249                if (sscanf(value, "%lu kB", &vma_size) < 1) {
 250                        printf("Unable to parse smaps entry for Size\n");
 251                        goto out;
 252                }
 253                break;
 254        }
 255
 256        while (getline(&line, &size, smaps) > 0) {
 257                if (!strstr(line, RSS)) {
 258                        free(line);
 259                        line = NULL;
 260                        size = 0;
 261                        continue;
 262                }
 263
 264                value = line + strlen(RSS);
 265                if (sscanf(value, "%lu kB", &vma_rss) < 1) {
 266                        printf("Unable to parse smaps entry for Rss\n");
 267                        goto out;
 268                }
 269                break;
 270        }
 271
 272        ret = locked && (vma_rss < vma_size);
 273out:
 274        free(line);
 275        if (smaps)
 276                fclose(smaps);
 277        return ret;
 278}
 279
 280#define PRESENT_BIT     0x8000000000000000ULL
 281#define PFN_MASK        0x007FFFFFFFFFFFFFULL
 282#define UNEVICTABLE_BIT (1UL << 18)
 283
 284static int lock_check(char *map)
 285{
 286        unsigned long page_size = getpagesize();
 287        uint64_t page1_flags, page2_flags;
 288
 289        page1_flags = get_pageflags((unsigned long)map);
 290        page2_flags = get_pageflags((unsigned long)map + page_size);
 291
 292        /* Both pages should be present */
 293        if (((page1_flags & PRESENT_BIT) == 0) ||
 294            ((page2_flags & PRESENT_BIT) == 0)) {
 295                printf("Failed to make both pages present\n");
 296                return 1;
 297        }
 298
 299        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 300        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 301
 302        /* Both pages should be unevictable */
 303        if (((page1_flags & UNEVICTABLE_BIT) == 0) ||
 304            ((page2_flags & UNEVICTABLE_BIT) == 0)) {
 305                printf("Failed to make both pages unevictable\n");
 306                return 1;
 307        }
 308
 309        if (!is_vmflag_set((unsigned long)map, LOCKED)) {
 310                printf("VMA flag %s is missing on page 1\n", LOCKED);
 311                return 1;
 312        }
 313
 314        if (!is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
 315                printf("VMA flag %s is missing on page 2\n", LOCKED);
 316                return 1;
 317        }
 318
 319        return 0;
 320}
 321
 322static int unlock_lock_check(char *map)
 323{
 324        unsigned long page_size = getpagesize();
 325        uint64_t page1_flags, page2_flags;
 326
 327        page1_flags = get_pageflags((unsigned long)map);
 328        page2_flags = get_pageflags((unsigned long)map + page_size);
 329        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 330        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 331
 332        if ((page1_flags & UNEVICTABLE_BIT) || (page2_flags & UNEVICTABLE_BIT)) {
 333                printf("A page is still marked unevictable after unlock\n");
 334                return 1;
 335        }
 336
 337        if (is_vmflag_set((unsigned long)map, LOCKED)) {
 338                printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
 339                return 1;
 340        }
 341
 342        if (is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
 343                printf("VMA flag %s is present on page 2 after unlock\n", LOCKED);
 344                return 1;
 345        }
 346
 347        return 0;
 348}
 349
 350static int test_mlock_lock()
 351{
 352        char *map;
 353        int ret = 1;
 354        unsigned long page_size = getpagesize();
 355
 356        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 357                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 358        if (map == MAP_FAILED) {
 359                perror("test_mlock_locked mmap");
 360                goto out;
 361        }
 362
 363        if (mlock2_(map, 2 * page_size, 0)) {
 364                if (errno == ENOSYS) {
 365                        printf("Cannot call new mlock family, skipping test\n");
 366                        _exit(0);
 367                }
 368                perror("mlock2(0)");
 369                goto unmap;
 370        }
 371
 372        if (lock_check(map))
 373                goto unmap;
 374
 375        /* Now unlock and recheck attributes */
 376        if (munlock(map, 2 * page_size)) {
 377                perror("munlock()");
 378                goto unmap;
 379        }
 380
 381        ret = unlock_lock_check(map);
 382
 383unmap:
 384        munmap(map, 2 * page_size);
 385out:
 386        return ret;
 387}
 388
 389static int onfault_check(char *map)
 390{
 391        unsigned long page_size = getpagesize();
 392        uint64_t page1_flags, page2_flags;
 393
 394        page1_flags = get_pageflags((unsigned long)map);
 395        page2_flags = get_pageflags((unsigned long)map + page_size);
 396
 397        /* Neither page should be present */
 398        if ((page1_flags & PRESENT_BIT) || (page2_flags & PRESENT_BIT)) {
 399                printf("Pages were made present by MLOCK_ONFAULT\n");
 400                return 1;
 401        }
 402
 403        *map = 'a';
 404        page1_flags = get_pageflags((unsigned long)map);
 405        page2_flags = get_pageflags((unsigned long)map + page_size);
 406
 407        /* Only page 1 should be present */
 408        if ((page1_flags & PRESENT_BIT) == 0) {
 409                printf("Page 1 is not present after fault\n");
 410                return 1;
 411        } else if (page2_flags & PRESENT_BIT) {
 412                printf("Page 2 was made present\n");
 413                return 1;
 414        }
 415
 416        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 417
 418        /* Page 1 should be unevictable */
 419        if ((page1_flags & UNEVICTABLE_BIT) == 0) {
 420                printf("Failed to make faulted page unevictable\n");
 421                return 1;
 422        }
 423
 424        if (!is_vma_lock_on_fault((unsigned long)map)) {
 425                printf("VMA is not marked for lock on fault\n");
 426                return 1;
 427        }
 428
 429        if (!is_vma_lock_on_fault((unsigned long)map + page_size)) {
 430                printf("VMA is not marked for lock on fault\n");
 431                return 1;
 432        }
 433
 434        return 0;
 435}
 436
 437static int unlock_onfault_check(char *map)
 438{
 439        unsigned long page_size = getpagesize();
 440        uint64_t page1_flags;
 441
 442        page1_flags = get_pageflags((unsigned long)map);
 443        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 444
 445        if (page1_flags & UNEVICTABLE_BIT) {
 446                printf("Page 1 is still marked unevictable after unlock\n");
 447                return 1;
 448        }
 449
 450        if (is_vma_lock_on_fault((unsigned long)map) ||
 451            is_vma_lock_on_fault((unsigned long)map + page_size)) {
 452                printf("VMA is still lock on fault after unlock\n");
 453                return 1;
 454        }
 455
 456        return 0;
 457}
 458
 459static int test_mlock_onfault()
 460{
 461        char *map;
 462        int ret = 1;
 463        unsigned long page_size = getpagesize();
 464
 465        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 466                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 467        if (map == MAP_FAILED) {
 468                perror("test_mlock_locked mmap");
 469                goto out;
 470        }
 471
 472        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 473                if (errno == ENOSYS) {
 474                        printf("Cannot call new mlock family, skipping test\n");
 475                        _exit(0);
 476                }
 477                perror("mlock2(MLOCK_ONFAULT)");
 478                goto unmap;
 479        }
 480
 481        if (onfault_check(map))
 482                goto unmap;
 483
 484        /* Now unlock and recheck attributes */
 485        if (munlock(map, 2 * page_size)) {
 486                if (errno == ENOSYS) {
 487                        printf("Cannot call new mlock family, skipping test\n");
 488                        _exit(0);
 489                }
 490                perror("munlock()");
 491                goto unmap;
 492        }
 493
 494        ret = unlock_onfault_check(map);
 495unmap:
 496        munmap(map, 2 * page_size);
 497out:
 498        return ret;
 499}
 500
 501static int test_lock_onfault_of_present()
 502{
 503        char *map;
 504        int ret = 1;
 505        unsigned long page_size = getpagesize();
 506        uint64_t page1_flags, page2_flags;
 507
 508        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 509                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 510        if (map == MAP_FAILED) {
 511                perror("test_mlock_locked mmap");
 512                goto out;
 513        }
 514
 515        *map = 'a';
 516
 517        if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
 518                if (errno == ENOSYS) {
 519                        printf("Cannot call new mlock family, skipping test\n");
 520                        _exit(0);
 521                }
 522                perror("mlock2(MLOCK_ONFAULT)");
 523                goto unmap;
 524        }
 525
 526        page1_flags = get_pageflags((unsigned long)map);
 527        page2_flags = get_pageflags((unsigned long)map + page_size);
 528        page1_flags = get_kpageflags(page1_flags & PFN_MASK);
 529        page2_flags = get_kpageflags(page2_flags & PFN_MASK);
 530
 531        /* Page 1 should be unevictable */
 532        if ((page1_flags & UNEVICTABLE_BIT) == 0) {
 533                printf("Failed to make present page unevictable\n");
 534                goto unmap;
 535        }
 536
 537        if (!is_vma_lock_on_fault((unsigned long)map) ||
 538            !is_vma_lock_on_fault((unsigned long)map + page_size)) {
 539                printf("VMA with present pages is not marked lock on fault\n");
 540                goto unmap;
 541        }
 542        ret = 0;
 543unmap:
 544        munmap(map, 2 * page_size);
 545out:
 546        return ret;
 547}
 548
 549static int test_munlockall()
 550{
 551        char *map;
 552        int ret = 1;
 553        unsigned long page_size = getpagesize();
 554
 555        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 556                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 557
 558        if (map == MAP_FAILED) {
 559                perror("test_munlockall mmap");
 560                goto out;
 561        }
 562
 563        if (mlockall(MCL_CURRENT)) {
 564                perror("mlockall(MCL_CURRENT)");
 565                goto out;
 566        }
 567
 568        if (lock_check(map))
 569                goto unmap;
 570
 571        if (munlockall()) {
 572                perror("munlockall()");
 573                goto unmap;
 574        }
 575
 576        if (unlock_lock_check(map))
 577                goto unmap;
 578
 579        munmap(map, 2 * page_size);
 580
 581        map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
 582                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 583
 584        if (map == MAP_FAILED) {
 585                perror("test_munlockall second mmap");
 586                goto out;
 587        }
 588
 589        if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
 590                perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
 591                goto unmap;
 592        }
 593
 594        if (onfault_check(map))
 595                goto unmap;
 596
 597        if (munlockall()) {
 598                perror("munlockall()");
 599                goto unmap;
 600        }
 601
 602        if (unlock_onfault_check(map))
 603                goto unmap;
 604
 605        if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
 606                perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
 607                goto out;
 608        }
 609
 610        if (lock_check(map))
 611                goto unmap;
 612
 613        if (munlockall()) {
 614                perror("munlockall()");
 615                goto unmap;
 616        }
 617
 618        ret = unlock_lock_check(map);
 619
 620unmap:
 621        munmap(map, 2 * page_size);
 622out:
 623        munlockall();
 624        return ret;
 625}
 626
 627static int test_vma_management(bool call_mlock)
 628{
 629        int ret = 1;
 630        void *map;
 631        unsigned long page_size = getpagesize();
 632        struct vm_boundaries page1;
 633        struct vm_boundaries page2;
 634        struct vm_boundaries page3;
 635
 636        map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
 637                   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 638        if (map == MAP_FAILED) {
 639                perror("mmap()");
 640                return ret;
 641        }
 642
 643        if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
 644                if (errno == ENOSYS) {
 645                        printf("Cannot call new mlock family, skipping test\n");
 646                        _exit(0);
 647                }
 648                perror("mlock(ONFAULT)\n");
 649                goto out;
 650        }
 651
 652        if (get_vm_area((unsigned long)map, &page1) ||
 653            get_vm_area((unsigned long)map + page_size, &page2) ||
 654            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 655                printf("couldn't find mapping in /proc/self/maps\n");
 656                goto out;
 657        }
 658
 659        /*
 660         * Before we unlock a portion, we need to that all three pages are in
 661         * the same VMA.  If they are not we abort this test (Note that this is
 662         * not a failure)
 663         */
 664        if (page1.start != page2.start || page2.start != page3.start) {
 665                printf("VMAs are not merged to start, aborting test\n");
 666                ret = 0;
 667                goto out;
 668        }
 669
 670        if (munlock(map + page_size, page_size)) {
 671                perror("munlock()");
 672                goto out;
 673        }
 674
 675        if (get_vm_area((unsigned long)map, &page1) ||
 676            get_vm_area((unsigned long)map + page_size, &page2) ||
 677            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 678                printf("couldn't find mapping in /proc/self/maps\n");
 679                goto out;
 680        }
 681
 682        /* All three VMAs should be different */
 683        if (page1.start == page2.start || page2.start == page3.start) {
 684                printf("failed to split VMA for munlock\n");
 685                goto out;
 686        }
 687
 688        /* Now unlock the first and third page and check the VMAs again */
 689        if (munlock(map, page_size * 3)) {
 690                perror("munlock()");
 691                goto out;
 692        }
 693
 694        if (get_vm_area((unsigned long)map, &page1) ||
 695            get_vm_area((unsigned long)map + page_size, &page2) ||
 696            get_vm_area((unsigned long)map + page_size * 2, &page3)) {
 697                printf("couldn't find mapping in /proc/self/maps\n");
 698                goto out;
 699        }
 700
 701        /* Now all three VMAs should be the same */
 702        if (page1.start != page2.start || page2.start != page3.start) {
 703                printf("failed to merge VMAs after munlock\n");
 704                goto out;
 705        }
 706
 707        ret = 0;
 708out:
 709        munmap(map, 3 * page_size);
 710        return ret;
 711}
 712
 713static int test_mlockall(int (test_function)(bool call_mlock))
 714{
 715        int ret = 1;
 716
 717        if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
 718                perror("mlockall");
 719                return ret;
 720        }
 721
 722        ret = test_function(false);
 723        munlockall();
 724        return ret;
 725}
 726
 727int main(int argc, char **argv)
 728{
 729        int ret = 0;
 730        ret += test_mlock_lock();
 731        ret += test_mlock_onfault();
 732        ret += test_munlockall();
 733        ret += test_lock_onfault_of_present();
 734        ret += test_vma_management(true);
 735        ret += test_mlockall(test_vma_management);
 736        return ret;
 737}
 738