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:
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user