keos/mm/mod.rs
1//! Memory Management.
2//!
3//! This module implements functionality for memory management operations such
4//! as allocating and deallocating memory. The core abstraction is the [`Page`],
5//! which represents a single memory page.
6//!
7//! Memory allocation and deallocation in KeOS is closely tied to Rust's
8//! ownership and lifetime system: A page is allocated by creating an instance
9//! of the [`Page`] struct. Once the [`Page`] instance is dropped, the page is
10//! automatically freed, ensuring proper memory management and preventing memory
11//! leaks.
12pub mod page_table;
13pub mod tlb;
14
15use crate::addressing::{Kva, PAGE_MASK, PAGE_SHIFT, Pa};
16use abyss::{boot::Regions, spinlock::SpinLock};
17use alloc::vec::Vec;
18use core::{
19 ops::Range,
20 sync::atomic::{AtomicU64, Ordering},
21};
22
23/// A reference of a memory page.
24///
25/// `PageRef` represents a borrowed reference to a kernel virtual address
26/// (`Kva`) that maps to a memory.
27///
28/// # Usage
29/// This struct is useful for safely accessing mapped kernel pages without
30/// requiring ownership transfers. The lifetime parameter `'a` ensures that
31/// the reference does not outlive the memory it points to.
32pub struct PageRef<'a> {
33 kva: Kva,
34 _lt: core::marker::PhantomData<&'a ()>,
35}
36
37impl PageRef<'_> {
38 /// Build a page refernce from physical address.
39 ///
40 /// # Safety
41 /// [`Pa`] must be held by a other object.
42 pub unsafe fn from_pa(pa: Pa) -> Self {
43 PageRef {
44 kva: pa.into_kva(),
45 _lt: core::marker::PhantomData,
46 }
47 }
48
49 /// Increase the page reference count corresponding to.
50 pub fn into_page(&self) -> Page {
51 let page = unsafe { Page::from_pa(self.kva.into_pa()) };
52 Page::into_raw(page.clone());
53 page
54 }
55
56 /// Get the kernel virtual address of this page.
57 ///
58 /// # Returns
59 /// - The kernel virtual address ([`Kva`]) of the page.
60 ///
61 /// ## Example:
62 /// ```rust
63 /// let kva = page.kva(); // Get the kernel virtual address
64 /// ```
65 #[inline]
66 pub fn kva(&self) -> Kva {
67 self.kva
68 }
69
70 /// Get the physical address of this page.
71 ///
72 /// # Returns
73 /// - The physical address ([`Pa`]) of the page.
74 ///
75 /// ## Example:
76 /// ```rust
77 /// let pa = page.pa(); // Get the physical address
78 /// ```
79 #[inline]
80 pub fn pa(&self) -> Pa {
81 self.kva.into_pa()
82 }
83
84 /// Get a reference to the underlying slice of the page (read-only).
85 ///
86 /// This method allows access to the contents of the page as a byte slice.
87 /// The caller can read from the page's memory, but cannot modify it.
88 ///
89 /// # Returns
90 /// - A reference to the byte slice representing the contents of the page.
91 ///
92 /// ## Example:
93 /// ```rust
94 /// let slice = page.inner(); // Get read-only access to the page's content
95 /// ```
96 pub fn inner(&self) -> &[u8] {
97 unsafe { core::slice::from_raw_parts(self.kva().into_usize() as *const u8, 4096) }
98 }
99
100 /// Get a mutable reference to the underlying slice of the page.
101 ///
102 /// This method allows modification of the contents of the page as a byte
103 /// slice. The caller can read from and write to the page's memory.
104 ///
105 /// # Returns
106 /// - A mutable reference to the byte slice representing the contents of the
107 /// page.
108 ///
109 /// ## Example:
110 /// ```rust
111 /// let slice = page.inner_mut();
112 /// ```
113 pub fn inner_mut(&mut self) -> &mut [u8] {
114 unsafe { core::slice::from_raw_parts_mut(self.kva().into_usize() as *mut u8, 4096) }
115 }
116}
117
118/// A representation of a memory page.
119///
120/// The [`Page`] struct encapsulates a single memory page, providing methods to
121/// allocate, access, and manipulate the underlying page's contents.
122///
123/// This page internally holds the reference counts. This counter increases on a
124/// calling of [`Page::clone`], and decreases when the page instance is dropped.
125///
126/// ## Example:
127/// ```
128/// let page = Page::new().unwrap();
129/// let va = page.va(); // Get the virtual address of the page.
130/// let pa = page.pa(); // Get the physical address of the page.
131/// ```
132#[derive(Clone)]
133pub struct Page {
134 inner: ContigPages,
135}
136
137impl Default for Page {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl Page {
144 /// Allocate a new page.
145 ///
146 /// This function allocates a new memory page.
147 #[inline]
148 #[track_caller]
149 pub fn new() -> Self {
150 let loc = core::panic::Location::caller();
151 ContigPages::new(0x1000)
152 .map(|inner| Self { inner })
153 .inspect(|pg| {
154 crate::thread::with_current(|th| {
155 let mut guard = th.allocations.lock();
156 if let Some(alloc) = &mut *guard {
157 assert!(alloc.insert(pg.kva(), loc).is_none())
158 }
159 guard.unlock();
160 });
161 })
162 .expect("Failed to allocate page.")
163 }
164
165 /// Get the kernel virtual address of this page.
166 ///
167 /// # Returns
168 /// - The kernel virtual address ([`Kva`]) of the page.
169 #[inline]
170 pub fn kva(&self) -> Kva {
171 self.inner.kva
172 }
173
174 /// Get the physical address of this page.
175 ///
176 /// # Returns
177 /// - The physical address ([`Pa`]) of the page.
178 #[inline]
179 pub fn pa(&self) -> Pa {
180 self.inner.kva.into_pa()
181 }
182
183 /// Consumes the page, returning its physical address.
184 ///
185 /// This method "consumes" the [`Page`] and returns its physical address.
186 /// After calling this function, the caller is responsible for managing
187 /// the memory previously associated with the page. It is important to
188 /// properly release the page, which can be done using [`Page::from_pa`].
189 ///
190 /// # Returns
191 /// - The physical address ([`Pa`]) of the page.
192 #[inline]
193 pub fn into_raw(self) -> Pa {
194 core::mem::ManuallyDrop::new(self).pa()
195 }
196
197 /// Constructs a page from a given physical address.
198 ///
199 /// This method reconstructs a [`Page`] from a physical address ([`Pa`]). It
200 /// should be used only after consuming a [`Page`] with
201 /// [`Page::into_raw`]. The physical address passed must be valid.
202 ///
203 /// # Safety
204 /// - This function is unsafe because incorrect usage could result in memory
205 /// issues such as a double-free.
206 /// - Ensure that the physical address passed is valid and is being used
207 /// correctly.
208 ///
209 /// # Arguments
210 /// - `pa`: The physical address of the page.
211 ///
212 /// # Returns
213 /// - A [`Page`] reconstructed from the physical address.
214 #[inline]
215 pub unsafe fn from_pa(pa: Pa) -> Self {
216 Page {
217 inner: unsafe { ContigPages::from_va(pa.into_kva(), 0x1000) },
218 }
219 }
220
221 /// Get a reference to the underlying slice of the page (read-only).
222 ///
223 /// This method allows access to the contents of the page as a byte slice.
224 /// The caller can read from the page's memory, but cannot modify it.
225 ///
226 /// # Returns
227 /// - A reference to the byte slice representing the contents of the page.
228 pub fn inner(&self) -> &[u8] {
229 unsafe { core::slice::from_raw_parts(self.kva().into_usize() as *const u8, 4096) }
230 }
231
232 /// Get a mutable reference to the underlying slice of the page.
233 ///
234 /// This method allows modification of the contents of the page as a byte
235 /// slice. The caller can read from and write to the page's memory.
236 ///
237 /// # Returns
238 /// - A mutable reference to the byte slice representing the contents of the
239 /// page.
240 pub fn inner_mut(&mut self) -> &mut [u8] {
241 unsafe { core::slice::from_raw_parts_mut(self.kva().into_usize() as *mut u8, 4096) }
242 }
243}
244
245impl Drop for Page {
246 fn drop(&mut self) {
247 crate::thread::with_current(|th| {
248 let mut guard = th.allocations.lock();
249 if let Some(alloc) = &mut *guard {
250 assert!(alloc.remove(&self.kva()).is_some());
251 }
252 guard.unlock();
253 });
254 }
255}
256
257struct BytePP(usize);
258impl core::fmt::Display for BytePP {
259 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260 if self.0 > 16 * 1024 * 1024 * 1024 {
261 write!(f, "{} GiB", self.0 / 1024 / 1024 / 1024)
262 } else if self.0 > 16 * 1024 * 1024 {
263 write!(f, "{} MiB", self.0 / 1024 / 1024)
264 } else if self.0 > 16 * 1024 {
265 write!(f, "{} KiB", self.0 / 1024)
266 } else {
267 write!(f, "{} B", self.0)
268 }
269 }
270}
271
272/// Initialize the physical memory allocator.
273#[doc(hidden)]
274pub unsafe fn init_mm(regions: Regions) {
275 unsafe {
276 unsafe extern "C" {
277 static __edata_end: u64;
278 }
279
280 let edata_end = Kva::new(&__edata_end as *const _ as usize).unwrap();
281
282 for region in regions.iter() {
283 if region.usable {
284 let Range { start, end } = region.addr;
285 let (start, end) = (start.into_kva().max(edata_end), end.into_kva());
286 if start < end {
287 info!(
288 " Usable: 0x{:016x}~0x{:016x} ({})",
289 start.into_usize(),
290 end.into_usize(),
291 BytePP(end.into_usize() - start.into_usize())
292 );
293 let mut allocator = PALLOC.lock();
294 allocator.foster(start, end);
295 allocator.unlock();
296 }
297 }
298 }
299 }
300}
301
302// Physical memory allocators.
303struct Arena {
304 start: Kva,
305 end: Kva,
306 // 0: used, 1: unused
307 bitmap: &'static mut [u64],
308 ref_cnts: &'static [AtomicU64],
309}
310
311impl Arena {
312 const EMPTY: Option<Self> = None;
313 fn set_used(&mut self, index: usize) {
314 let (pos, ofs) = (index / 64, index % 64);
315 debug_assert_ne!(self.bitmap[pos] & (1 << ofs), 0);
316 self.bitmap[pos] &= !(1 << ofs);
317 debug_assert_eq!(self.bitmap[pos] & (1 << ofs), 0);
318 }
319 fn set_unused(&mut self, index: usize) {
320 let (pos, ofs) = (index / 64, index % 64);
321 debug_assert_eq!(self.bitmap[pos] & (1 << ofs), 0);
322 self.bitmap[pos] |= 1 << ofs;
323 debug_assert_ne!(self.bitmap[pos] & (1 << ofs), 0);
324 }
325 fn alloc(&mut self, cnt: usize, align: usize) -> Option<(Kva, &'static AtomicU64)> {
326 let mut search = 0;
327 while search < self.bitmap.len() * 64 {
328 let (mut pos, ofs) = (search / 64, search % 64);
329 // search first qword that contains one.
330 if ofs % 64 == 0 {
331 while self.bitmap[pos] == 0 {
332 pos += 1;
333 }
334 search = pos * 64;
335 }
336
337 let mut cont = 0;
338 if align != 0
339 && !((self.start.into_usize() >> PAGE_SHIFT) + search).is_multiple_of(align)
340 {
341 search += 1;
342 } else {
343 let start = search;
344 loop {
345 // Found!
346 if cont == cnt {
347 for i in start..start + cnt {
348 self.set_used(i);
349 }
350 let ref_cnt = &self.ref_cnts[start];
351 debug_assert_eq!(
352 ref_cnt.fetch_add(1, core::sync::atomic::Ordering::SeqCst),
353 0
354 );
355 return Some((self.start + (start << PAGE_SHIFT), ref_cnt));
356 }
357
358 let (pos, ofs) = (search / 64, search % 64);
359 search += 1;
360 if self.bitmap[pos] & (1 << ofs) != 0 {
361 // usable
362 cont += 1;
363 } else {
364 break;
365 }
366 }
367 }
368 }
369 None
370 }
371 fn dealloc(&mut self, va: Kva, cnt: usize) {
372 let ofs = (va.into_usize() - self.start.into_usize()) >> PAGE_SHIFT;
373 for i in ofs..ofs + cnt {
374 self.set_unused(i);
375 }
376 }
377 fn ref_cnt_for_va(&self, va: Kva) -> &'static AtomicU64 {
378 &self.ref_cnts[(va - self.start) >> PAGE_SHIFT]
379 }
380}
381
382struct PhysicalAllocator {
383 inner: [Option<Arena>; 8],
384 max_idx: usize,
385}
386
387static PALLOC: SpinLock<PhysicalAllocator> = SpinLock::new(PhysicalAllocator {
388 inner: [Arena::EMPTY; 8],
389 max_idx: 0,
390});
391
392impl PhysicalAllocator {
393 unsafe fn foster(&mut self, start: Kva, end: Kva) {
394 unsafe {
395 // Calculate usable page of this region.
396 let usable_pages = (end.into_usize() - start.into_usize()) >> PAGE_SHIFT;
397 let mut meta_end = start;
398 // Each region has alloc bitmap on first N pages.
399 let bitmap = core::slice::from_raw_parts_mut(
400 start.into_usize() as *mut u64,
401 usable_pages.div_ceil(64),
402 );
403 bitmap.fill(u64::MAX);
404 meta_end += 8 * bitmap.len();
405 // Array for reference counts are following to the bitmap.
406 core::slice::from_raw_parts_mut(meta_end.into_usize() as *mut u64, usable_pages)
407 .fill(0);
408 let ref_cnts = core::slice::from_raw_parts(
409 meta_end.into_usize() as *const AtomicU64,
410 usable_pages,
411 );
412 meta_end += 8 * ref_cnts.len();
413 meta_end = (meta_end + PAGE_MASK) & !PAGE_MASK;
414
415 let mut arena = Arena {
416 bitmap,
417 start,
418 end,
419 ref_cnts,
420 };
421 // Pad front.
422 for i in 0..(meta_end - start) >> PAGE_SHIFT {
423 arena.set_used(i);
424 }
425 // Pad back.
426 for i in usable_pages..((usable_pages + 63) & !63) {
427 arena.set_used(i);
428 }
429 self.inner[self.max_idx] = Some(arena);
430 self.max_idx += 1;
431 }
432 }
433}
434
435/// A contiguous pages representation.
436pub struct ContigPages {
437 arena_idx: usize,
438 kva: Kva,
439 cnt: usize,
440 ref_cnt: &'static AtomicU64,
441}
442
443impl Clone for ContigPages {
444 fn clone(&self) -> Self {
445 self.ref_cnt.fetch_add(1, Ordering::SeqCst);
446
447 Self {
448 arena_idx: self.arena_idx,
449 kva: self.kva,
450 cnt: self.cnt,
451 ref_cnt: self.ref_cnt,
452 }
453 }
454}
455
456impl ContigPages {
457 /// Allocate a page.
458 #[inline]
459 pub fn new(size: usize) -> Option<Self> {
460 Self::new_with_align(size, 0x1000)
461 }
462
463 /// Allocate a page with align
464 #[inline]
465 pub fn new_with_align(size: usize, align: usize) -> Option<Self> {
466 if size != 0 {
467 // align up to page size.
468 let cnt = (size + PAGE_MASK) >> PAGE_SHIFT;
469 let mut allocator = PALLOC.lock();
470 let max_idx = allocator.max_idx;
471 for (arena_idx, arena) in allocator.inner.iter_mut().take(max_idx).enumerate() {
472 if let Some((kva, ref_cnt)) =
473 arena.as_mut().unwrap().alloc(cnt, align >> PAGE_SHIFT)
474 {
475 unsafe {
476 core::slice::from_raw_parts_mut(
477 kva.into_usize() as *mut u64,
478 cnt * 0x1000 / core::mem::size_of::<u64>(),
479 )
480 .fill(0);
481 }
482 allocator.unlock();
483 return Some(Self {
484 arena_idx,
485 kva,
486 cnt,
487 ref_cnt,
488 });
489 }
490 }
491 }
492 None
493 }
494
495 /// Get virtual address of this page.
496 #[inline]
497 pub fn kva(&self) -> Kva {
498 self.kva
499 }
500
501 /// Constructs a page from a kva.
502 ///
503 /// ## Safety
504 /// This function is unsafe because improper use may lead to memory
505 /// problems. For example, a double-free may occur if the function is called
506 /// twice on the same raw pointer.
507 #[inline]
508 pub unsafe fn from_va(kva: Kva, size: usize) -> Self {
509 let allocator = PALLOC.lock();
510 let arena_idx = allocator
511 .inner
512 .iter()
513 .take(allocator.max_idx)
514 .enumerate()
515 .find_map(|(idx, arena)| {
516 let Arena { start, end, .. } = arena.as_ref().unwrap();
517 if (*start..*end).contains(&kva) {
518 Some(idx)
519 } else {
520 None
521 }
522 })
523 .expect("Failed to find arena index.");
524 let page = ContigPages {
525 arena_idx,
526 kva,
527 cnt: size / 4096,
528 ref_cnt: allocator.inner[arena_idx]
529 .as_ref()
530 .unwrap()
531 .ref_cnt_for_va(kva),
532 };
533 allocator.unlock();
534 page
535 }
536
537 /// Split the ContigPages into multiple pages.
538 pub fn split(self) -> Vec<Page> {
539 let mut out = Vec::new();
540 assert_eq!(self.ref_cnt.load(Ordering::SeqCst), 1);
541 let this = core::mem::ManuallyDrop::new(self);
542 for i in 0..this.cnt {
543 let ref_cnt = unsafe {
544 &core::slice::from_raw_parts(this.ref_cnt as *const AtomicU64, this.cnt)[i]
545 };
546 if i != 0 {
547 assert_eq!(ref_cnt.fetch_add(1, Ordering::SeqCst), 1);
548 }
549 out.push(Page {
550 inner: ContigPages {
551 arena_idx: this.arena_idx,
552 kva: this.kva + i * 0x1000,
553 cnt: 1,
554 ref_cnt,
555 },
556 })
557 }
558 out
559 }
560}
561
562impl Drop for ContigPages {
563 fn drop(&mut self) {
564 if self.ref_cnt.fetch_sub(1, Ordering::SeqCst) == 1 {
565 let mut allocator = PALLOC.lock();
566 allocator.inner[self.arena_idx]
567 .as_mut()
568 .unwrap()
569 .dealloc(self.kva, self.cnt);
570 allocator.unlock();
571 }
572 }
573}