diff --git a/crates/domain/src/error.rs b/crates/domain/src/error.rs index f998b31..0563f9c 100644 --- a/crates/domain/src/error.rs +++ b/crates/domain/src/error.rs @@ -17,4 +17,5 @@ pub enum KernelError { InvalidStateTransition(ThreadId), InvalidThread(ThreadId), TooManyThreads, + OutOfMemory, } diff --git a/crates/domain/src/lib.rs b/crates/domain/src/lib.rs index 0479ac1..20141d1 100644 --- a/crates/domain/src/lib.rs +++ b/crates/domain/src/lib.rs @@ -4,6 +4,7 @@ pub mod capability; pub mod endpoint; pub mod error; pub mod fault; +pub mod memory; pub mod message; pub mod process; pub mod resource; diff --git a/crates/domain/src/memory/mod.rs b/crates/domain/src/memory/mod.rs new file mode 100644 index 0000000..2b1f366 --- /dev/null +++ b/crates/domain/src/memory/mod.rs @@ -0,0 +1,95 @@ +use types::{ + address::PhysicalAddress, + bounded::PageCount, + generation::Generation, + id::{ProcessId, RegionId}, +}; + +pub mod page_pool; +pub mod process_memory; + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; + +const MAX_REGIONS: usize = 256; + +pub struct MemoryRegion { + id: RegionId, + owner: ProcessId, + base: PhysicalAddress, + size: PageCount, + generation: Generation, +} + +impl MemoryRegion { + pub fn new( + id: RegionId, + owner: ProcessId, + base: PhysicalAddress, + size: PageCount, + generation: Generation, + ) -> Self { + Self { + id, + owner, + base, + size, + generation, + } + } + + pub fn id(&self) -> RegionId { + self.id + } + + pub fn owner(&self) -> ProcessId { + self.owner + } + + pub fn base(&self) -> PhysicalAddress { + self.base + } + + pub fn size(&self) -> PageCount { + self.size + } + + pub fn generation(&self) -> &Generation { + &self.generation + } +} + +pub struct RegionTable { + regions: [Option; MAX_REGIONS], +} + +impl RegionTable { + pub fn new() -> Self { + Self { + regions: [const { None }; MAX_REGIONS], + } + } + + pub fn insert(&mut self, region: MemoryRegion) -> Option { + let idx = self.regions.iter().position(|s| s.is_none())?; + let id = RegionId::from_raw(idx as u16); + self.regions[idx] = Some(region); + Some(id) + } + + pub fn get(&self, id: RegionId) -> Option<&MemoryRegion> { + self.regions.get(id.raw() as usize).and_then(|s| s.as_ref()) + } + + pub fn remove(&mut self, id: RegionId) -> Option { + self.regions.get_mut(id.raw() as usize).and_then(|s| s.take()) + } + + pub fn regions_for_process(&self, process: ProcessId) -> impl Iterator { + self.regions + .iter() + .filter_map(|s| s.as_ref()) + .filter(move |r| r.owner() == process) + } +} diff --git a/crates/domain/src/memory/page_pool.rs b/crates/domain/src/memory/page_pool.rs new file mode 100644 index 0000000..23c077a --- /dev/null +++ b/crates/domain/src/memory/page_pool.rs @@ -0,0 +1,68 @@ +use types::address::PhysicalAddress; + +const MAX_PAGES: usize = 16384; + +pub struct PagePool { + pages: [bool; MAX_PAGES], + base: PhysicalAddress, + total: usize, + free_count: usize, +} + +impl PagePool { + pub fn new(base: PhysicalAddress, total_pages: usize) -> Self { + let mut pages = [false; MAX_PAGES]; + for i in 0..total_pages { + pages[i] = true; + } + Self { + pages, + base, + total: total_pages, + free_count: total_pages, + } + } + + pub fn allocate(&mut self, count: usize) -> Option { + let start = self.find_contiguous(count)?; + for i in start..start + count { + self.pages[i] = false; + } + self.free_count -= count; + Some(self.base.offset(start)) + } + + pub fn free(&mut self, addr: PhysicalAddress, count: usize) -> bool { + if addr.raw() < self.base.raw() { + return false; + } + let start = (addr.raw() - self.base.raw()) / 4096; + if start + count > self.total { + return false; + } + for i in start..start + count { + self.pages[i] = true; + } + self.free_count += count; + true + } + + pub fn free_count(&self) -> usize { + self.free_count + } + + fn find_contiguous(&self, count: usize) -> Option { + let mut run = 0; + for i in 0..self.total { + if self.pages[i] { + run += 1; + if run == count { + return Some(i + 1 - count); + } + } else { + run = 0; + } + } + None + } +} diff --git a/crates/domain/src/memory/process_memory.rs b/crates/domain/src/memory/process_memory.rs new file mode 100644 index 0000000..d8eff63 --- /dev/null +++ b/crates/domain/src/memory/process_memory.rs @@ -0,0 +1,47 @@ +use types::id::ProcessId; + +use crate::error::KernelError; + +pub struct ProcessMemory { + process: ProcessId, + page_count: usize, + limit: usize, +} + +impl ProcessMemory { + pub fn new(process: ProcessId, limit: usize) -> Self { + Self { + process, + page_count: 0, + limit, + } + } + + pub fn process(&self) -> ProcessId { + self.process + } + + pub fn page_count(&self) -> usize { + self.page_count + } + + pub fn limit(&self) -> usize { + self.limit + } + + pub fn grant(&mut self, count: usize) -> Result<(), KernelError> { + if self.page_count + count > self.limit { + return Err(KernelError::OutOfMemory); + } + self.page_count += count; + Ok(()) + } + + pub fn release(&mut self, count: usize) -> Result<(), KernelError> { + if count > self.page_count { + return Err(KernelError::OutOfMemory); + } + self.page_count -= count; + Ok(()) + } +} diff --git a/crates/domain/src/memory/tests.rs b/crates/domain/src/memory/tests.rs new file mode 100644 index 0000000..106c452 --- /dev/null +++ b/crates/domain/src/memory/tests.rs @@ -0,0 +1,207 @@ +use super::page_pool::PagePool; +use super::process_memory::ProcessMemory; +use super::*; +use types::address::PhysicalAddress; +use types::bounded::PageCount; +use types::generation::Generation; +use types::id::{ProcessId, RegionId}; + +fn base() -> PhysicalAddress { + PhysicalAddress::from_raw(0x8000_0000) +} + +fn process(id: u16) -> ProcessId { + ProcessId::from_raw(id) +} + +fn pages(n: u16) -> PageCount { + PageCount::new(n).unwrap() +} + +// --- PagePool tests --- + +#[test] +fn new_pool_has_all_pages_free() { + let pool = PagePool::new(base(), 100); + assert_eq!(pool.free_count(), 100); +} + +#[test] +fn allocate_reduces_free_count() { + let mut pool = PagePool::new(base(), 100); + pool.allocate(10).unwrap(); + assert_eq!(pool.free_count(), 90); +} + +#[test] +fn allocate_returns_base_aligned_address() { + let mut pool = PagePool::new(base(), 100); + let addr = pool.allocate(1).unwrap(); + assert_eq!(addr, base()); +} + +#[test] +fn allocate_sequential_returns_contiguous() { + let mut pool = PagePool::new(base(), 100); + let a = pool.allocate(4).unwrap(); + let b = pool.allocate(4).unwrap(); + assert_eq!(a, base()); + assert_eq!(b, base().offset(4)); +} + +#[test] +fn allocate_too_many_returns_none() { + let mut pool = PagePool::new(base(), 10); + assert!(pool.allocate(11).is_none()); +} + +#[test] +fn free_then_reallocate() { + let mut pool = PagePool::new(base(), 10); + let addr = pool.allocate(5).unwrap(); + assert_eq!(pool.free_count(), 5); + assert!(pool.free(addr, 5)); + assert_eq!(pool.free_count(), 10); + let addr2 = pool.allocate(5).unwrap(); + assert_eq!(addr2, base()); +} + +#[test] +fn free_out_of_range_returns_false() { + let mut pool = PagePool::new(base(), 10); + assert!(!pool.free(PhysicalAddress::from_raw(0), 1)); +} + +#[test] +fn allocate_fragmented_finds_gap() { + let mut pool = PagePool::new(base(), 10); + let a = pool.allocate(3).unwrap(); + let _b = pool.allocate(3).unwrap(); + let _c = pool.allocate(3).unwrap(); + pool.free(a, 3); + // now pages 0-2 are free, 3-8 allocated, 9 free + // asking for 3 contiguous should find 0-2 + let d = pool.allocate(3).unwrap(); + assert_eq!(d, base()); +} + +#[test] +fn allocate_exact_remaining() { + let mut pool = PagePool::new(base(), 5); + let addr = pool.allocate(5).unwrap(); + assert_eq!(addr, base()); + assert_eq!(pool.free_count(), 0); + assert!(pool.allocate(1).is_none()); +} + +// --- ProcessMemory tests --- + +#[test] +fn new_process_memory_has_zero_pages() { + let pm = ProcessMemory::new(process(0), 100); + assert_eq!(pm.page_count(), 0); + assert_eq!(pm.limit(), 100); +} + +#[test] +fn grant_pages_within_limit() { + let mut pm = ProcessMemory::new(process(0), 100); + assert!(pm.grant(10).is_ok()); + assert_eq!(pm.page_count(), 10); +} + +#[test] +fn grant_pages_exceeding_limit_fails() { + let mut pm = ProcessMemory::new(process(0), 10); + assert!(pm.grant(11).is_err()); + assert_eq!(pm.page_count(), 0); +} + +#[test] +fn release_pages_reduces_count() { + let mut pm = ProcessMemory::new(process(0), 100); + pm.grant(20).unwrap(); + assert!(pm.release(5).is_ok()); + assert_eq!(pm.page_count(), 15); +} + +#[test] +fn release_more_than_owned_fails() { + let mut pm = ProcessMemory::new(process(0), 100); + pm.grant(5).unwrap(); + assert!(pm.release(10).is_err()); + assert_eq!(pm.page_count(), 5); +} + +#[test] +fn grant_up_to_exact_limit() { + let mut pm = ProcessMemory::new(process(0), 10); + pm.grant(10).unwrap(); + assert!(pm.grant(1).is_err()); +} + +// --- MemoryRegion tests --- + +#[test] +fn memory_region_construction_and_accessors() { + let region = MemoryRegion::new( + RegionId::from_raw(1), + process(0), + base(), + pages(4), + Generation::initial(), + ); + assert_eq!(region.id(), RegionId::from_raw(1)); + assert_eq!(region.owner(), process(0)); + assert_eq!(region.base(), base()); + assert_eq!(region.size(), pages(4)); + assert_eq!(region.generation(), &Generation::initial()); +} + +// --- RegionTable tests --- + +#[test] +fn region_table_insert_and_get() { + let mut table = RegionTable::new(); + let id = table.insert(MemoryRegion::new( + RegionId::from_raw(0), + process(0), + base(), + pages(4), + Generation::initial(), + )); + assert!(id.is_some()); + let rid = id.unwrap(); + assert!(table.get(rid).is_some()); +} + +#[test] +fn region_table_remove() { + let mut table = RegionTable::new(); + let id = table.insert(MemoryRegion::new( + RegionId::from_raw(0), + process(0), + base(), + pages(4), + Generation::initial(), + )).unwrap(); + let removed = table.remove(id); + assert!(removed.is_some()); + assert!(table.get(id).is_none()); +} + +#[test] +fn region_table_regions_for_process() { + let mut table = RegionTable::new(); + table.insert(MemoryRegion::new( + RegionId::from_raw(0), process(0), base(), pages(4), Generation::initial(), + )); + table.insert(MemoryRegion::new( + RegionId::from_raw(1), process(1), base(), pages(4), Generation::initial(), + )); + table.insert(MemoryRegion::new( + RegionId::from_raw(2), process(0), base(), pages(2), Generation::initial(), + )); + assert_eq!(table.regions_for_process(process(0)).count(), 2); + assert_eq!(table.regions_for_process(process(1)).count(), 1); +} diff --git a/crates/domain/src/resource.rs b/crates/domain/src/resource.rs index e825544..394c5df 100644 --- a/crates/domain/src/resource.rs +++ b/crates/domain/src/resource.rs @@ -1,9 +1,4 @@ -use types::{ - address::VirtualAddress, - bounded::PageCount, - generation::Generation, - id::{EndpointId, IrqId, ProcessId, RegionId}, -}; +use types::id::{EndpointId, IrqId, ProcessId, RegionId}; #[derive(Clone)] pub enum Resource { @@ -13,10 +8,3 @@ pub enum Resource { Interrupt(IrqId), ProcessCreate, } - -pub struct MemoryRegion { - id: RegionId, - base: VirtualAddress, - size: PageCount, - generation: Generation, -} diff --git a/crates/types/src/address.rs b/crates/types/src/address.rs index 4cf9197..5ef3868 100644 --- a/crates/types/src/address.rs +++ b/crates/types/src/address.rs @@ -7,6 +7,23 @@ pub struct StackPointer(usize); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct VirtualAddress(usize); +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct PhysicalAddress(usize); + +impl PhysicalAddress { + pub fn from_raw(addr: usize) -> Self { + Self(addr) + } + + pub fn raw(&self) -> usize { + self.0 + } + + pub fn offset(&self, pages: usize) -> Self { + Self(self.0 + pages * 4096) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DataRef { addr: VirtualAddress,