feat(kernel): kernel-owned memory model

- FrameTable: per-frame owner/use, replaces PagePool + ProcessMemory
- Quota per process, page tables charged to owner
- Regions for shared mem + device MMIO, torn down w/ owner
- Platform: create/destroy AS, map/unmap, zero_frame; drop translate_va
- RequestPages/ReleasePages/MapPage(move) checked vs kernel records
- user window 0x1000..32MiB, private pts by construction
- boot loader + device setup go through kernel api
- kernel built in place, boot allocator bounded
- isolation tests, randomized invariant test, FrameTable kani proofs
- fixes: inflated release, unmapped release, dst va unchecked,
  heap overflow, unbounded pt alloc, unzeroed frames, double free
This commit is contained in:
2026-10-26 19:40:50 +01:00
parent e931485ca7
commit 8d2c40cb1e
43 changed files with 2815 additions and 1172 deletions

View File

@@ -1,58 +1,83 @@
use types::{
address::PhysicalAddress,
address::{PhysicalAddress, VirtualAddress},
bounded::PageCount,
generation::Generation,
id::{ProcessId, RegionId},
mapping::PagePerms,
};
pub mod page_pool;
pub mod process_memory;
pub mod frame;
pub mod quota;
#[cfg(test)]
#[path = "quota_tests.rs"]
mod quota_tests;
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
const MAX_REGIONS: usize = 256;
pub const MAX_REGIONS: usize = 64;
pub const MAX_REGION_MAPPINGS: usize = 4;
pub struct MemoryRegion {
id: RegionId,
owner: ProcessId,
base: PhysicalAddress,
size: PageCount,
generation: Generation,
/// Where a region's frames come from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegionBacking {
/// Frames from the user pool, freed with the region.
Pool,
/// Device MMIO; never freed.
Device,
}
impl MemoryRegion {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegionMapping {
pub process: ProcessId,
pub va: VirtualAddress,
pub perms: PagePerms,
}
/// Physically contiguous memory that may be mapped into several address
/// spaces. `owner` is charged for pool-backed frames.
pub struct Region {
owner: ProcessId,
backing: RegionBacking,
base: PhysicalAddress,
pages: PageCount,
generation: Generation,
mappings: [Option<RegionMapping>; MAX_REGION_MAPPINGS],
}
impl Region {
pub fn new(
id: RegionId,
owner: ProcessId,
backing: RegionBacking,
base: PhysicalAddress,
size: PageCount,
generation: Generation,
pages: PageCount,
) -> Self {
Self {
id,
owner,
backing,
base,
size,
generation,
pages,
generation: Generation::initial(),
mappings: [None; MAX_REGION_MAPPINGS],
}
}
pub fn id(&self) -> RegionId {
self.id
}
pub fn owner(&self) -> ProcessId {
self.owner
}
pub fn backing(&self) -> RegionBacking {
self.backing
}
pub fn base(&self) -> PhysicalAddress {
self.base
}
pub fn size(&self) -> PageCount {
self.size
pub fn pages(&self) -> PageCount {
self.pages
}
pub fn generation(&self) -> &Generation {
@@ -62,10 +87,43 @@ impl MemoryRegion {
pub fn revoke(&mut self) {
self.generation = self.generation.next();
}
pub fn mappings(&self) -> impl Iterator<Item = &RegionMapping> {
self.mappings.iter().flatten()
}
/// Records that `mapping.process` maps this region. Each process maps a
/// region at most once.
pub fn add_mapping(&mut self, mapping: RegionMapping) -> bool {
if self.mappings().any(|m| m.process == mapping.process) {
return false;
}
match self.mappings.iter_mut().find(|m| m.is_none()) {
Some(slot) => {
*slot = Some(mapping);
true
}
None => false,
}
}
pub fn remove_mapping(&mut self, process: ProcessId) -> Option<RegionMapping> {
self.mappings
.iter_mut()
.find(|m| m.is_some_and(|m| m.process == process))?
.take()
}
/// Whether `va` falls inside this region's mapping in `process`.
pub fn covers(&self, process: ProcessId, va: VirtualAddress) -> bool {
let len = self.pages.value() as usize * frame::PAGE_SIZE;
self.mappings()
.any(|m| m.process == process && va.raw() >= m.va.raw() && va.raw() - m.va.raw() < len)
}
}
pub struct RegionTable {
regions: [Option<MemoryRegion>; MAX_REGIONS],
regions: [Option<Region>; MAX_REGIONS],
}
impl RegionTable {
@@ -75,50 +133,60 @@ impl RegionTable {
}
}
pub fn insert(&mut self, region: MemoryRegion) -> Option<RegionId> {
/// The id the next `insert` will return.
pub fn next_free_id(&self) -> Option<RegionId> {
let idx = self.regions.iter().position(|s| s.is_none())?;
Some(RegionId::from_raw(idx as u16))
}
pub fn insert(&mut self, region: Region) -> 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)
Some(RegionId::from_raw(idx as u16))
}
pub fn get(&self, id: RegionId) -> Option<&MemoryRegion> {
self.regions.get(id.raw() as usize).and_then(|s| s.as_ref())
pub fn get(&self, id: RegionId) -> Option<&Region> {
self.regions.get(id.raw() as usize)?.as_ref()
}
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut MemoryRegion> {
self.regions
.get_mut(id.raw() as usize)
.and_then(|s| s.as_mut())
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut Region> {
self.regions.get_mut(id.raw() as usize)?.as_mut()
}
pub fn remove(&mut self, id: RegionId) -> Option<MemoryRegion> {
self.regions
.get_mut(id.raw() as usize)
.and_then(|s| s.take())
pub fn remove(&mut self, id: RegionId) -> Option<Region> {
self.regions.get_mut(id.raw() as usize)?.take()
}
pub fn find_by_base(
&self,
owner: ProcessId,
base: PhysicalAddress,
) -> Option<(RegionId, &MemoryRegion)> {
for (i, slot) in self.regions.iter().enumerate() {
if let Some(region) = slot
&& region.owner() == owner
&& region.base() == base
{
return Some((RegionId::from_raw(i as u16), region));
}
}
None
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = RegionId> + '_ {
self.regions.iter().enumerate().filter_map(move |(i, r)| {
r.as_ref()
.filter(|r| r.owner == owner)
.map(|_| RegionId::from_raw(i as u16))
})
}
pub fn regions_for_process(&self, process: ProcessId) -> impl Iterator<Item = &MemoryRegion> {
pub fn iter(&self) -> impl Iterator<Item = (RegionId, &Region)> {
self.regions
.iter()
.filter_map(|s| s.as_ref())
.filter(move |r| r.owner() == process)
.enumerate()
.filter_map(|(i, r)| r.as_ref().map(|r| (RegionId::from_raw(i as u16), r)))
}
/// Every region, by id, so callers can drop a process's mappings.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut Region)> {
self.regions
.iter_mut()
.enumerate()
.filter_map(|(i, r)| r.as_mut().map(|r| (RegionId::from_raw(i as u16), r)))
}
/// The region whose mapping in `process` covers `va`.
pub fn covering(&self, process: ProcessId, va: VirtualAddress) -> Option<RegionId> {
self.regions.iter().enumerate().find_map(|(i, r)| {
r.as_ref()
.filter(|r| r.covers(process, va))
.map(|_| RegionId::from_raw(i as u16))
})
}
}