| 1 | #include "mem/tests/test_internal.h" |
| 2 | |
| 3 | #define DP_PAGES 16 |
| 4 | #define DP_STRIDE (PAGE_SIZE / sizeof(uint64_t)) |
| 5 | #define DP_MAX_BUFS 8 |
| 6 | #define DP_MAX_THREADS 64 |
| 7 | |
| 8 | struct dp_worker { |
| 9 | _Atomic uint64_t **bufs; /* nbuf demand buffers, counter at page head */ |
| 10 | size_t nbuf; |
| 11 | size_t pages; |
| 12 | atomic_uint *done; |
| 13 | }; |
| 14 | |
| 15 | static void dp_hammer(void *arg) { |
| 16 | struct dp_worker *w = arg; |
| 17 | |
| 18 | /* touch every page of every buffer */ |
| 19 | for (size_t b = 0; b < w->nbuf; b++) |
| 20 | for (size_t p = 0; p < w->pages; p++) |
| 21 | atomic_fetch_add_explicit(&w->bufs[b][p * DP_STRIDE], 1, |
| 22 | memory_order_relaxed); |
| 23 | |
| 24 | atomic_fetch_add(w->done, 1); |
| 25 | } |
| 26 | |
| 27 | static bool dp_alloc_bufs(_Atomic uint64_t **bufs, size_t nbuf, size_t pages) { |
| 28 | for (size_t b = 0; b < nbuf; b++) { |
| 29 | bufs[b] = page_alloc_demand(pages, ALLOC_FLAGS_ZERO); |
| 30 | if (!bufs[b]) { |
| 31 | for (size_t j = 0; j < b; j++) |
| 32 | page_free((void *) bufs[j], pages); |
| 33 | return false; |
| 34 | } |
| 35 | } |
| 36 | return true; |
| 37 | } |
| 38 | |
| 39 | /* every page was faulted in by workers, so all frames are present */ |
| 40 | static void dp_free_bufs(_Atomic uint64_t **bufs, size_t nbuf, size_t pages) { |
| 41 | for (size_t b = 0; b < nbuf; b++) |
| 42 | page_free((void *) bufs[b], pages); |
| 43 | } |
| 44 | |
| 45 | static bool dp_verify(_Atomic uint64_t **bufs, size_t nbuf, size_t pages, |
| 46 | uint64_t expect) { |
| 47 | for (size_t b = 0; b < nbuf; b++) |
| 48 | for (size_t p = 0; p < pages; p++) |
| 49 | if (atomic_load(&bufs[b][p * DP_STRIDE]) != expect) |
| 50 | return false; |
| 51 | |
| 52 | return true; |
| 53 | } |
| 54 | |
| 55 | /* Spawn nthreads workers over shared buffer set. single_core pins them, |
| 56 | * and we can test all on one CPU vs spread out */ |
| 57 | static void dp_spawn(struct thread **t, size_t nthreads, struct dp_worker *w, |
| 58 | bool single_core) { |
| 59 | for (size_t i = 0; i < nthreads; i++) { |
| 60 | uint64_t core = single_core ? 0 : (i % global.core_count); |
| 61 | /* Join reference is what makes thread_pin safe, worker may |
| 62 | * have already exited by then */ |
| 63 | t[i] = kassert( |
| 64 | thread_spawn_joinable_on_core("dp_hammer" , dp_hammer, w, core)); |
| 65 | if (single_core) |
| 66 | thread_pin(t: t[i]); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | static void dp_join(struct thread **t, size_t nthreads) { |
| 71 | for (size_t i = 0; i < nthreads; i++) |
| 72 | thread_join(t: t[i]); |
| 73 | } |
| 74 | |
| 75 | /* 1 buffer, N threads, 1 CPU = serialized faults + preemption mid-handler */ |
| 76 | TEST_DECLARE_UNIT(mem, demand_single_buf_up, TEST_INTENSITY(2, 8, 32)) { |
| 77 | ABORT_IF_RAM_LOW(); |
| 78 | |
| 79 | size_t nthreads = ctx->intensity_val ? ctx->intensity_val : 8; |
| 80 | if (nthreads > DP_MAX_THREADS) |
| 81 | nthreads = DP_MAX_THREADS; |
| 82 | const size_t pages = DP_PAGES, nbuf = 1; |
| 83 | _Atomic uint64_t *bufs[1]; |
| 84 | TEST_ASSERT(dp_alloc_bufs(bufs, nbuf, pages)); |
| 85 | |
| 86 | atomic_uint done = 0; |
| 87 | struct dp_worker w = {bufs, nbuf, pages, &done}; |
| 88 | struct thread *t[DP_MAX_THREADS]; |
| 89 | dp_spawn(t, nthreads, w: &w, /*single_core=*/true); |
| 90 | |
| 91 | dp_join(t, nthreads); |
| 92 | |
| 93 | TEST_ASSERT_EQ(atomic_load(&done), nthreads); |
| 94 | TEST_ASSERT(dp_verify(bufs, nbuf, pages, nthreads)); |
| 95 | dp_free_bufs(bufs, nbuf, pages); |
| 96 | return TEST_SUCCESS; |
| 97 | } |
| 98 | |