Skip to main content

SummaryBuddyAllocator

Struct SummaryBuddyAllocator 

Source
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>

Source

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?
examples/basic.rs (line 89)
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}
Source

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>

Source§

fn allocate_physical( &self, ps: PageSize, count: NonZeroUsize, ) -> Result<usize, AllocError>

Allocate count contiguous frames of size ps. Read more
Source§

unsafe fn deallocate_physical( &self, ps: PageSize, count: NonZeroUsize, phys: usize, )

Return count contiguous frames of size ps starting at phys. Read more
Source§

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>

Configure metadata over [phys_base, phys_base + span_len) and mark only the usable ranges free. Holes stay reserved. Read more
Source§

unsafe fn add_usable(&self, base: usize, len: usize)

Transition a reserved (never-freed) in-span range to free. Repeatable after try_init. base/len must satisfy the same per-range constraints as a usable entry. Read more
Source§

unsafe fn init(&self, phys_base: usize, span_len: usize, usable: &[PhysRange])

As try_init but panic on error. Read more
Source§

unsafe fn try_init_region( &self, phys_base: usize, len: usize, ) -> Result<(), InitError>

Convenience: initialise from a single fully-usable contiguous region [phys_base, phys_base + len). Read more
Source§

unsafe fn init_region(&self, phys_base: usize, len: usize)

As try_init_region but panic on error. Read more

Auto Trait Implementations§

§

impl<const ORDERS: usize, P, G = DefaultGates> !Freeze for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> RefUnwindSafe for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> Send for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> Sync for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> Unpin for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> UnsafeUnpin for SummaryBuddyAllocator<ORDERS, P, G>

§

impl<const ORDERS: usize, P, G> UnwindSafe for SummaryBuddyAllocator<ORDERS, P, G>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.