pub struct SummaryBuddyAllocator<const ORDERS: usize, P: Provenance, G: GateConfig = DefaultGates> { /* private fields */ }Expand description
A lock-free, bitmap-only buddy allocator with a two-level summary bitmap that carves its bitmap out of the managed physical memory region at init time.
ORDERS is the number of size classes: order 0 corresponds to the base
frame size (base.bytes()), order ORDERS - 1 to the largest block
(base.bytes() << (ORDERS - 1)).
P is the Provenance strategy. Unlike the intrusive backends, this
allocator only touches the managed memory at init - to obtain a pointer to the
carved bitmap - and never reaches into the frames it hands out; so P::create
is called exactly once, for the bitmap, and P::destroy is never needed (the
bitmap is permanent).
Implementations§
Source§impl<const ORDERS: usize, P: Provenance, G: GateConfig> SummaryBuddyAllocator<ORDERS, P, G>
impl<const ORDERS: usize, P: Provenance, G: GateConfig> SummaryBuddyAllocator<ORDERS, P, G>
Sourcepub const fn new(base_frame: PageSize) -> Self
pub const fn new(base_frame: PageSize) -> Self
Create a new, empty allocator with the given base frame size.
All runtime state is zeroed. Call init / init_region to finish
initialisation before allocating. max_page defaults to the max block
(base_frame << (ORDERS-1)), the most conservative choice; use
with_max_page to shrink the boundary init rounds
phys_base down to (a smaller max_page shrinks the phantom prefix).
Examples found in repository?
89static PHYS: SummaryBuddyAllocator<ORDERS, IdentityProv> = SummaryBuddyAllocator::new(BASE);
90
91fn main() {
92 // A boot memory map with holes:
93 //
94 // The backing pool spans the whole window; we then declare only the *usable*
95 // sub-ranges. Two holes stay reserved and are never handed out:
96 //
97 // [ 0 .. 100) usable <- the buddy carves its in-pool bitmap from here
98 // [100 .. 104) RESERVED (kernel image)
99 // [104 .. 200) usable
100 // [200 .. 205) RESERVED (MMIO window)
101 // [205 .. 256) usable
102 let pool = Region::new(SPAN_FRAMES * BASE.bytes(), MAX_BLOCK);
103 let phys_base = pool.addr(); // the "physical" origin
104
105 let frame = BASE.bytes();
106 let range = |lo: usize, hi: usize| PhysRange {
107 base: phys_base + lo * frame,
108 len: (hi - lo) * frame,
109 };
110 let usable = [range(0, 100), range(104, 200), range(205, 256)];
111
112 // SAFETY: single-threaded, called once before any allocation. `phys_base` is
113 // MAX_BLOCK-aligned (Region honours the requested alignment); the bitmap host
114 // range is exclusively owned and reachable through `IdentityProv::create`; the
115 // usable ranges are sorted, non-overlapping, base-frame-aligned, and within
116 // the span.
117 unsafe { PHYS.init(phys_base, SPAN_FRAMES * frame, &usable) };
118
119 println!("Initialised SummaryBuddy over a 1 MiB span with two reserved holes.");
120 print_stats("after init", &PHYS);
121
122 // A few allocate / deallocate cycles:
123 //
124 // One single base frame, then a 4-frame (order-2) contiguous block. Every
125 // address is physical and aligned to the request size.
126 let single = PHYS
127 .allocate_physical(BASE, n(1))
128 .expect("single-frame alloc");
129 let block = PHYS
130 .allocate_physical(BASE, n(4))
131 .expect("4-frame contiguous alloc");
132 println!("\nallocate_physical(1 frame) -> {single:#x}");
133 println!("allocate_physical(4 frames) -> {block:#x}");
134 print_stats("with 5 frames out", &PHYS);
135
136 // SAFETY: each address came from `allocate_physical` with the same page size
137 // and count and is not used afterwards.
138 unsafe {
139 PHYS.deallocate_physical(BASE, n(1), single);
140 PHYS.deallocate_physical(BASE, n(4), block);
141 }
142 println!("\nfreed both — buddies merge back.");
143 print_stats("after free", &PHYS);
144
145 // Composition: a per-CPU magazine + shared depot over a fresh SummaryBuddy:
146 compose_with_depot();
147}
148
149/// Wrap a `SummaryBuddyAllocator` in a [`DepotAllocator`].
150fn compose_with_depot() {
151 /// Uniprocessor selector: every CPU maps to slot 0. A real kernel returns an
152 /// APIC id / `TPIDR_EL1` here.
153 struct OneCpu;
154 impl CpuId for OneCpu {
155 fn current_cpu() -> usize {
156 0
157 }
158 }
159 const SLOTS: usize = 8; // magazines (>= CPUs you want disjoint)
160
161 // Same const-new composability: the whole stack is one `static`-able value.
162 let mag: DepotAllocator<SummaryBuddyAllocator<ORDERS, IdentityProv>, OneCpu, SLOTS> =
163 DepotAllocator::new(BASE, SummaryBuddyAllocator::new(BASE));
164
165 let pool = Region::new(SPAN_FRAMES * BASE.bytes(), MAX_BLOCK);
166 // SAFETY: as above; here the whole region is usable, so the single-range
167 // convenience applies.
168 unsafe { mag.init_region(pool.addr(), SPAN_FRAMES * BASE.bytes()) };
169
170 println!("\n── DepotAllocator<SummaryBuddyAllocator> ──");
171 let a = mag.allocate_physical(BASE, n(1)).expect("mag alloc");
172 // SAFETY: `a` came from this allocator; freed once, then not reused by us.
173 unsafe { mag.deallocate_physical(BASE, n(1), a) };
174 let b = mag.allocate_physical(BASE, n(1)).expect("mag re-alloc");
175 println!("alloc {a:#x} -> free -> alloc {b:#x}");
176 assert_eq!(a, b, "the magazine should return the just-freed frame");
177 println!("re-alloc returned the cached frame (no backend round-trip).");
178 // SAFETY: final free of a live frame.
179 unsafe { mag.deallocate_physical(BASE, n(1), b) };
180}Sourcepub const fn with_max_page(base_frame: PageSize, max_page: PageSize) -> Self
pub const fn with_max_page(base_frame: PageSize, max_page: PageSize) -> Self
Like new but pins max_page, the largest page a caller may
request. It caps allocations (ps > max_page -> InvalidPageSize) and,
equivalently, sets the boundary init rounds phys_base down to - the two
are the same bound. Must lie in base_frame ..= base_frame << (ORDERS-1).
Trait Implementations§
Source§impl<const ORDERS: usize, P: Provenance, G: GateConfig> PhysicalAllocator for SummaryBuddyAllocator<ORDERS, P, G>
impl<const ORDERS: usize, P: Provenance, G: GateConfig> PhysicalAllocator for SummaryBuddyAllocator<ORDERS, P, G>
Source§fn allocate_physical(
&self,
ps: PageSize,
count: NonZeroUsize,
) -> Result<usize, AllocError>
fn allocate_physical( &self, ps: PageSize, count: NonZeroUsize, ) -> Result<usize, AllocError>
Source§unsafe fn deallocate_physical(
&self,
ps: PageSize,
count: NonZeroUsize,
phys: usize,
)
unsafe fn deallocate_physical( &self, ps: PageSize, count: NonZeroUsize, phys: usize, )
Source§impl<const ORDERS: usize, P: Provenance, G: GateConfig> RegionInit for SummaryBuddyAllocator<ORDERS, P, G>
impl<const ORDERS: usize, P: Provenance, G: GateConfig> RegionInit for SummaryBuddyAllocator<ORDERS, P, G>
Source§unsafe fn try_init(
&self,
phys_base: usize,
span_len: usize,
usable: &[PhysRange],
) -> Result<(), InitError>
unsafe fn try_init( &self, phys_base: usize, span_len: usize, usable: &[PhysRange], ) -> Result<(), InitError>
[phys_base, phys_base + span_len) and mark only
the usable ranges free. Holes stay reserved. Read moreSource§unsafe fn add_usable(&self, base: usize, len: usize)
unsafe fn add_usable(&self, base: usize, len: usize)
Source§unsafe fn try_init_region(
&self,
phys_base: usize,
len: usize,
) -> Result<(), InitError>
unsafe fn try_init_region( &self, phys_base: usize, len: usize, ) -> Result<(), InitError>
[phys_base, phys_base + len). Read moreSource§unsafe fn init_region(&self, phys_base: usize, len: usize)
unsafe fn init_region(&self, phys_base: usize, len: usize)
try_init_region but panic on error. Read more