// SPDX-License-Identifier: GPL-2.0 /* * A test case that must run on a system with one and only one huge page available. * # echo 1 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages * * During setup, the test allocates the only available page, and starts three threads: * - thread1: * * madvise(MADV_DONTNEED) on the allocated huge page * - thread 2: * * Write to the allocated huge page * - thread 3: * * Try to allocated an extra huge page (which must not available) * * The test fails if thread3 is able to allocate a page. * * Touching the first page after thread3's allocation will raise a SIGBUS * * Author: Breno Leitao */ #include #include #include #include #include #include #include "vm_util.h" #include "hugepage_settings.h" #define INLOOP_ITER 100 size_t mmap_size; char *huge_ptr; /* Touch the memory while it is being madvised() */ void *touch(void *unused) { for (int i = 0; i < INLOOP_ITER; i++) huge_ptr[0] = '.'; return NULL; } void *madv(void *unused) { for (int i = 0; i < INLOOP_ITER; i++) madvise(huge_ptr, mmap_size, MADV_DONTNEED); return NULL; } /* * We got here, and there must be no huge page available for mapping * The other hugepage should be flipping from used <-> reserved, because * of madvise(DONTNEED). */ void *map_extra(void *unused) { void *ptr; for (int i = 0; i < INLOOP_ITER; i++) { ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); if ((long)ptr != -1) { /* Touching the other page now will cause a SIGBUG * huge_ptr[0] = '1'; */ return ptr; } } return NULL; } int main(void) { pthread_t thread1, thread2, thread3; void *ret; /* * On kernel 6.7, we are able to reproduce the problem with ~10 * interactions */ int max = 10; ksft_print_header(); ksft_set_plan(1); if (!hugetlb_setup_default_exact(1)) ksft_exit_skip("This test needs one and only one page to execute. Got %lu\n", hugetlb_free_default_pages()); mmap_size = default_huge_page_size(); while (max--) { huge_ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); if ((unsigned long)huge_ptr == -1) ksft_exit_fail_msg("Failed to allocate huge page\n"); pthread_create(&thread1, NULL, madv, NULL); pthread_create(&thread2, NULL, touch, NULL); pthread_create(&thread3, NULL, map_extra, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, &ret); if (ret) { ksft_test_result_fail("Unexpected huge page allocation\n"); ksft_finished(); } /* Unmap and restart */ munmap(huge_ptr, mmap_size); } ksft_test_result_pass("No unexpected huge page allocations\n"); ksft_finished(); }