rusty_mos/memory/buddy_allocator.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
//! Global allocator use the buddy system.
//!
//! The *category* count can be specified via a const generic. The smallest
//! alloc unit is one page while the largest is (2 ^ (category - 1)) pages.
//!
//! This allocator can be used after the page-memory manager is initilized.
use core::{
alloc::GlobalAlloc,
cmp::{max, min},
mem,
ptr::null_mut,
};
use crate::{
debugln,
memory::pmap::{PageList, PageNode, PAGES},
memory::regions::PAGE_SIZE,
pa2page, page2kva,
utils::sync_ref_cell::SyncImplRef,
PADDR,
};
/// The buddy system core data structure.
struct BuddyInner<const CCOUNT: usize> {
/// Free page lists. Only the first page will be in the list.
///
/// The `i`-th category holds (2^i) pages each.
free_list: [PageList; CCOUNT],
/// The start page address used in the system.
page_start: *mut PageNode,
}
/// For it can be used globally.
unsafe impl<const CCOUNT: usize> Send for BuddyInner<CCOUNT> {}
impl<const CCOUNT: usize> Default for BuddyInner<CCOUNT> {
/// Default constructions.
fn default() -> Self {
Self::new()
}
}
impl<const CCOUNT: usize> BuddyInner<CCOUNT> {
/// Create a brand-new buddy contents.
const fn new() -> Self {
Self {
free_list: [PageList::new(); CCOUNT],
page_start: null_mut(),
}
}
/// Initialize the free list. The pages will be inserted into the topest
/// category and the remained into one-level-lower category. As to the
/// category *zero*.
///
/// Only after this function invoked, the system can be used.
fn init(&mut self, page_start: *mut PageNode, size: usize) {
self.page_start = page_start;
let page_count = size / PAGE_SIZE;
let mut index = 0;
for i in (0..CCOUNT).rev() {
while index < page_count {
self.free_list[i].insert_head(page_start.wrapping_add(index));
index += 1 << i;
}
}
}
}
impl<const CCOUNT: usize> BuddyInner<CCOUNT> {
/// Buddy Alloc.
///
/// This method will search the specified category's free list first. If
/// found, the page will be returned.
///
/// Otherwise, this method will search the higher category, until a free
/// item was found. In this situation, the pages will be splitted and
/// inserted into the lower category's free list one by one.
///
/// If no pages can be found, a *null* pointer will be returned.
fn alloc(&mut self, layout: core::alloc::Layout) -> *mut u8 {
let page_count =
(max(layout.size(), layout.align()).div_ceil(PAGE_SIZE)).next_power_of_two();
let page_count = max(page_count, 1);
let category = page_count.trailing_zeros() as usize;
for i in category..CCOUNT {
if self.free_list[i].empty() {
continue;
}
let allocated = self.free_list[i].pop_head().unwrap();
let page = allocated as *mut PageNode;
for j in category..i {
self.free_list[j].insert_head(page.wrapping_add(1 << j));
}
let kva = page2kva!(allocated, *PAGES.borrow(); PageNode);
debugln!(
"> ALLOC: allocated: {} bytes, page at 0x{:x}, kva at 0x{:x}, index: {}",
layout.size(),
allocated as usize,
kva,
(allocated as usize - self.page_start as usize) / mem::size_of::<PageNode>()
);
return kva as *mut u8;
}
null_mut()
}
/// Buddy Dealloc.
///
/// The page will be inserted into its category's free list if no buddy is
/// found.
///
/// Otherwise, the buddy will be removed from the free list. They will
/// be merged into a larger item and be inserted into one-level-higher
/// category.
///
/// The process above will be performed recursively unless reached the
/// toppest category.
fn dealloc(&mut self, ptr: *mut u8, layout: core::alloc::Layout) {
let p = pa2page!(PADDR!(ptr as usize), *PAGES.borrow(); PageNode);
let mut page_index = (p - self.page_start as usize) / mem::size_of::<PageNode>();
let page_count =
(max(layout.size(), layout.align()).div_ceil(PAGE_SIZE)).next_power_of_two();
let page_count = max(page_count, 1);
let category = page_count.trailing_zeros() as usize;
debugln!(
"> FREE: dealloc {} bytes with align {}, at 0x{:x}, index: {}",
layout.size(),
layout.align(),
ptr as usize,
page_index
);
'iter_cate: for i in category..CCOUNT {
let buddy = page_index ^ (1 << i);
let mut list = self.free_list[i].head;
while i != CCOUNT - 1 && !list.is_null() {
if buddy == (list as usize - self.page_start as usize) / mem::size_of::<PageNode>()
{
PageList::remove(self.page_start.wrapping_add(buddy));
page_index = min(page_index, buddy);
continue 'iter_cate;
}
unsafe { list = (*list).next };
}
// Not find or reach the end
self.free_list[i].insert_head(self.page_start.wrapping_add(page_index));
break;
}
}
}
/// The *real* allocator provided to the Rust. A wrapper.
pub struct BuddyAllocator<const CCOUNT: usize>(SyncImplRef<BuddyInner<CCOUNT>>);
impl<const CCOUNT: usize> Default for BuddyAllocator<CCOUNT> {
/// Default constructions.
fn default() -> Self {
Self::new()
}
}
impl<const CCOUNT: usize> BuddyAllocator<CCOUNT> {
/// Just build a inner struct.
pub const fn new() -> Self {
Self(SyncImplRef::new(BuddyInner::<CCOUNT>::new()))
}
/// Just initialize the inner struct.
pub fn init(&self, page_start: *mut PageNode, size: usize) {
self.0.borrow_mut().init(page_start, size)
}
}
unsafe impl<const CCOUNT: usize> GlobalAlloc for BuddyAllocator<CCOUNT> {
/// Do alloc.
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
self.0.borrow_mut().alloc(layout)
}
/// Do dealloc.
unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
self.0.borrow_mut().dealloc(ptr, layout)
}
}