mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-24 10:26:20 +04:00
274 lines
7.2 KiB
Rust
274 lines
7.2 KiB
Rust
// kmem.rs
|
|
// Sub-page level: malloc-like allocation system
|
|
// Stephen Marz
|
|
// 7 October 2019
|
|
|
|
use crate::page::{align_val, zalloc, Table, PAGE_SIZE};
|
|
use core::{mem::size_of, ptr::null_mut};
|
|
|
|
#[repr(usize)]
|
|
enum AllocListFlags {
|
|
Taken = 1 << 63,
|
|
}
|
|
impl AllocListFlags {
|
|
pub fn val(self) -> usize {
|
|
self as usize
|
|
}
|
|
}
|
|
|
|
struct AllocList {
|
|
pub flags_size: usize,
|
|
}
|
|
impl AllocList {
|
|
pub fn is_taken(&self) -> bool {
|
|
self.flags_size & AllocListFlags::Taken.val() != 0
|
|
}
|
|
|
|
pub fn is_free(&self) -> bool {
|
|
!self.is_taken()
|
|
}
|
|
|
|
pub fn set_taken(&mut self) {
|
|
self.flags_size |= AllocListFlags::Taken.val();
|
|
}
|
|
|
|
pub fn set_free(&mut self) {
|
|
self.flags_size &= !AllocListFlags::Taken.val();
|
|
}
|
|
|
|
pub fn set_size(&mut self, sz: usize) {
|
|
let k = self.is_taken();
|
|
self.flags_size = sz & !AllocListFlags::Taken.val();
|
|
if k {
|
|
self.flags_size |= AllocListFlags::Taken.val();
|
|
}
|
|
}
|
|
|
|
pub fn get_size(&self) -> usize {
|
|
self.flags_size & !AllocListFlags::Taken.val()
|
|
}
|
|
}
|
|
|
|
// This is the head of the allocation. We start here when
|
|
// we search for a free memory location.
|
|
static mut KMEM_HEAD: *mut AllocList = null_mut();
|
|
// In the future, we will have on-demand pages
|
|
// so, we need to keep track of our memory footprint to
|
|
// see if we actually need to allocate more.
|
|
static mut KMEM_ALLOC: usize = 0;
|
|
static mut KMEM_PAGE_TABLE: *mut Table = null_mut();
|
|
|
|
// These functions are safe helpers around an unsafe
|
|
// operation.
|
|
pub fn get_head() -> *mut u8 {
|
|
unsafe { KMEM_HEAD as *mut u8 }
|
|
}
|
|
|
|
pub fn get_page_table() -> *mut Table {
|
|
unsafe { KMEM_PAGE_TABLE as *mut Table }
|
|
}
|
|
|
|
pub fn get_num_allocations() -> usize {
|
|
unsafe { KMEM_ALLOC }
|
|
}
|
|
|
|
/// Initialize kernel's memory
|
|
/// This is not to be used to allocate memory
|
|
/// for user processes. If that's the case, use
|
|
/// alloc/dealloc from the page crate.
|
|
pub fn init() {
|
|
unsafe {
|
|
// Allocate kernel pages (KMEM_ALLOC)
|
|
KMEM_ALLOC = 512;
|
|
let k_alloc = zalloc(KMEM_ALLOC);
|
|
assert!(!k_alloc.is_null());
|
|
KMEM_HEAD = k_alloc as *mut AllocList;
|
|
(*KMEM_HEAD).set_free();
|
|
(*KMEM_HEAD).set_size(KMEM_ALLOC * PAGE_SIZE);
|
|
KMEM_PAGE_TABLE = zalloc(1) as *mut Table;
|
|
}
|
|
}
|
|
|
|
/// Allocate sub-page level allocation based on bytes and zero the memory
|
|
pub fn kzmalloc(sz: usize) -> *mut u8 {
|
|
let size = align_val(sz, 3);
|
|
let ret = kmalloc(size);
|
|
|
|
if !ret.is_null() {
|
|
for i in 0..size {
|
|
unsafe {
|
|
(*ret.add(i)) = 0;
|
|
}
|
|
}
|
|
}
|
|
ret
|
|
}
|
|
|
|
/// Allocate sub-page level allocation based on bytes
|
|
pub fn kmalloc(sz: usize) -> *mut u8 {
|
|
unsafe {
|
|
let size = align_val(sz, 3) + size_of::<AllocList>();
|
|
let mut head = KMEM_HEAD;
|
|
// .add() uses pointer arithmetic, so we type-cast into a u8
|
|
// so that we multiply by an absolute size (KMEM_ALLOC *
|
|
// PAGE_SIZE).
|
|
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
|
|
as *mut AllocList;
|
|
|
|
while head < tail {
|
|
if (*head).is_free() && size <= (*head).get_size() {
|
|
let chunk_size = (*head).get_size();
|
|
let rem = chunk_size - size;
|
|
(*head).set_taken();
|
|
if rem > size_of::<AllocList>() {
|
|
let next = (head as *mut u8).add(size)
|
|
as *mut AllocList;
|
|
// There is space remaining here.
|
|
(*next).set_free();
|
|
(*next).set_size(rem);
|
|
(*head).set_size(size);
|
|
}
|
|
else {
|
|
// If we get here, take the entire chunk
|
|
(*head).set_size(chunk_size);
|
|
}
|
|
return head.add(1) as *mut u8;
|
|
}
|
|
else {
|
|
// If we get here, what we saw wasn't a free
|
|
// chunk, move on to the next.
|
|
head = (head as *mut u8).add((*head).get_size())
|
|
as *mut AllocList;
|
|
}
|
|
}
|
|
}
|
|
// If we get here, we didn't find any free chunks--i.e. there isn't
|
|
// enough memory for this. TODO: Add on-demand page allocation.
|
|
null_mut()
|
|
}
|
|
|
|
/// Free a sub-page level allocation
|
|
pub fn kfree(ptr: *mut u8) {
|
|
unsafe {
|
|
if !ptr.is_null() {
|
|
let p = (ptr as *mut AllocList).offset(-1);
|
|
if (*p).is_taken() {
|
|
(*p).set_free();
|
|
}
|
|
// After we free, see if we can combine adjacent free
|
|
// spots to see if we can reduce fragmentation.
|
|
coalesce();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Merge smaller chunks into a bigger chunk
|
|
pub fn coalesce() {
|
|
unsafe {
|
|
let mut head = KMEM_HEAD;
|
|
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
|
|
as *mut AllocList;
|
|
|
|
while head < tail {
|
|
let next = (head as *mut u8).add((*head).get_size())
|
|
as *mut AllocList;
|
|
if (*head).get_size() == 0 {
|
|
// If this happens, then we have a bad heap
|
|
// (double free or something). However, that
|
|
// will cause an infinite loop since the next
|
|
// pointer will never move beyond the current
|
|
// location.
|
|
break;
|
|
}
|
|
else if next >= tail {
|
|
// We calculated the next by using the size
|
|
// given as get_size(), however this could push
|
|
// us past the tail. In that case, the size is
|
|
// wrong, hence we break and stop doing what we
|
|
// need to do.
|
|
break;
|
|
}
|
|
else if (*head).is_free() && (*next).is_free() {
|
|
// This means we have adjacent blocks needing to
|
|
// be freed. So, we combine them into one
|
|
// allocation.
|
|
(*head).set_size(
|
|
(*head).get_size()
|
|
+ (*next).get_size(),
|
|
);
|
|
}
|
|
// If we get here, we might've moved. Recalculate new
|
|
// head.
|
|
head = (head as *mut u8).add((*head).get_size())
|
|
as *mut AllocList;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// For debugging purposes, print the kmem table
|
|
pub fn print_table() {
|
|
unsafe {
|
|
let mut head = KMEM_HEAD;
|
|
let tail = (KMEM_HEAD as *mut u8).add(KMEM_ALLOC * PAGE_SIZE)
|
|
as *mut AllocList;
|
|
while head < tail {
|
|
println!(
|
|
"{:p}: Length = {:<10} Taken = {}",
|
|
head,
|
|
(*head).get_size(),
|
|
(*head).is_taken()
|
|
);
|
|
head = (head as *mut u8).add((*head).get_size())
|
|
as *mut AllocList;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ///////////////////////////////////
|
|
// / GLOBAL ALLOCATOR
|
|
// ///////////////////////////////////
|
|
|
|
// The global allocator allows us to use the data structures
|
|
// in the core library, such as a linked list or B-tree.
|
|
// We want to use these sparingly since we have a coarse-grained
|
|
// allocator.
|
|
use core::alloc::{GlobalAlloc, Layout};
|
|
|
|
// The global allocator is a static constant to a global allocator
|
|
// structure. We don't need any members because we're using this
|
|
// structure just to implement alloc and dealloc.
|
|
struct OsGlobalAlloc;
|
|
|
|
unsafe impl GlobalAlloc for OsGlobalAlloc {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
// We align to the next page size so that when
|
|
// we divide by PAGE_SIZE, we get exactly the number
|
|
// of pages necessary.
|
|
kzmalloc(layout.size())
|
|
}
|
|
|
|
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
|
// We ignore layout since our allocator uses ptr_start -> last
|
|
// to determine the span of an allocation.
|
|
kfree(ptr);
|
|
}
|
|
}
|
|
|
|
#[global_allocator]
|
|
/// Technically, we don't need the {} at the end, but it
|
|
/// reveals that we're creating a new structure and not just
|
|
/// copying a value.
|
|
static GA: OsGlobalAlloc = OsGlobalAlloc {};
|
|
|
|
#[alloc_error_handler]
|
|
/// If for some reason alloc() in the global allocator gets null_mut(),
|
|
/// then we come here. This is a divergent function, so we call panic to
|
|
/// let the tester know what's going on.
|
|
pub fn alloc_error(l: Layout) -> ! {
|
|
panic!(
|
|
"Allocator failed to allocate {} bytes with {}-byte alignment.",
|
|
l.size(),
|
|
l.align()
|
|
);
|
|
}
|