Skip to content

Commit 815936a

Browse files
author
ash bek
committed
create linked list allocator
1 parent e0bb33a commit 815936a

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

src/allocator/linked_list.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use super::{align_up, Locked};
2+
use alloc::alloc::{GlobalAlloc, Layout};
3+
use core::{mem, ptr};
4+
5+
/// List node for a linked list allocator
6+
///
7+
/// The node contains its size and a pointer to the next
8+
/// node.
9+
struct ListNode {
10+
size: usize,
11+
next: Option<&'static mut ListNode>,
12+
}
13+
14+
impl ListNode {
15+
const fn new(size: usize) -> Self {
16+
ListNode { size, next: None }
17+
}
18+
19+
/// Return the start address of the node.
20+
fn start_addr(&self) -> usize {
21+
self as *const Self as usize
22+
}
23+
24+
/// Return the end address of the region
25+
///
26+
/// This works because the region itself contains the node, so the end address
27+
/// should always be `self.start_addr() + self.size`
28+
fn end_addr(&self) -> usize {
29+
self.start_addr() + self.size
30+
}
31+
}
32+
33+
pub struct LinkedListAllocator {
34+
head: ListNode,
35+
}
36+
37+
impl LinkedListAllocator {
38+
/// Create a new empty linked list allocator
39+
pub const fn new() -> Self {
40+
LinkedListAllocator {
41+
head: ListNode::new(0),
42+
}
43+
}
44+
45+
/// Initialize the allocator with the given heap bounds.
46+
///
47+
/// This function is unsafe because the caller must guarantee that the given
48+
/// heap bounds are valid and that the heap is unused. This method must be
49+
/// called only once.
50+
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
51+
self.add_free_region(heap_start, heap_size)
52+
}
53+
54+
/// Add a new free region to the allocator
55+
///
56+
/// This function is unsafe as we're operating on raw memory and need to ensure
57+
/// the alignment and size is correct.
58+
unsafe fn add_free_region(&mut self, addr: usize, size: usize) {
59+
// Ensure the size of the free region is capable of holding a node
60+
assert_eq!(align_up(addr, mem::align_of::<ListNode>()), addr);
61+
assert!(size >= mem::size_of::<ListNode>());
62+
63+
// Create a new list node and append it at the start of the list.
64+
let mut node = ListNode::new(size);
65+
node.next = self.head.next.take();
66+
let node_ptr = addr as *mut ListNode;
67+
node_ptr.write(node);
68+
self.head.next = Some(&mut *node_ptr)
69+
}
70+
71+
/// Looks for a free region with the given size and alignment and removes
72+
/// it from the list.
73+
///
74+
/// Returns a tuple of the list node and the start address of the allocation.
75+
fn find_region(&mut self, size: usize, align: usize) -> Option<(&'static mut ListNode, usize)> {
76+
// Ref to the current list node, updated each iteration.
77+
let mut current = &mut self.head;
78+
79+
// Iterate over the list until we find a large enough region.
80+
while let Some(ref mut region) = current.next {
81+
if let Ok(alloc_start) = Self::alloc_from_region(&region, size, align) {
82+
// Region suitable for allocation, remove it from the list.
83+
let next = region.next.take();
84+
let ret = Some((current.next.take().unwrap(), alloc_start));
85+
current.next = next;
86+
return ret;
87+
} else {
88+
// Region not suitable, find another region.
89+
current = current.next.as_mut().unwrap();
90+
}
91+
}
92+
// No suitable regions.
93+
None
94+
}
95+
96+
/// Try to use the given region for an allocation with given size and
97+
/// alignment.
98+
///
99+
/// Returns the allocation start address on success.
100+
fn alloc_from_region(region: &ListNode, size: usize, align: usize) -> Result<usize, ()> {
101+
let alloc_start = align_up(region.start_addr(), align);
102+
let alloc_end = alloc_start.checked_add(size).ok_or(())?;
103+
104+
if alloc_end > region.end_addr() {
105+
// Region is too small for the desired allocation
106+
return Err(());
107+
}
108+
109+
// Region is large enough for the required allocation, now check if the leftover region can contain a node.
110+
let excess_size = region.end_addr() - alloc_end;
111+
if excess_size > 0 && excess_size < mem::size_of::<ListNode>() {
112+
return Err(());
113+
}
114+
115+
// Region suitable for allocation.
116+
Ok(alloc_start)
117+
}
118+
119+
/// Adjust the given layout so that the resulting allocated memory
120+
/// region is also capable of storing a `ListNode`.
121+
///
122+
/// Returns the adjusted size and alignment as a (size, align) tuple.
123+
fn size_align(layout: Layout) -> (usize, usize) {
124+
let layout = layout
125+
.align_to(mem::align_of::<ListNode>())
126+
.expect("Alignment failed")
127+
.pad_to_align();
128+
129+
let size = layout.size().max(mem::size_of::<ListNode>());
130+
131+
(size, layout.align())
132+
}
133+
}
134+
135+
unsafe impl GlobalAlloc for Locked<LinkedListAllocator> {
136+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
137+
// Perform normal layout adjustments
138+
let (size, align) = LinkedListAllocator::size_align(layout);
139+
140+
let mut allocator = self.lock();
141+
142+
if let Some((region, alloc_start)) = allocator.find_region(size, align){
143+
let alloc_end = alloc_start.checked_add(size).expect("Size has overflowed");
144+
let excess_size = region.end_addr() - alloc_end;
145+
if excess_size > 0 {
146+
allocator.add_free_region(alloc_end, excess_size);
147+
}
148+
149+
alloc_start as *mut u8
150+
} else {
151+
ptr::null_mut()
152+
}
153+
}
154+
155+
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
156+
let (size, _) = LinkedListAllocator::size_align(layout);
157+
self.lock().add_free_region(ptr as usize, size)
158+
}
159+
}

0 commit comments

Comments
 (0)