| 1 | /* @title: Page Zeroer Subsystem */ |
| 2 | #pragma once |
| 3 | #include <math/ewma.h> |
| 4 | #include <math/fixed.h> |
| 5 | #include <mem/buddy.h> |
| 6 | #include <thread/daemon.h> |
| 7 | #include <thread/workqueue.h> |
| 8 | #include <types/types.h> |
| 9 | |
| 10 | /* Notes on the page zeroer architecture: |
| 11 | * |
| 12 | * We have a structure called `daemon`, in `daemon.h`, and our |
| 13 | * page zeroer mechanism is essentially built off of how that works. |
| 14 | * |
| 15 | * The basic idea of `daemon`: you have 1 background thread and N |
| 16 | * timesharing threads, and ONE background work and ONE timesharing work |
| 17 | * that you can wake the threads to go and perform |
| 18 | * |
| 19 | * For the more low level aspects: we make one page zeroer per domain, |
| 20 | * with one timesharing thread per 4 (TODO: subject to change) CPUs in |
| 21 | * that domain. The idea is that pages are marked as zeroed at the buddy_page |
| 22 | * level, and "zero pages push at front, non-zero push at tail" for buddy |
| 23 | * allocator freelists. |
| 24 | * |
| 25 | * During coalescing, the allocator will need to check the full block to |
| 26 | * determine if THE WHOLE THING is zeroed out, otherwise it will bail |
| 27 | * |
| 28 | * TODO: I'm thinking we could perhaps perform that part smarter? maybe |
| 29 | * we can have a system where the zero pages at order > 0 are sorted, |
| 30 | * but that would probably call for major expansion in struct page |
| 31 | * |
| 32 | * Anywho, we have a structure (TODO:) that manages the quotas |
| 33 | * for the page zeroer to meet. This likely won't be global, |
| 34 | * but I can try having some degree of a global structure on |
| 35 | * a "summary" of the per-domain page zeroers. |
| 36 | * |
| 37 | * The background thread performs aging on this global structure, |
| 38 | * as it runs whenever all other threads on that CPU are zzzzzz, |
| 39 | * and it can also kick awake the timesharing threads if needed |
| 40 | * |
| 41 | * But the idea is that upon interacting with the page zeroer |
| 42 | * interfaces, the quotas will be checked and the relevant |
| 43 | * timesharing threads will be booted awake to do some work |
| 44 | * |
| 45 | * There is also a path in which the page zeroer can be instructed |
| 46 | * to perform a specific task, and this involves things like the slowpath |
| 47 | * in a zero alloc'd hugepage, in which case the timesharing threads |
| 48 | * are instructed to zero out that hugepage |
| 49 | * |
| 50 | */ |
| 51 | |
| 52 | struct page_zeroer_demand { |
| 53 | struct ewma rate; /* consumed blocks per epoch */ |
| 54 | struct ewma mad; /* mean abs deviation of `rate` */ |
| 55 | atomic_size_t consumed_epoch; /* fast-path inc, bg drain */ |
| 56 | atomic_size_t stockout_epoch; /* sync-zero fallbacks */ |
| 57 | }; |
| 58 | |
| 59 | struct page_zeroer_rate { |
| 60 | size_t batch_blocks; /* blocks TS thread zeros per wake */ |
| 61 | fx32_32_t safety_factor; /* multiplier on demand for min_blocks */ |
| 62 | fx32_32_t integral; /* PI integral state */ |
| 63 | fx32_32_t idle_scale; /* [0, 1] until scheduler idle features come */ |
| 64 | }; |
| 65 | |
| 66 | struct page_zeroer_watermark { |
| 67 | size_t min_min_blocks; |
| 68 | size_t max_max_blocks; |
| 69 | |
| 70 | size_t min_blocks; /* These are dynamic */ |
| 71 | size_t max_blocks; |
| 72 | }; |
| 73 | |
| 74 | /* TODO: once we get scheduler idleness integration, add it here */ |
| 75 | struct page_zeroer_quota { |
| 76 | struct page_zeroer_watermark wm; |
| 77 | struct page_zeroer_rate rate; |
| 78 | struct page_zeroer_demand demand; |
| 79 | time_t zero_block_time_ns; |
| 80 | atomic_size_t num_zero_blocks; |
| 81 | }; |
| 82 | |
| 83 | struct page_zeroer { |
| 84 | struct page_zeroer_quota quotas[BUDDY_MAX_ORDER]; |
| 85 | struct daemon *daemon; |
| 86 | }; |
| 87 | |