free_list/
lib.rs

1//! A free-list-based page/frame allocator.
2//!
3//! The main type of this crate is [`FreeList`], which allocates [`PageRange`]s.
4//!
5//! # Examples
6//!
7//! ```
8//! use free_list::{FreeList, PageLayout};
9//!
10//! let mut free_list = FreeList::<16>::new();
11//!
12//! unsafe {
13//!     free_list.deallocate((0x1000..0x5000).try_into().unwrap()).unwrap();
14//! }
15//! assert_eq!(free_list.free_space(), 0x4000);
16//!
17//! let layout = PageLayout::from_size(0x4000).unwrap();
18//! assert_eq!(free_list.allocate(layout).unwrap(), (0x1000..0x5000).try_into().unwrap());
19//! ```
20
21#![cfg_attr(not(test), no_std)]
22#![cfg_attr(docsrs, feature(doc_auto_cfg))]
23#![warn(missing_docs)]
24#![doc(test(attr(deny(warnings))))]
25
26#[doc = include_str!("../README.md")]
27#[cfg(doctest)]
28pub struct ReadmeDoctests;
29
30mod page_layout;
31mod page_list;
32mod page_range;
33
34use core::fmt;
35use core::num::NonZeroUsize;
36
37pub use self::page_layout::{PageLayout, PageLayoutError};
38use self::page_list::PageList;
39pub use self::page_range::{PageRange, PageRangeError, PageRangeSub};
40
41/// The base page size.
42///
43/// [`PageRange`] and [`PageLayout`] may only refer to whole pages of this size.
44pub const PAGE_SIZE: usize = 4096;
45
46/// A free-list-based page/frame allocator.
47///
48/// This type can be used for managing (allocating and deallocating) physical and virtual memory at [`PAGE_SIZE`] granularity.
49/// This is useful for ensuring pages and frames being currently unused before mapping them.
50///
51/// The `const N: usize` generic specifies how many internal [`PageRange`]s the free list can hold before needing to allocate more memory from the global allocator.
52///
53/// Before allocating, the free list has to be provided a page range to allocate from via [`FreeList::deallocate`].
54///
55/// # Examples
56///
57/// ```
58/// use free_list::{FreeList, PageLayout};
59///
60/// let mut free_list = FreeList::<16>::new();
61///
62/// unsafe {
63///     free_list.deallocate((0x1000..0x5000).try_into().unwrap()).unwrap();
64/// }
65/// assert_eq!(free_list.free_space(), 0x4000);
66///
67/// let layout = PageLayout::from_size(0x4000).unwrap();
68/// assert_eq!(free_list.allocate(layout).unwrap(), (0x1000..0x5000).try_into().unwrap());
69/// ```
70#[derive(Debug)]
71pub struct FreeList<const N: usize> {
72    list: PageList<N>,
73}
74
75/// Allocation failure.
76#[derive(Clone, PartialEq, Eq, Debug)]
77#[non_exhaustive]
78pub struct AllocError;
79
80impl fmt::Display for AllocError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.write_str("memory allocation failed")
83    }
84}
85
86impl<const N: usize> FreeList<N> {
87    /// Creates a new free list without any free space.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # #![allow(unused_variables)]
93    /// use free_list::FreeList;
94    ///
95    /// let free_list = FreeList::<16>::new();
96    /// ```
97    pub const fn new() -> Self {
98        Self {
99            list: PageList::new(),
100        }
101    }
102
103    /// Attempts to deallocates a page range.
104    ///
105    /// This should also be used to add more free pages to the allocator, such as after initialization.
106    /// This returns an error, if `range` overlaps with any pages that are already deallocated.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use free_list::FreeList;
112    ///
113    /// let mut free_list = FreeList::<16>::new();
114    ///
115    /// unsafe {
116    ///     free_list.deallocate((0x1000..0x2000).try_into().unwrap()).unwrap();
117    /// }
118    /// ```
119    ///
120    /// # Safety
121    ///
122    /// `range` must be valid to be allocated and used (again).
123    pub unsafe fn deallocate(&mut self, range: PageRange) -> Result<(), AllocError> {
124        self.list.add(range).map_err(|_| AllocError)
125    }
126
127    /// Attempts to allocate a page range.
128    ///
129    /// On success, returns a [`PageRange`] meeting the size and alignment guarantees of `layout`.
130    ///
131    /// This call can only succeed if the free list has previously been provided a free page range to allocate from via [`FreeList::deallocate`].
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use free_list::{FreeList, PageLayout};
137    ///
138    /// let mut free_list = FreeList::<16>::new();
139    ///
140    /// unsafe {
141    ///     free_list.deallocate((0x1000..0x5000).try_into().unwrap()).unwrap();
142    /// }
143    ///
144    /// let layout = PageLayout::from_size(0x4000).unwrap();
145    /// assert_eq!(free_list.allocate(layout).unwrap(), (0x1000..0x5000).try_into().unwrap());
146    /// ```
147    pub fn allocate(&mut self, layout: PageLayout) -> Result<PageRange, AllocError> {
148        self.allocate_with(|range| range.fit(layout))
149    }
150
151    /// Attempts to allocate a specific page range.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use free_list::FreeList;
157    ///
158    /// let mut free_list = FreeList::<16>::new();
159    ///
160    /// unsafe {
161    ///     free_list.deallocate((0x1000..0x2000).try_into().unwrap()).unwrap();
162    ///     free_list.deallocate((0x3000..0x4000).try_into().unwrap()).unwrap();
163    /// }
164    ///
165    /// free_list.allocate_at((0x3000..0x4000).try_into().unwrap()).unwrap();
166    /// ```
167    pub fn allocate_at(&mut self, range: PageRange) -> Result<(), AllocError> {
168        self.list.remove(range).map_err(|_| AllocError)
169    }
170
171    /// Attempts to allocate a page range outside of a given page range.
172    ///
173    /// On success, returns a [`PageRange`] meeting the size and alignment guarantees of `layout` outside of `range`.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// use free_list::{FreeList, PageLayout, PageRange};
179    ///
180    /// let mut free_list = FreeList::<16>::new();
181    ///
182    /// unsafe {
183    ///     free_list.deallocate((0x1000..0x5000).try_into().unwrap()).unwrap();
184    /// }
185    ///
186    /// let layout = PageLayout::from_size(0x1000).unwrap();
187    /// let range = PageRange::new(0x0000, 0x2000).unwrap();
188    /// let allocated = free_list.allocate_outside_of(layout, range);
189    /// assert_eq!(allocated.unwrap(), (0x2000..0x3000).try_into().unwrap());
190    /// ```
191    pub fn allocate_outside_of(
192        &mut self,
193        layout: PageLayout,
194        range: PageRange,
195    ) -> Result<PageRange, AllocError> {
196        self.allocate_with(|entry| match entry - range {
197            PageRangeSub::None => None,
198            PageRangeSub::One(a) => a.fit(layout),
199            PageRangeSub::Two(a, b) => a.fit(layout).or_else(|| b.fit(layout)),
200        })
201    }
202
203    /// Attempts to allocate a page range according to a function.
204    ///
205    /// On success, allocates and returns the first non-none [`PageRange`] returned by `f`.
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use free_list::{FreeList, PageLayout};
211    ///
212    /// let mut free_list = FreeList::<16>::new();
213    ///
214    /// unsafe {
215    ///     free_list.deallocate((0x1000..0x2000).try_into().unwrap()).unwrap();
216    ///     free_list.deallocate((0x3000..0x4000).try_into().unwrap()).unwrap();
217    /// }
218    ///
219    /// let layout = PageLayout::from_size(0x1000).unwrap();
220    /// let allocated = free_list.allocate_with(|range| {
221    ///     (range.start() > 0x2000)
222    ///         .then_some(range)
223    ///         .and_then(|range| range.fit(layout))
224    /// });
225    /// assert_eq!(allocated.unwrap(), (0x3000..0x4000).try_into().unwrap());
226    /// ```
227    pub fn allocate_with<F>(&mut self, f: F) -> Result<PageRange, AllocError>
228    where
229        F: FnMut(PageRange) -> Option<PageRange>,
230    {
231        let mut f = f;
232
233        let (index, fit) = self
234            .list
235            .iter()
236            .enumerate()
237            .find_map(|(index, entry)| f(entry).map(|fit| (index, fit)))
238            .ok_or(AllocError)?;
239
240        self.list.remove_at(index, fit).unwrap();
241
242        Ok(fit)
243    }
244
245    /// Returns how much free space this allocator has in bytes.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// use free_list::FreeList;
251    ///
252    /// let mut free_list = FreeList::<16>::new();
253    ///
254    /// unsafe {
255    ///     free_list.deallocate((0x1000..0x5000).try_into().unwrap()).unwrap();
256    /// }
257    /// assert_eq!(free_list.free_space(), 0x4000);
258    /// ```
259    pub fn free_space(&self) -> usize {
260        self.list
261            .iter()
262            .map(PageRange::len)
263            .map(NonZeroUsize::get)
264            .sum()
265    }
266}
267
268impl<const N: usize> Default for FreeList<N> {
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274impl<const N: usize> fmt::Display for FreeList<N> {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        self.list.fmt(f)
277    }
278}
279
280#[cfg(all(target_arch = "x86_64", feature = "x86_64"))]
281mod frame_allocator {
282    use x86_64::structures::paging::{FrameAllocator, FrameDeallocator, PageSize, PhysFrame};
283    use x86_64::PhysAddr;
284
285    use super::*;
286
287    unsafe impl<S: PageSize, const N: usize> FrameAllocator<S> for FreeList<N> {
288        fn allocate_frame(&mut self) -> Option<PhysFrame<S>> {
289            let size = S::SIZE.try_into().unwrap();
290            let layout = PageLayout::from_size_align(size, size).unwrap();
291            let range = self.allocate(layout).ok()?;
292            let address = PhysAddr::new(range.start().try_into().unwrap());
293            Some(PhysFrame::from_start_address(address).unwrap())
294        }
295    }
296
297    impl<S: PageSize, const N: usize> FrameDeallocator<S> for FreeList<N> {
298        unsafe fn deallocate_frame(&mut self, frame: PhysFrame<S>) {
299            unsafe {
300                self.deallocate(frame.into())
301                    .expect("frame could not be deallocated");
302            }
303        }
304    }
305
306    impl<S: PageSize> From<PhysFrame<S>> for PageRange {
307        fn from(value: PhysFrame<S>) -> Self {
308            let start = value.start_address().as_u64().try_into().unwrap();
309            let len = value.size().try_into().unwrap();
310            Self::from_start_len(start, len).unwrap()
311        }
312    }
313}