Skip to main content

buddy_system_allocator/
frame.rs

1use super::prev_power_of_two;
2use alloc::collections::BTreeSet;
3use core::alloc::Layout;
4use core::cmp::{max, min};
5use core::ops::Range;
6
7#[cfg(feature = "use_spin")]
8use core::ops::Deref;
9#[cfg(feature = "use_spin")]
10use spin::Mutex;
11
12/// A frame allocator that uses buddy system, requiring a global allocator.
13///
14/// The max order of the allocator is determined by the const generic parameter `ORDER` (`MAX_ORDER = ORDER - 1`).
15/// The frame allocator will only be able to allocate ranges of size up to 2<sup>MAX_ORDER</sup>, out of a total
16/// range of size at most 2<sup>MAX_ORDER + 1</sup> - 1.
17///
18/// # Usage
19///
20/// Create a frame allocator and add some frames to it:
21/// ```
22/// use buddy_system_allocator::*;
23/// // Notice that the max order is `ORDER - 1`.
24/// let mut frame = FrameAllocator::<33>::new();
25/// assert!(frame.alloc(1).is_none());
26///
27/// frame.add_frame(0, 3);
28/// let num = frame.alloc(1);
29/// assert_eq!(num, Some(2));
30/// let num = frame.alloc(2);
31/// assert_eq!(num, Some(0));
32/// ```
33pub struct FrameAllocator<const ORDER: usize = 33> {
34    // buddy system with max order of `ORDER - 1`
35    free_list: [BTreeSet<usize>; ORDER],
36
37    // statistics
38    allocated: usize,
39    total: usize,
40}
41
42impl<const ORDER: usize> FrameAllocator<ORDER> {
43    /// Create an empty frame allocator
44    pub const fn new() -> Self {
45        Self {
46            free_list: [const { BTreeSet::new() }; ORDER],
47            allocated: 0,
48            total: 0,
49        }
50    }
51
52    /// Add a range of frame number [start, end) to the allocator
53    pub fn add_frame(&mut self, start: usize, end: usize) {
54        assert!(start <= end);
55
56        let mut total = 0;
57        let mut current_start = start;
58        let max_block_size = 1 << (ORDER - 1);
59
60        while current_start < end {
61            let lowbit = if current_start > 0 {
62                current_start & (!current_start + 1)
63            } else {
64                max_block_size
65            };
66            let size = min(
67                min(lowbit, prev_power_of_two(end - current_start)),
68                max_block_size,
69            );
70            total += size;
71
72            self.free_list[size.trailing_zeros() as usize].insert(current_start);
73            current_start += size;
74        }
75
76        self.total += total;
77    }
78
79    /// Add a range of frames to the allocator.
80    pub fn insert(&mut self, range: Range<usize>) {
81        self.add_frame(range.start, range.end);
82    }
83
84    /// Allocate a range of frames from the allocator, returning the first frame of the allocated
85    /// range.
86    pub fn alloc(&mut self, count: usize) -> Option<usize> {
87        let size = count.next_power_of_two();
88        self.alloc_power_of_two(size)
89    }
90
91    /// Allocate a range of frames with the given size and alignment from the allocator, returning
92    /// the first frame of the allocated range.
93    /// The allocated size is the maximum of the next power of two of the given size and the
94    /// alignment.
95    pub fn alloc_aligned(&mut self, layout: Layout) -> Option<usize> {
96        let size = max(layout.size().next_power_of_two(), layout.align());
97        self.alloc_power_of_two(size)
98    }
99
100    /// Try to allocate a specific range of frames `[start, start + count)` from the allocator.
101    ///
102    /// The `count` will be rounded up to the next power of two, same as [`alloc`]. The `start`
103    /// address must be aligned to this rounded-up size (buddy system invariant).
104    ///
105    /// Returns `Some(start)` if the range was successfully allocated, or `None` if the range is
106    /// unavailable (not managed, already allocated, or misaligned).
107    pub fn alloc_at(&mut self, start: usize, count: usize) -> Option<usize> {
108        let size = count.next_power_of_two();
109        self.alloc_at_power_of_two(start, size)
110    }
111
112    /// Allocate a range of frames of the given size from the allocator. The size must be a power of
113    /// two. The allocated range will have alignment equal to the size.
114    fn alloc_power_of_two(&mut self, size: usize) -> Option<usize> {
115        let class = size.trailing_zeros() as usize;
116        for i in class..self.free_list.len() {
117            // Find the first non-empty size class
118            if !self.free_list[i].is_empty() {
119                // Split buffers
120                for j in (class + 1..i + 1).rev() {
121                    if let Some(block_ref) = self.free_list[j].iter().next() {
122                        let block = *block_ref;
123                        self.free_list[j - 1].insert(block + (1 << (j - 1)));
124                        self.free_list[j - 1].insert(block);
125                        self.free_list[j].remove(&block);
126                    } else {
127                        return None;
128                    }
129                }
130
131                let result = self.free_list[class].iter().next();
132                if let Some(result_ref) = result {
133                    let result = *result_ref;
134                    self.free_list[class].remove(&result);
135                    self.allocated += size;
136                    return Some(result);
137                } else {
138                    return None;
139                }
140            }
141        }
142        None
143    }
144
145    /// Allocate a specific range of frames at the given start address with the given power-of-two
146    /// size. The start address must be aligned to the size.
147    fn alloc_at_power_of_two(&mut self, start: usize, size: usize) -> Option<usize> {
148        let class = size.trailing_zeros() as usize;
149
150        if start & (size - 1) != 0 {
151            return None;
152        }
153
154        for i in class..self.free_list.len() {
155            let block_start = start & !((1 << i) - 1);
156            if self.free_list[i].remove(&block_start) {
157                let mut current_start = block_start;
158                for j in (class..i).rev() {
159                    let mid = current_start + (1 << j);
160                    if start >= mid {
161                        self.free_list[j].insert(current_start);
162                        current_start = mid;
163                    } else {
164                        self.free_list[j].insert(mid);
165                    }
166                }
167                self.allocated += size;
168                return Some(start);
169            }
170        }
171
172        None
173    }
174
175    /// Deallocate a range of frames [frame, frame+count) from the frame allocator.
176    ///
177    /// The range should be exactly the same when it was allocated, as in heap allocator
178    pub fn dealloc(&mut self, start_frame: usize, count: usize) {
179        let size = count.next_power_of_two();
180        self.dealloc_power_of_two(start_frame, size)
181    }
182
183    /// Deallocate a range of frames which was previously allocated by [`alloc_aligned`].
184    ///
185    /// The layout must be exactly the same as when it was allocated.
186    pub fn dealloc_aligned(&mut self, start_frame: usize, layout: Layout) {
187        let size = max(layout.size().next_power_of_two(), layout.align());
188        self.dealloc_power_of_two(start_frame, size)
189    }
190
191    /// Deallocate a range of frames with the given size from the allocator. The size must be a
192    /// power of two.
193    fn dealloc_power_of_two(&mut self, start_frame: usize, size: usize) {
194        let class = size.trailing_zeros() as usize;
195
196        // Merge free buddy lists
197        let mut current_ptr = start_frame;
198        let mut current_class = class;
199        while current_class < self.free_list.len() - 1 {
200            let buddy = current_ptr ^ (1 << current_class);
201            if self.free_list[current_class].remove(&buddy) {
202                // Free buddy found
203                current_ptr = min(current_ptr, buddy);
204                current_class += 1;
205            } else {
206                break;
207            }
208        }
209
210        self.free_list[current_class].insert(current_ptr);
211        self.allocated -= size;
212    }
213}
214
215impl<const ORDER: usize> Default for FrameAllocator<ORDER> {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221/// A locked version of `FrameAllocator`
222///
223/// # Usage
224///
225/// Create a locked frame allocator and add frames to it:
226/// ```
227/// use buddy_system_allocator::*;
228/// // Notice that the max order is `ORDER - 1`.
229/// let mut frame = LockedFrameAllocator::<33>::new();
230/// assert!(frame.lock().alloc(1).is_none());
231///
232/// frame.lock().add_frame(0, 3);
233/// let num = frame.lock().alloc(1);
234/// assert_eq!(num, Some(2));
235/// let num = frame.lock().alloc(2);
236/// assert_eq!(num, Some(0));
237/// ```
238#[cfg(feature = "use_spin")]
239#[derive(Default)]
240pub struct LockedFrameAllocator<const ORDER: usize = 33>(Mutex<FrameAllocator<ORDER>>);
241
242#[cfg(feature = "use_spin")]
243impl<const ORDER: usize> LockedFrameAllocator<ORDER> {
244    /// Creates an empty frame allocator.
245    pub const fn new() -> Self {
246        Self(Mutex::new(FrameAllocator::new()))
247    }
248}
249
250#[cfg(feature = "use_spin")]
251impl<const ORDER: usize> Deref for LockedFrameAllocator<ORDER> {
252    type Target = Mutex<FrameAllocator<ORDER>>;
253
254    fn deref(&self) -> &Mutex<FrameAllocator<ORDER>> {
255        &self.0
256    }
257}