feat(domain): add memory module with page pool, per-process tracking, and region table
This commit is contained in:
@@ -17,4 +17,5 @@ pub enum KernelError {
|
||||
InvalidStateTransition(ThreadId),
|
||||
InvalidThread(ThreadId),
|
||||
TooManyThreads,
|
||||
OutOfMemory,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
95
crates/domain/src/memory/mod.rs
Normal file
95
crates/domain/src/memory/mod.rs
Normal file
@@ -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<MemoryRegion>; MAX_REGIONS],
|
||||
}
|
||||
|
||||
impl RegionTable {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regions: [const { None }; MAX_REGIONS],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, region: MemoryRegion) -> Option<RegionId> {
|
||||
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<MemoryRegion> {
|
||||
self.regions.get_mut(id.raw() as usize).and_then(|s| s.take())
|
||||
}
|
||||
|
||||
pub fn regions_for_process(&self, process: ProcessId) -> impl Iterator<Item = &MemoryRegion> {
|
||||
self.regions
|
||||
.iter()
|
||||
.filter_map(|s| s.as_ref())
|
||||
.filter(move |r| r.owner() == process)
|
||||
}
|
||||
}
|
||||
68
crates/domain/src/memory/page_pool.rs
Normal file
68
crates/domain/src/memory/page_pool.rs
Normal file
@@ -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<PhysicalAddress> {
|
||||
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<usize> {
|
||||
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
|
||||
}
|
||||
}
|
||||
47
crates/domain/src/memory/process_memory.rs
Normal file
47
crates/domain/src/memory/process_memory.rs
Normal file
@@ -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(())
|
||||
}
|
||||
}
|
||||
207
crates/domain/src/memory/tests.rs
Normal file
207
crates/domain/src/memory/tests.rs
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user