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),
|
InvalidStateTransition(ThreadId),
|
||||||
InvalidThread(ThreadId),
|
InvalidThread(ThreadId),
|
||||||
TooManyThreads,
|
TooManyThreads,
|
||||||
|
OutOfMemory,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
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::{
|
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,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user