1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * A test case that must run on a system with one and only one huge page available. 4 * # echo 1 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 5 * 6 * During setup, the test allocates the only available page, and starts three threads: 7 * - thread1: 8 * * madvise(MADV_DONTNEED) on the allocated huge page 9 * - thread 2: 10 * * Write to the allocated huge page 11 * - thread 3: 12 * * Try to allocated an extra huge page (which must not available) 13 * 14 * The test fails if thread3 is able to allocate a page. 15 * 16 * Touching the first page after thread3's allocation will raise a SIGBUS 17 * 18 * Author: Breno Leitao <leitao@debian.org> 19 */ 20 #include <pthread.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <sys/mman.h> 24 #include <sys/types.h> 25 #include <unistd.h> 26 27 #include "vm_util.h" 28 #include "hugepage_settings.h" 29 30 #define INLOOP_ITER 100 31 32 size_t mmap_size; 33 char *huge_ptr; 34 35 /* Touch the memory while it is being madvised() */ 36 void *touch(void *unused) 37 { 38 for (int i = 0; i < INLOOP_ITER; i++) 39 huge_ptr[0] = '.'; 40 41 return NULL; 42 } 43 44 void *madv(void *unused) 45 { 46 for (int i = 0; i < INLOOP_ITER; i++) 47 madvise(huge_ptr, mmap_size, MADV_DONTNEED); 48 49 return NULL; 50 } 51 52 /* 53 * We got here, and there must be no huge page available for mapping 54 * The other hugepage should be flipping from used <-> reserved, because 55 * of madvise(DONTNEED). 56 */ 57 void *map_extra(void *unused) 58 { 59 void *ptr; 60 61 for (int i = 0; i < INLOOP_ITER; i++) { 62 ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, 63 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, 64 -1, 0); 65 66 if ((long)ptr != -1) { 67 /* Touching the other page now will cause a SIGBUG 68 * huge_ptr[0] = '1'; 69 */ 70 return ptr; 71 } 72 } 73 74 return NULL; 75 } 76 77 int main(void) 78 { 79 pthread_t thread1, thread2, thread3; 80 void *ret; 81 82 /* 83 * On kernel 6.7, we are able to reproduce the problem with ~10 84 * interactions 85 */ 86 int max = 10; 87 88 ksft_print_header(); 89 ksft_set_plan(1); 90 91 if (!hugetlb_setup_default_exact(1)) 92 ksft_exit_skip("This test needs one and only one page to execute. Got %lu\n", 93 hugetlb_free_default_pages()); 94 95 mmap_size = default_huge_page_size(); 96 97 while (max--) { 98 huge_ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, 99 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, 100 -1, 0); 101 102 if ((unsigned long)huge_ptr == -1) 103 ksft_exit_fail_msg("Failed to allocate huge page\n"); 104 105 pthread_create(&thread1, NULL, madv, NULL); 106 pthread_create(&thread2, NULL, touch, NULL); 107 pthread_create(&thread3, NULL, map_extra, NULL); 108 109 pthread_join(thread1, NULL); 110 pthread_join(thread2, NULL); 111 pthread_join(thread3, &ret); 112 113 if (ret) { 114 ksft_test_result_fail("Unexpected huge page allocation\n"); 115 ksft_finished(); 116 } 117 118 /* Unmap and restart */ 119 munmap(huge_ptr, mmap_size); 120 } 121 122 ksft_test_result_pass("No unexpected huge page allocations\n"); 123 ksft_finished(); 124 } 125