Skip to main content

rustubs/mm/allocator/
buddy.rs

1// buddy allocator, mimicing that of the linux kernel.
2// very fucking unsafe.
3//
4//
5// TERMINOLOGY: (TODO: move to docs)
6//
7//                             |       | page size
8// +---------------------------+-------+-----------+
9// |                           | order | 12 bits   |
10// +---------------------------+-------+-----------+
11// |<--Physical Block Number-->|       |           |
12// |                                   |           |
13// |<--Physical Frame Number (PFN)---->|           |
14// |                                               |
15// |<--Physical Address (PA) --------------------->|
16//
17// BlockID		:= (PA - ZONE_BASE) << (order + 12)
18// BuddyBlockID := BlockID ^ 1
19// BuddyPFN     := PFN ^ (1 << order)
20
21use core::{
22	fmt,
23	ops::Range,
24	ptr::{self, null_mut},
25};
26
27use crate::defs;
28use defs::Mem::*;
29
30#[derive(Copy, Clone)]
31struct Node {
32	next: *const Node,
33	prev: *const Node,
34}
35
36type FreeList = Node;
37
38/// return type of the buddy allocation, describes the allocated physical memory
39/// frame(s)
40/// - `pfn`: physical frame number of the starting address
41/// - `order` : number of allocated frames in 2's order
42pub struct FrameDesc {
43	pfn: u64,     // paddr = pfn << pageshift;
44	order: usize, // block_size == page_size << order
45}
46
47impl FrameDesc {
48	pub fn get_size(&self) -> usize {
49		return (PAGE_SIZE << self.order) as usize;
50	}
51	pub fn get_paddr(&self) -> u64 { return self.pfn << PAGE_SHIFT; }
52	pub fn from_paddr(paddr: u64, order: usize) -> FrameDesc {
53		return Self { pfn: paddr >> PAGE_SHIFT, order };
54	}
55}
56
57#[derive(Debug)]
58/// A contiguous physical memory range to be managed by a (buddy) allocator.
59/// `free_areas` : free-lists for difference sizes (in order of 2).
60///
61/// IMPORTANT: the Zone, or the global ZONE object is unprotected in any sense.
62/// One must synchronize the operations via a sane Allocator object.
63pub struct Zone {
64	pa_range: Range<u64>,
65	free_areas: [FreeArea; PFA_NR_PAGE_ORDER],
66}
67
68// the implementations of Zone only provides meta information. Any
69// allocation-related method should be implemented in the Allocator
70impl Zone {
71	pub const fn default() -> Self {
72		Self {
73			pa_range: Range::<u64> { start: 0, end: 0 },
74			free_areas: [FreeArea::default(); PFA_NR_PAGE_ORDER],
75		}
76	}
77
78	// pfn offset = (pa - base) >> PAGESHIFT
79	pub fn get_buddy(&self, frame: &FrameDesc) -> Option<FrameDesc> {
80		// we assume that the reference frame is valid wrt. the zone.
81		// .. also a few extra steps in calculation to make it more readable.
82		let pa: u64 = frame.get_paddr();
83		let offset_pfn = (pa - self.pa_range.start) >> PAGE_SHIFT;
84		let buddy_pfn = offset_pfn ^ (1 << frame.order);
85		let buddy = FrameDesc { pfn: buddy_pfn, order: frame.order };
86
87		// since we don't enforce the memory size to be power of 2, some blocks
88		// may not have a buddy. In which case we'll treat its psuedo-buddy as
89		// permanently "occupied"
90		if !self.contains(&buddy) {
91			return None;
92		}
93		Some(FrameDesc { pfn: buddy_pfn, order: frame.order })
94	}
95
96	// similar to get_buddy, but gets buddy pa via ref pa.
97	// I don't mind repeat a few lines of code, btw.
98	pub fn get_buddy_by_pa(&self, pa: u64, order: usize) -> Option<u64> {
99		let offset_pfn = (pa - self.pa_range.start) >> PAGE_SHIFT;
100		let buddy_offset_pfn = offset_pfn ^ (1 << order);
101		let buddy_offset_pa = buddy_offset_pfn << PAGE_SHIFT;
102		let buddy_pa = buddy_offset_pa + self.pa_range.start;
103		if !self.pa_range.contains(&buddy_pa) {
104			return None;
105		}
106		return Some(buddy_pa);
107	}
108
109	#[inline]
110	pub fn contains(&self, frame: &FrameDesc) -> bool {
111		let pa: u64 = frame.get_paddr();
112		let pa_end = pa + frame.get_size() as u64;
113		return self.pa_range.contains(&pa) && self.pa_range.contains(&pa_end);
114	}
115}
116
117#[derive(Copy, Clone)]
118struct FreeArea {
119	list_head: FreeList,
120	nr_free: usize,
121	order: usize,
122}
123
124impl fmt::Debug for FreeArea {
125	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126		write!(f, "free: {}", self.nr_free)
127	}
128}
129
130impl FreeArea {
131	pub const fn default() -> FreeArea {
132		FreeArea {
133			list_head: FreeList {
134				next: ptr::null_mut::<Node>(),
135				prev: ptr::null_mut::<Node>(),
136			},
137			nr_free: 0,
138			order: 0,
139		}
140	}
141
142	// this linked list uses the list head as sentinel
143	pub fn init_empty(&mut self) {
144		self.list_head.next = &self.list_head as *const Node;
145		self.list_head.prev = self.list_head.next;
146	}
147
148	// don't care about ordering ... (yet)
149	// let's push and pop both from the one end.
150	// the caller is responsible of making sure n points to valid memory.
151	pub unsafe fn push(&mut self, n: *mut Node) {
152		(*n).next = self.list_head.next;
153		(*n).prev = &(self.list_head);
154		self.list_head.next = n;
155		(*(self.list_head.next as *mut Node)).prev = n;
156		self.nr_free += 1;
157	}
158
159	pub fn pop(&mut self) -> Option<*const Node> {
160		if self.nr_free == 0 {
161			return None;
162		}
163
164		let n = self.list_head.next;
165		unsafe {
166			self.list_head.next = (*n).next;
167			(*((*n).next as *mut Node)).prev = &self.list_head;
168		}
169		self.nr_free -= 1;
170		return Some(n);
171	}
172}
173
174// unlocked buddy allocator. Should use with sync wrapper.
175#[derive(Debug)]
176pub struct BuddyAllocator {
177	zone: Zone,
178}
179
180// very fucking bogus pointer ops which completely lose the point of using rust.
181// But hey, rust doesn't like intrusive linked list anyways..
182impl BuddyAllocator {
183	pub const fn default() -> Self { BuddyAllocator { zone: Zone::default() } }
184
185	// TODO: deal with ill-formed frame object (though unlikely...)
186	// TODO: move this to FreeArea::push
187	// merging is TODO
188	// since we don't require the zone size to be 2's power, a calculated
189	// buddy block (in the top order) could be out of range. However we
190	// won't merge blocks above this order either. So no checking is
191	// necessary here. Here is for sanitychecks whether the to-be-freed
192	// block is ill-formed
193	pub unsafe fn free_frame_by_pa(&mut self, paddr: u64, order: usize) {
194		if let Some(va) = defs::P2V(paddr) {
195			let n = va as *mut Node;
196			self.zone.free_areas[order].push(n);
197		} else {
198			return;
199		}
200	}
201
202	pub unsafe fn free_frame_by_desc(&mut self, frame: &FrameDesc) {
203		self.free_frame_by_pa(frame.get_paddr(), frame.order);
204	}
205
206	pub fn print_debug_info(&self) {
207		for i in &self.zone.free_areas {
208			print!("{}:{}  ;", i.order, i.nr_free);
209		}
210		print!("\n");
211	}
212
213	pub fn alloc_frame(&mut self, order: usize) -> Option<FrameDesc> {
214		let n = self.recursive_split_down(order);
215		n.map(|n| FrameDesc {
216			pfn: defs::V2P(n as u64).unwrap() >> PAGE_SHIFT,
217			order,
218		})
219	}
220
221	// split an order N block in two order N - 1 blk. No return.
222	fn split_buddy_block(&mut self, n: *mut Node, order: usize) {
223		let pa = defs::V2P(n as u64).unwrap();
224		let buddy_pa = self.zone.get_buddy_by_pa(pa, order - 1);
225
226		if buddy_pa.is_none() {
227			return;
228		}
229
230		let buddy_pa = buddy_pa.unwrap();
231		let buddy_node = defs::P2V(buddy_pa).unwrap() as *mut Node;
232
233		let fa_down = &mut self.zone.free_areas[order - 1];
234		unsafe {
235			fa_down.push(buddy_node);
236		}
237	}
238
239	// do_split: whether split the block on the non-recuseive (i.e. first)  match
240	fn recursive_split_down(&mut self, order: usize) -> Option<*const Node> {
241		if order > PFA_MAX_PAGE_ORDER {
242			return None;
243		}
244		let n = self.zone.free_areas[order].pop();
245
246		if let Some(n) = n {
247			Some(n)
248		} else {
249			let n_higher = self.recursive_split_down(order + 1)?;
250			self.split_buddy_block(n_higher as *mut Node, order + 1);
251			return Some(n_higher);
252		}
253	}
254
255	// initialize one single zone for PFA
256	/// [start: end) the physical address range to initialize the PFA.
257	/// unsafe because it assumes no data race. This could only work safely during
258	/// system initialization.
259	pub unsafe fn init(&mut self, start: u64, end: u64) {
260		let start = defs::roundup_4k(start);
261		let end = defs::rounddown_4k(end);
262		let len = end - start;
263		// sanity checks
264		assert!(
265			len >= MIN_PHY_MEM,
266			"TO LITTLE RAM ...{:X}-{:X} = {}",
267			start,
268			end,
269			end - start
270		);
271
272		// decompose the whole physical range into max order chunks
273		// For simplicity, the tail <= max size (order 10 == 4MB) will not be used.
274		const MAX_ORDER_BLKSIZE: u64 = PAGE_SIZE << PFA_MAX_PAGE_ORDER;
275		const {
276			assert!(MAX_ORDER_BLKSIZE == 4 * M, "sancheck failed");
277		}
278
279		self.zone.pa_range = start..end;
280
281		// initialize all free areas and list heads.
282		for i in PFA_ORDER_RANGE {
283			let fa = &mut self.zone.free_areas[i];
284			fa.order = i;
285			fa.init_empty();
286		}
287
288		// populate the highest order free areas
289		let list_head = &(self.zone.free_areas[PFA_MAX_PAGE_ORDER].list_head)
290			as *const Node;
291
292		let mut prev_node_ptr = list_head;
293		let mut curr = start; // the starting address (aligned)
294		let mut n: *mut Node = null_mut::<Node>();
295		let mut free_blocks: usize = 0;
296
297		while curr + MAX_ORDER_BLKSIZE <= end {
298			n = defs::P2V(curr).unwrap() as *mut Node;
299
300			unsafe {
301				(*n).prev = prev_node_ptr;
302				(*(prev_node_ptr as *mut Node)).next = n as *const Node;
303			}
304
305			prev_node_ptr = n;
306			free_blocks += 1;
307			curr += MAX_ORDER_BLKSIZE;
308		}
309
310		// n points to the last free node
311		if !n.is_null() {
312			unsafe {
313				(*n).next = list_head;
314				(*(list_head as *mut Node)).prev = n;
315			}
316		}
317
318		self.zone.free_areas[PFA_MAX_PAGE_ORDER].nr_free = free_blocks;
319	}
320}