feat(domain): add memory module with page pool, per-process tracking, and region table

This commit is contained in:
2026-10-17 23:45:17 +02:00
parent 021b2d83b9
commit ed80fc43a6
8 changed files with 437 additions and 13 deletions

View File

@@ -17,4 +17,5 @@ pub enum KernelError {
InvalidStateTransition(ThreadId), InvalidStateTransition(ThreadId),
InvalidThread(ThreadId), InvalidThread(ThreadId),
TooManyThreads, TooManyThreads,
OutOfMemory,
} }

View File

@@ -4,6 +4,7 @@ pub mod capability;
pub mod endpoint; pub mod endpoint;
pub mod error; pub mod error;
pub mod fault; pub mod fault;
pub mod memory;
pub mod message; pub mod message;
pub mod process; pub mod process;
pub mod resource; pub mod resource;

View 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)
}
}

View 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
}
}

View 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(())
}
}

View 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);
}

View File

@@ -1,9 +1,4 @@
use types::{ use types::id::{EndpointId, IrqId, ProcessId, RegionId};
address::VirtualAddress,
bounded::PageCount,
generation::Generation,
id::{EndpointId, IrqId, ProcessId, RegionId},
};
#[derive(Clone)] #[derive(Clone)]
pub enum Resource { pub enum Resource {
@@ -13,10 +8,3 @@ pub enum Resource {
Interrupt(IrqId), Interrupt(IrqId),
ProcessCreate, ProcessCreate,
} }
pub struct MemoryRegion {
id: RegionId,
base: VirtualAddress,
size: PageCount,
generation: Generation,
}

View File

@@ -7,6 +7,23 @@ pub struct StackPointer(usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VirtualAddress(usize); 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DataRef { pub struct DataRef {
addr: VirtualAddress, addr: VirtualAddress,