From 386df39c9866a4d945de46ef0dcab2363c674e0e Mon Sep 17 00:00:00 2001 From: Nathan Ringo Date: Sun, 1 Sep 2024 19:59:44 -0500 Subject: Move almost all the kernel into crates/. --- kernel/src/allocators/buddy/bitvec.rs | 50 ---- kernel/src/allocators/buddy/mod.rs | 52 ---- kernel/src/allocators/buddy/stripe.rs | 148 ---------- kernel/src/allocators/buddy/tree.rs | 112 ------- kernel/src/allocators/mod.rs | 24 -- kernel/src/allocators/physical_memory_free_list.rs | 322 --------------------- 6 files changed, 708 deletions(-) delete mode 100644 kernel/src/allocators/buddy/bitvec.rs delete mode 100644 kernel/src/allocators/buddy/mod.rs delete mode 100644 kernel/src/allocators/buddy/stripe.rs delete mode 100644 kernel/src/allocators/buddy/tree.rs delete mode 100644 kernel/src/allocators/mod.rs delete mode 100644 kernel/src/allocators/physical_memory_free_list.rs (limited to 'kernel/src/allocators') diff --git a/kernel/src/allocators/buddy/bitvec.rs b/kernel/src/allocators/buddy/bitvec.rs deleted file mode 100644 index c7f415a..0000000 --- a/kernel/src/allocators/buddy/bitvec.rs +++ /dev/null @@ -1,50 +0,0 @@ -use core::mem::transmute; - -use contracts::requires; - -/// A fixed-length vector of bits. -pub struct BitVec([usize]); - -impl BitVec { - fn from_mut(words: &mut [usize]) -> &mut BitVec { - // SAFETY: The types have a newtype relationship. - unsafe { transmute(words) } - } - - fn from_ref(words: &[usize]) -> &BitVec { - // SAFETY: The types have a newtype relationship. - unsafe { transmute(words) } - } - - /// Retrieves the value of a bit from the BitVec. - #[requires(i < self.len())] - pub fn get(&self, i: usize) -> bool { - let word_index = i / usize::BITS as usize; - let subword_index = i % usize::BITS as usize; - let one_hot = 1 << subword_index; - (self.0[word_index] & one_hot) != 0 - } - - /// Returns whether the BitVec is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Returns the number of bits in the BitVec. - pub fn len(&self) -> usize { - self.0.len() * usize::BITS as usize - } - - /// Sets the value of a bit in the BitVec. - #[requires(i < self.len())] - pub fn set(&mut self, i: usize, value: bool) { - let word_index = i / usize::BITS as usize; - let subword_index = i % usize::BITS as usize; - let one_hot = 1 << subword_index; - if value { - self.0[word_index] |= one_hot; - } else { - self.0[word_index] &= !one_hot; - } - } -} diff --git a/kernel/src/allocators/buddy/mod.rs b/kernel/src/allocators/buddy/mod.rs deleted file mode 100644 index 08b30a7..0000000 --- a/kernel/src/allocators/buddy/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! A buddy allocator, used to allocate pages. -//! -//! The allocator can be split into three abstractions: stripes, trees, and the allocator. -//! -//! TODO: See if there's standard terminology for these. -//! -//! ## Stripes -//! -//! The buddy allocator works in terms of size classes, which are power-of-two sized, starting at a -//! single page and going up from there. Each stripe corresponds to a single size class and a -//! particular region of memory. -//! -//! A stripe contains a circular doubly-linked free-list for subregions of that size class, and a -//! bitset marking whether a particular region has been allocated or not. Being a circular -//! doubly-linked list makes it cheap to remove an element whose address we know, as well as cheap -//! to push and pop elements. -//! -//! It's efficient to go from the address of a subregion to the index of its corresponding bit, so -//! when we hand out a subregion from the free-list or put one back, it's cheap to read or write -//! its bit. -//! -//! ## Trees -//! -//! A tree is logically a collection of stripes, one per size class. To pack the structures more -//! efficiently, they are stored interleaved with each other, and the tree manages this. -//! -//! The important logic at the level of trees happens when we allocate a subregion from a size -//! class whose stripe's free-list is empty, and when we free subregions. -//! -//! When a stripe's free-list is empty, the tree instead allocates a subregion of a larger size -//! from the next stripe. It can then store the unused portion in the current size class. -//! -//! The really important bit is the ability to merge subregions when they're freed. When we free a -//! subregion of a certain size class, we can check whether its neighbor (its buddy) is unallocated -//! as well. If so, we can remove it from the free-list by its address. We can combine the two -//! subregions into one of the next larger size class, and then return that subregion to the next -//! stripe. -//! -//! ## The buddy allocator -//! -//! Finally, the overall buddy allocator needs to be able to handle multiple memory regions. To -//! facilitate this, the trees are stored in an array, which forms the overall allocator. - -mod bitvec; -mod stripe; -mod tree; - -/// The index of the largest size class; i.e., one less than the number of size classes. -const MAX_ORDER: usize = 18; - -// The max order comes out to a largest size class of 1GiB pages. -static_assertions::const_assert_eq!(4096 << MAX_ORDER, 1024 * 1024 * 1024); diff --git a/kernel/src/allocators/buddy/stripe.rs b/kernel/src/allocators/buddy/stripe.rs deleted file mode 100644 index 9ec5985..0000000 --- a/kernel/src/allocators/buddy/stripe.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::allocators::{buddy::bitvec::BitVec, PAGE_SIZE_BITS}; -use core::ptr::NonNull; - -/// A single size class for a single region of the allocator. See the comment on the -/// `crate::allocators::buddy` module for more information. -pub struct Stripe<'buddy> { - /// The base address of the tree this stripe is a part of. - pub base: *const (), - - /// The order of the stripe. Order `n` means the subregions are `2ⁿ` pages in size. - pub order: usize, - - /// The sentinel node of the free-list for this node. As an invariant of the type, there are no - /// live references to any node in this list. - pub free_list: NonNull, - - /// The bitset used to track whether a given subregion is allocated or not. A `true` bit - /// corresponds to an allocated subregion. - pub bitset: &'buddy mut BitVec, - - /// The offset from the start of the bitset to the region used by this stripe. - pub bitset_offset: usize, -} - -impl<'buddy> Stripe<'buddy> { - /// Returns the buddy of the given pointer. - /// - /// ## Safety - /// - /// - The pointer must actually be part of this region. - unsafe fn buddy_of(&self, ptr: NonNull) -> NonNull { - let index = self.buddy_of_index(self.index_of(ptr)); - let addr = self.base as usize + (index << (PAGE_SIZE_BITS + self.order)); - NonNull::new_unchecked(addr as *mut FreeListNode) - } - - /// Returns the buddy of the given index. - fn buddy_of_index(&self, index: usize) -> usize { - index ^ (1 << (PAGE_SIZE_BITS + self.order)) - } - - /// Returns the index the given pointer should have in the BitVec. - fn index_of(&self, ptr: NonNull) -> usize { - (ptr.as_ptr() as usize - self.base as usize) >> (PAGE_SIZE_BITS + self.order) - } - - /// Pops a subregion from the free-list. - pub fn pop(&mut self) -> Option> { - // SAFETY: The `free_list` is guaranteed to be valid by the invariants of the buddy - // allocator. Retrieving the next pointer doesn't, on its own, break aliasing rules. - let next = unsafe { self.free_list.read().next }; - - // If the sentinel and the next pointer refer to the same spot, the free-list was empty, so - // we can't pop from it. - if self.free_list == next { - return None; - } - - // Otherwise, remove the node from the free-list. - unsafe { - FreeListNode::remove(next); - } - - // Finally, mark the node as allocated in the bitvec. - let index = self.index_of(next); - assert!(self.bitset.get(self.bitset_offset + index)); - self.bitset.set(self.bitset_offset + index, true); - - Some(next) - } - - /// Pushes a subregion back into the free-list. - /// - /// # Safety - /// - /// - There must be no live references to `subregion`. - /// - `subregion` must not be a member of any list. - pub unsafe fn push(&mut self, subregion: NonNull) { - // Insert the subregion as the first element of the free-list. - // - // SAFETY: The free-list is guaranteed to be valid by the invariants of the buddy - // allocator. - unsafe { - FreeListNode::insert(self.free_list, subregion); - } - - // Mark the node as unallocated in the bitvec. - let index = self.index_of(subregion); - assert!(self.bitset.get(self.bitset_offset + index)); - self.bitset.set(self.bitset_offset + index, false); - } - - /// Pushes a subregion into the free-list for the first time. - /// - /// # Safety - /// - /// - There must be no live references to `subregion`. - /// - `subregion` must not be a member of any list. - pub unsafe fn push_initial(&mut self, subregion: NonNull) { - // Insert the subregion as the first element of the free-list. - // - // SAFETY: The free-list is guaranteed to be valid by the invariants of the buddy - // allocator. - unsafe { - FreeListNode::insert(self.free_list, subregion); - } - - // Mark the node as unallocated in the bitvec. - let index = self.index_of(subregion); - assert!(self.bitset.get(self.bitset_offset + index)); - self.bitset.set(self.bitset_offset + index, false); - } -} - -pub struct FreeListNode { - next: NonNull, - prev: NonNull, -} - -impl FreeListNode { - /// Inserts `new` after `prev`, initializing it. `prev` may be the sentinel. - /// - /// # Safety - /// - /// - There must be no live references to any node in the list that includes `prev`, including - /// the sentinel. - /// - There must be no live references to `new`. - /// - `new` must not be a member of any list. - pub unsafe fn insert(prev: NonNull, new: NonNull) { - let next = prev.read().next; - (*prev.as_ptr()).next = new; - (*next.as_ptr()).prev = new; - new.write(FreeListNode { next, prev }); - } - - /// Removes this node from the free list it is a part of. - /// - /// # Safety - /// - /// - The pointer must point to a node that is part of a free list. - /// - There must be no live references to any node in the list, including the sentinel. - /// - The pointer must not point to the sentinel node. - pub unsafe fn remove(ptr: NonNull) { - let FreeListNode { next, prev } = ptr.read(); - (*next.as_ptr()).prev = prev; - (*prev.as_ptr()).next = next; - } -} diff --git a/kernel/src/allocators/buddy/tree.rs b/kernel/src/allocators/buddy/tree.rs deleted file mode 100644 index 3953f39..0000000 --- a/kernel/src/allocators/buddy/tree.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::allocators::{ - buddy::{ - bitvec::BitVec, - stripe::{FreeListNode, Stripe}, - MAX_ORDER, - }, - PAGE_SIZE_BITS, -}; -use contracts::requires; -use core::ptr::NonNull; - -/// A single region of the allocator. See the comment on the `crate::allocators::buddy` module for -/// more information. -pub struct Tree<'buddy> { - /// The base address of the tree. - pub base: *const (), - - /// The log2 of the number of pages in the region represented by the tree. - pub log2_page_count: usize, - - /// The array of sentinel nodes of the free-list for this node. As an invariant of the type, - /// there are no live references to any node in any list in this array, and there are - /// MAX_ORDER + 1 nodes. - pub free_lists: NonNull, - - /// The bitset used to track whether subregion are allocated or not in this tree. - pub bitset: &'buddy mut BitVec, -} - -impl<'buddy> Tree<'buddy> { - /// Tries to allocate a subregion with the given order, possibly splitting a larger subregion - /// in order to so so. - #[requires(order <= MAX_ORDER)] - pub fn alloc(&mut self, order: usize) -> Option> { - if let Some(ptr) = self.stripe(order).pop() { - Some(ptr) - } else if order == MAX_ORDER { - None - } else { - // Get a larger region. - let ptr = self.alloc(order + 1)?; - - // Get a pointer to the higher half. - // - // SAFETY: This has to be in-bounds, it's part of the same allocation! - let higher_half = unsafe { ptr.byte_add(1 << (PAGE_SIZE_BITS + order)) }; - - // Put the higher half in the current buddy's stripe. - // - // SAFETY: The higher half is from this region, not in the higher stripe, and of the - // right size. - unsafe { - self.stripe(order).push(higher_half); - } - - // Return the pointer. - Some(ptr) - } - } - - /// Returns the stripe with the given order. - #[requires(order <= MAX_ORDER)] - fn stripe<'stripe>(&'stripe mut self, order: usize) -> Stripe<'stripe> { - // TODO: There should be some smart bitwise-math version of this... - let mut bitset_offset = 0; - for i in 0..order { - bitset_offset += (1 << self.log2_page_count) >> i; - } - - Stripe { - base: self.base, - order, - // SAFETY: order is constrained to be in-bounds. - free_list: unsafe { self.free_lists.add(order) }, - bitset: self.bitset, - bitset_offset, - } - } -} - -/// Evil bitwise version of the reasonable loop to compute the `bitset_offset` of a stripe. -#[requires(log2_page_count < usize::BITS as usize)] -#[requires(order < usize::BITS as usize)] -fn compute_bitset_offset(log2_page_count: usize, order: usize) -> usize { - let ones = |i: usize| !(usize::MAX << i); - - if order > log2_page_count + 1 { - ones(log2_page_count + 1) - } else { - ones(order).wrapping_shl((log2_page_count + 1 - order) as u32) - } -} - -#[test] -fn compute_bitset_offset_test() { - fn compute_bitset_offset_loop(log2_page_count: usize, order: usize) -> usize { - let mut bitset_offset = 0; - for i in 0..order { - bitset_offset += (1 << log2_page_count) >> i; - } - bitset_offset - } - - for log2_page_count in 0..64 { - for order in 0..64 { - assert_eq!( - compute_bitset_offset(log2_page_count, order), - compute_bitset_offset_loop(log2_page_count, order), - ); - } - } -} diff --git a/kernel/src/allocators/mod.rs b/kernel/src/allocators/mod.rs deleted file mode 100644 index 49f29e2..0000000 --- a/kernel/src/allocators/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -use core::{ffi::c_void, ops::Range, ptr::addr_of}; - -pub mod buddy; -pub mod physical_memory_free_list; - -/// The number of bits in the offset in a page. -pub const PAGE_SIZE_BITS: usize = 12; - -/// The size of a page, in bytes. -pub const PAGE_SIZE: usize = 1 << PAGE_SIZE_BITS; - -/// Returns the physical address range the kernel is in. -pub fn kernel_boundaries() -> Range { - extern "C" { - static kernel_start: c_void; - static kernel_end: c_void; - } - - // SAFETY: We only use these as addresses, we never dereference them. - let (kernel_start_addr, kernel_end_addr) = - unsafe { (addr_of!(kernel_start), addr_of!(kernel_end)) }; - - kernel_start_addr as usize..kernel_end_addr as usize -} diff --git a/kernel/src/allocators/physical_memory_free_list.rs b/kernel/src/allocators/physical_memory_free_list.rs deleted file mode 100644 index 0d2f1b4..0000000 --- a/kernel/src/allocators/physical_memory_free_list.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! A free-list allocator that runs in physical memory. This should only be used to bootstrap the -//! buddy allocator for physical memory. - -use crate::{ - allocators::{kernel_boundaries, PAGE_SIZE, PAGE_SIZE_BITS}, - device_tree::FlattenedDeviceTree, - prelude::*, - util::ranges_overlap, -}; -use contracts::requires; -use core::{ - fmt, iter, - ops::Range, - ptr::{self, NonNull}, -}; - -/// A free-list allocator that runs in physical memory. This should only be used to bootstrap the -/// buddy allocator for physical memory. -pub struct FreeList<'dt> { - /// The DeviceTree the machine was booted with. - device_tree: &'dt FlattenedDeviceTree<'dt>, - - /// The physical address of the head of the free-list allocator. - /// - /// The free-list is sorted by size, smallest to largest. - head: Option, - - /// The number of regions in the free-list. - len: usize, -} - -impl<'dt> FreeList<'dt> { - /// Creates a new, empty free-list that checks addresses against the given DeviceTree. - pub fn new(device_tree: &'dt FlattenedDeviceTree<'dt>) -> FreeList { - let out = FreeList { - device_tree, - head: None, - len: 0, - }; - - // Log the memory reservations. - let reservation_count = out.iter_reserved().count(); - debug!( - "found {} memory reservations{}", - reservation_count, - if reservation_count == 0 { "" } else { ":" } - ); - for reservation in out.iter_reserved() { - debug!( - "{:p}..{:p} ({} byte{})", - reservation.start as *const u8, - reservation.end as *const u8, - reservation.len(), - if reservation.len() == 1 { "" } else { "s" } - ) - } - - out - } - - /// Adds a physical address range to the free-list allocator. - /// - /// ## Safety - /// - /// - This allocator must have been created with the DeviceTree that gave us this memory. - /// - The region must be a valid physical memory range. - /// - Paging must not be enabled for the entire lifetime of this allocator. - pub unsafe fn add_range(&mut self, mut addrs: Range) { - // Clamp the range, so that we can't somehow end up with the zero address or with - // higher-half addresses. - addrs.start = addrs.start.max(1); - addrs.end = addrs.end.min(isize::MAX as usize); - - // Trim the range to be page-aligned. - addrs.start = (addrs.start + (PAGE_SIZE - 1)) & !(PAGE_SIZE - 1); - addrs.end &= !(PAGE_SIZE - 1); - - // Add each unreserved subrange to the allocator's list. - loop { - assert_eq!(addrs.start & (PAGE_SIZE - 1), 0); - assert_eq!(addrs.end & (PAGE_SIZE - 1), 0); - - // Walk forwards until the first page is unreserved. - loop { - // Ensure the range is non-empty. - if addrs.end <= addrs.start { - return; - } - - // If the start of the range is reserved, walk forwards a page. - let first_page = addrs.start..addrs.start + PAGE_SIZE; - if self - .iter_reserved() - .any(|range| ranges_overlap(&range, &first_page)) - { - addrs.start = first_page.end; - } else { - break; - } - } - - // Find the first reservation that overlaps. - if let Some(reservation) = self - .iter_reserved() - .filter(|range| ranges_overlap(range, &addrs)) - .min_by_key(|range| range.start) - { - // Get the range that's before the reservation. - let mut unreserved = addrs.start..reservation.start; - - // Trim the range to be page-aligned. We don't need to trim the start, because - // addrs.start is already aligned (by our loop invariant). - unreserved.end &= !(PAGE_SIZE - 1); - - // Add the range up to the start of that overlap. - self.add_range_unchecked(unreserved); - - // Adjust the remaining range to be after the overlap. - addrs.start = reservation.end; - - // Trim the range to be page-aligned. We don't need to trim the end, because it's - // unchanged. - addrs.start = (addrs.start + (PAGE_SIZE - 1)) & !(PAGE_SIZE - 1); - } else { - // If no range overlaps, the entire remaining range is valid. - self.add_range_unchecked(addrs); - return; - } - } - } - - /// Adds a physical address range to the free-list allocator, without checking for reservations - /// inside it. - /// - /// ## Safety - /// - /// - This allocator must have been created with the DeviceTree that gave us this memory. - /// - The region must be a valid physical memory range. - /// - The region must be page-aligned. - /// - The region must not overlap with any reservations. - /// - Paging must not be enabled for the entire lifetime of this allocator. - pub unsafe fn add_range_unchecked(&mut self, addrs: Range) { - // Initialize the list node. - let mut new_node = FreeListAddr::new(addrs); - - // Walk forwards through the list until the node we're going to put ourselves before has a - // size greater or equal to ours. - let mut spot_to_store = &mut self.head; - while let Some(existing_node) = spot_to_store { - if existing_node.pages_after() < new_node.pages_after() { - spot_to_store = spot_to_store.as_mut().unwrap().next_mut(); - } else { - break; - } - } - - *new_node.next_mut() = spot_to_store.take(); - *spot_to_store = Some(new_node); - self.len += 1; - } - - /// Returns an iterator that removes ranges from the free-list. Ranges are returned sorted from - /// smallest to largest. - pub fn drain<'iter: 'dt>(&'iter mut self) -> impl 'dt + Iterator> { - iter::from_fn(move || self.pop()) - } - - /// Returns whether the free-list is empty. - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Returns an iterator over the reserved ranges of addresses. - /// - /// Addresses may be reserved by: - /// - /// - Overlapping with the kernel, including the early-boot kernel stack. - /// - Overlapping with the DeviceTree. - /// - Overlapping with any memory reservations in the DeviceTree. - fn iter_reserved(&self) -> impl '_ + Iterator> { - // Get the boundaries of the kernel. - let kernel = kernel_boundaries(); - - // As a sanity check, make sure this function is inside those boundaries... - assert!(kernel.contains(&(FreeList::iter_reserved as usize))); - - // Check if the address is either in the kernel or somewhere in the DeviceTree. - iter::once(kernel).chain(self.device_tree.iter_reserved()) - } - - /// Returns the number of regions in the free-list. - pub fn len(&self) -> usize { - self.len - } - - /// Pops the smallest range from the free-list, returning it. - pub fn pop(&mut self) -> Option> { - match self.head.take() { - Some(head) => { - let (addrs, next) = head.take(); - self.head = next; - Some(addrs) - } - None => None, - } - } -} - -impl<'dt> fmt::Debug for FreeList<'dt> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - struct FreeListDebug<'a>(&'a Option); - - impl<'a> fmt::Debug for FreeListDebug<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let mut addr = self.0; - fmt.debug_list() - .entries(iter::from_fn(|| match addr { - Some(head) => { - addr = head.next(); - Some(head) - } - None => None, - })) - .finish() - } - } - - fmt.debug_struct("FreeList") - .field("device_tree", &self.device_tree) - .field("addrs", &FreeListDebug(&self.head)) - .finish() - } -} - -/// An address in the free-list. -/// -/// # Invariants -/// -/// - The pointer must point to physical memory. -/// - The FreeListHeader must be initialized. -/// - The region being pointed to must actually be free. -/// - The region being pointed to must be unreserved. -struct FreeListAddr(NonNull); - -impl FreeListAddr { - /// Initializes the page with the given address range, with no next address. - /// - /// # Safety - /// - /// - All of the invariants in the struct-level documentation, except the header being - /// initialized (this function will initialize it). - #[requires(addrs.start != 0)] - #[requires(addrs.start & (PAGE_SIZE - 1) == 0)] - #[requires(addrs.end & (PAGE_SIZE - 1) == 0)] - #[requires(addrs.start < addrs.end)] - unsafe fn new(addrs: Range) -> FreeListAddr { - let addr = addrs.start; - *(addr as *mut FreeListHeader) = FreeListHeader { - next: None, - pages_after: ((addrs.end - addrs.start) >> PAGE_SIZE_BITS) - 1, - }; - - FreeListAddr(NonNull::new_unchecked(addr as *mut FreeListHeader)) - } - - /// Returns the `next` field of the header. - fn next(&self) -> &Option { - // SAFETY: The invariants of FreeListAddr::new are sufficient to make this valid. - unsafe { &self.0.as_ref().next } - } - - /// Returns a mutable reference to the `next` field of the header. - fn next_mut(&mut self) -> &mut Option { - // SAFETY: The invariants of FreeListAddr::new are sufficient to make this valid. - unsafe { &mut self.0.as_mut().next } - } - - /// Returns the number of pages *after* the first one in this region. - fn pages_after(&self) -> usize { - // SAFETY: The invariants of FreeListAddr::new are sufficient to make this valid. - unsafe { self.0.as_ref().pages_after } - } - - /// Returns the range of addresses in the region and the address of the next region. - fn take(self) -> (Range, Option) { - let start = self.0.as_ptr() as usize; - let len_pages = 1 + self.pages_after(); - let len_bytes = len_pages << PAGE_SIZE_BITS; - let addrs = start..start + len_bytes; - - // SAFETY: The invariants of FreeListAddr::new are sufficient to make the read valid. - // Because we drop the address, we know we won't accidentally have any ownership issues - // with passing ownership up. - let header = unsafe { ptr::read(self.0.as_ptr()) }; - - (addrs, header.next) - } -} - -impl fmt::Debug for FreeListAddr { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let addr = self.0.as_ptr() as *const u8; - let len_pages = 1 + self.pages_after(); - let len_bytes = len_pages << PAGE_SIZE_BITS; - write!( - fmt, - "{:p}..{:p} ({} page{})", - addr, - addr.wrapping_add(len_bytes), - len_pages, - if len_pages == 1 { "" } else { "s" } - ) - } -} - -struct FreeListHeader { - /// The physical address of the next free-list header. - next: Option, - - /// The number of pages after this one. - pages_after: usize, -} -- cgit v1.2.3