diff --git a/Cargo.lock b/Cargo.lock index be73e2b..4bcd3b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,13 +6,38 @@ version = 4 name = "NovaError" version = "0.1.0" +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "heap" version = "0.1.0" dependencies = [ "NovaError", + "rand", ] +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + [[package]] name = "libm" version = "0.2.15" @@ -27,3 +52,117 @@ dependencies = [ "heap", "libm", ] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/heap/.cargo/config.toml b/heap/.cargo/config.toml new file mode 100644 index 0000000..e69de29 diff --git a/heap/Cargo.toml b/heap/Cargo.toml index 5665fac..ae5a233 100644 --- a/heap/Cargo.toml +++ b/heap/Cargo.toml @@ -5,3 +5,6 @@ edition = "2024" [dependencies] NovaError = {path = "../NovaError"} + +[dev-dependencies] +rand = "0.9.2" diff --git a/heap/src/lib.rs b/heap/src/lib.rs index 99e193a..e728c1a 100644 --- a/heap/src/lib.rs +++ b/heap/src/lib.rs @@ -3,35 +3,28 @@ use core::{ alloc::GlobalAlloc, - default::Default, mem::size_of, prelude::v1::*, - ptr::{self, null_mut, read_volatile}, + ptr::{self, null_mut}, result::Result, }; use NovaError::NovaError; -#[cfg(not(target_os = "none"))] -extern crate std; - extern crate alloc; #[repr(C, align(16))] +#[derive(Clone, Copy)] pub struct HeapHeader { - pub next: *mut HeapHeader, - before: *mut HeapHeader, - pub size: usize, + next: Option<*mut HeapHeader>, + before: Option<*mut HeapHeader>, + size: usize, free: bool, } const HEAP_HEADER_SIZE: usize = size_of::(); const MIN_BLOCK_SIZE: usize = 16; -// TODO: This implementation has to be reevaluated when implementing multiprocessing -// Spinlock could be a solution but has its issues: -// https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html - pub struct Heap { pub start_address: *mut HeapHeader, pub end_address: *mut HeapHeader, @@ -50,14 +43,14 @@ impl Heap { self.start_address = heap_start as *mut HeapHeader; self.end_address = heap_end as *mut HeapHeader; - self.raw_size = heap_end - heap_start; + self.raw_size = heap_end - heap_start + 1; unsafe { ptr::write( self.start_address, HeapHeader { - next: null_mut(), - before: null_mut(), + next: None, + before: None, size: self.raw_size - HEAP_HEADER_SIZE, free: true, }, @@ -68,10 +61,11 @@ impl Heap { unsafe fn find_first_fit(&self, size: usize) -> Result<*mut HeapHeader, NovaError> { let mut current = self.start_address; while !fits(size, current) { - if (*self.start_address).next.is_null() { + if let Some(next) = (*self.start_address).next { + current = next; + } else { return Err(NovaError::HeapFull); } - current = (*current).next; } Ok(current) } @@ -113,8 +107,8 @@ impl Heap { // Handle case where fragmenting center free space let next = (*current).next; - if !(*current).next.is_null() { - (*next).before = new_address; + if let Some(next) = next { + (*next).before = Some(new_address); } unsafe { @@ -122,35 +116,38 @@ impl Heap { new_address as *mut HeapHeader, HeapHeader { next, - before: current, - size: (*current).size - size - HEAP_HEADER_SIZE, + before: Some(current), + size: (*current).size - byte_offset, free: true, }, ) }; - (*current).next = new_address; + (*current).next = Some(new_address); (*current).free = false; (*current).size = size; } pub fn free(&self, pointer: *mut u8) -> Result<(), NovaError> { - let mut segment = unsafe { pointer.sub(HEAP_HEADER_SIZE) as *mut HeapHeader }; + let mut segment = Self::get_header_ref_from_data_pointer(pointer); unsafe { // IF prev is free: // Delete header, add size to previous and fix pointers. // Move Head left - if !(*segment).before.is_null() && (*(*segment).before).free { - let before_head = (*segment).before; - (*before_head).size += (*segment).size + HEAP_HEADER_SIZE; - delete_header(segment); - segment = before_head; + if let Some(before_head) = (*segment).before { + if (*before_head).free { + (*before_head).size += (*segment).size + HEAP_HEADER_SIZE; + delete_header(segment); + segment = before_head; + } } + // IF next is free: // Delete next header and merge size, fix pointers - if !(*segment).next.is_null() && (*(*segment).next).free { - let next_head = (*segment).next; - (*segment).size += (*next_head).size + HEAP_HEADER_SIZE; - delete_header(next_head); + if let Some(next_head) = (*segment).next { + if (*next_head).free { + (*segment).size += (*next_head).size + HEAP_HEADER_SIZE; + delete_header(next_head); + } } // Neither: Set free @@ -159,6 +156,10 @@ impl Heap { Ok(()) } + + const fn get_header_ref_from_data_pointer(pointer: *mut u8) -> *mut HeapHeader { + unsafe { pointer.sub(HEAP_HEADER_SIZE) as *mut HeapHeader } + } } unsafe impl GlobalAlloc for Heap { @@ -178,17 +179,17 @@ unsafe fn fits(size: usize, header: *mut HeapHeader) -> bool { } unsafe fn delete_header(header: *mut HeapHeader) { - let before = (*header).before; - let next = (*header).next; + let before_opt = (*header).before; + let next_opt = (*header).next; - if !before.is_null() { - (*before).next = next; + if let Some(before) = before_opt { + (*before).next = next_opt; } - if !next.is_null() { - (*next).before = before; + if let Some(next) = next_opt { + (*next).before = before_opt; } } #[cfg(test)] -mod tests {} +mod tests; diff --git a/heap/src/tests.rs b/heap/src/tests.rs new file mode 100644 index 0000000..7d82076 --- /dev/null +++ b/heap/src/tests.rs @@ -0,0 +1,125 @@ +use super::*; +use rand::{self, random_range}; +extern crate std; + +static HEAP_SIZE: usize = 1024; + +#[test] +fn test_heap_allocation() { + let heap_vector = Box::new([0u8; HEAP_SIZE]); + let mut heap = Heap::empty(); + heap.init( + &heap_vector[0] as *const u8 as usize, + &heap_vector[HEAP_SIZE - 1] as *const u8 as usize, + ); + + let root_header = heap.start_address; + + let malloc_size = random_range(0..(HEAP_SIZE - HEAP_HEADER_SIZE)); + let malloc = heap.malloc(malloc_size).unwrap(); + let malloc_header = Heap::get_header_ref_from_data_pointer(malloc); + + assert_eq!(root_header, malloc_header); + + unsafe { + let actual_alloc_size = (*malloc_header).size; + let actual_raw_size = actual_alloc_size + HEAP_HEADER_SIZE; + // Verify sizing + assert!(actual_alloc_size >= malloc_size); + assert_eq!(actual_alloc_size % MIN_BLOCK_SIZE, 0); + + // Verify section is occupied + assert!((*malloc_header).free == false); + + // Verify next header has been created + let next = (*malloc_header).next.unwrap(); + + assert_eq!(malloc_header.byte_add(actual_raw_size), next); + assert!((*next).free); + assert_eq!((*malloc_header).next.unwrap(), next); + assert_eq!((*next).before.unwrap(), malloc_header); + assert_eq!((*next).size, HEAP_SIZE - actual_raw_size - HEAP_HEADER_SIZE) + } +} + +#[test] +fn test_full_heap() { + let heap_vector = Box::new([0u8; HEAP_SIZE]); + let mut heap = Heap::empty(); + heap.init( + &heap_vector[0] as *const u8 as usize, + &heap_vector[HEAP_SIZE - 1] as *const u8 as usize, + ); + + let malloc_size = HEAP_SIZE - HEAP_HEADER_SIZE; + let malloc = heap.malloc(malloc_size).unwrap(); + let malloc_header = Heap::get_header_ref_from_data_pointer(malloc); + unsafe { + assert_eq!((*malloc_header).free, false); + assert!((*malloc_header).next.is_none()); + } + + let malloc2 = heap.malloc(MIN_BLOCK_SIZE); + assert!(malloc2.is_err()); +} + +#[test] +fn test_freeing_root() { + let heap_vector = Box::new([0u8; HEAP_SIZE]); + let mut heap = Heap::empty(); + heap.init( + &heap_vector[0] as *const u8 as usize, + &heap_vector[HEAP_SIZE - 1] as *const u8 as usize, + ); + + let root_header = heap.start_address; + let root_header_start_size = unsafe { (*root_header).size }; + + let malloc_size = random_range(0..((HEAP_SIZE - HEAP_HEADER_SIZE) / 2)); + let malloc = heap.malloc(malloc_size).unwrap(); + let malloc_header = Heap::get_header_ref_from_data_pointer(malloc); + unsafe { + assert_eq!((*malloc_header).free, false); + assert!((*malloc_header).size >= malloc_size); + assert!((*root_header).next.is_some()); + + assert!(heap.free(malloc).is_ok()); + + assert_eq!((*root_header).size, root_header_start_size); + assert!((*root_header).next.is_none()); + } + + fn test_merging_free_sections() { + let heap_vector = Box::new([0u8; HEAP_SIZE]); + let mut heap = Heap::empty(); + heap.init( + &heap_vector[0] as *const u8 as usize, + &heap_vector[HEAP_SIZE - 1] as *const u8 as usize, + ); + + let root_header = heap.start_address; + let root_header_start_size = unsafe { (*root_header).size }; + + let malloc1 = heap.malloc(MIN_BLOCK_SIZE).unwrap(); + let malloc_header_before = unsafe { *Heap::get_header_ref_from_data_pointer(malloc1) }; + let malloc2 = heap.malloc(MIN_BLOCK_SIZE).unwrap(); + let malloc3 = heap.malloc(MIN_BLOCK_SIZE).unwrap(); + + unsafe { + assert!(heap.free(malloc1).is_ok()); + + let malloc_header_free = *Heap::get_header_ref_from_data_pointer(malloc1); + assert_ne!(malloc_header_before.free, malloc_header_free.free); + assert_eq!(malloc_header_before.size, malloc_header_free.size); + + assert!(heap.free(malloc2).is_ok()); + let malloc_header_merge = *Heap::get_header_ref_from_data_pointer(malloc1); + + assert!(malloc_header_merge.free); + assert_eq!( + malloc_header_merge.size, + malloc_header_free.size + MIN_BLOCK_SIZE + HEAP_HEADER_SIZE + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index 082715a..7985c38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use core::{ extern crate alloc; +use alloc::boxed::Box; use nova::{ framebuffer::{FrameBuffer, BLUE, GREEN, RED}, init_heap, @@ -23,7 +24,6 @@ use nova::{ }, print, println, timer::{delay_nops, sleep_us}, - GLOBAL_ALLOCATOR, }; global_asm!(include_str!("vector.S")); @@ -113,13 +113,8 @@ pub extern "C" fn kernel_main() -> ! { } unsafe fn heap_test() { - let a = GLOBAL_ALLOCATOR.malloc(32).unwrap(); - let b = GLOBAL_ALLOCATOR.malloc(64).unwrap(); - let c = GLOBAL_ALLOCATOR.malloc(128).unwrap(); - let _ = GLOBAL_ALLOCATOR.malloc(256).unwrap(); - GLOBAL_ALLOCATOR.free(b).unwrap(); - GLOBAL_ALLOCATOR.free(a).unwrap(); - GLOBAL_ALLOCATOR.free(c).unwrap(); + let b = Box::new([1, 2, 3, 4]); + println!("{:?}", b); } fn cos(x: u32) -> f64 {