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 "../kselftest.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 unsigned long free_hugepages; 81 void *ret; 82 83 /* 84 * On kernel 6.7, we are able to reproduce the problem with ~10 85 * interactions 86 */ 87 int max = 10; 88 89 free_hugepages = get_free_hugepages(); 90 91 if (free_hugepages != 1) { 92 ksft_exit_skip("This test needs one and only one page to execute. Got %lu\n", 93 free_hugepages); 94 } 95 96 mmap_size = default_huge_page_size(); 97 98 while (max--) { 99 huge_ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, 100 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, 101 -1, 0); 102 103 if ((unsigned long)huge_ptr == -1) { 104 ksft_test_result_fail("Failed to allocate huge page\n"); 105 return KSFT_FAIL; 106 } 107 108 pthread_create(&thread1, NULL, madv, NULL); 109 pthread_create(&thread2, NULL, touch, NULL); 110 pthread_create(&thread3, NULL, map_extra, NULL); 111 112 pthread_join(thread1, NULL); 113 pthread_join(thread2, NULL); 114 pthread_join(thread3, &ret); 115 116 if (ret) { 117 ksft_test_result_fail("Unexpected huge page allocation\n"); 118 return KSFT_FAIL; 119 } 120 121 /* Unmap and restart */ 122 munmap(huge_ptr, mmap_size); 123 } 124 125 return KSFT_PASS; 126 } 127