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,6 +1,7 @@
#![no_std]
pub mod interrupt;
mod memory;
#[cfg(kani)]
pub mod proofs;
pub mod syscall;
@@ -16,7 +17,7 @@ use domain::{
endpoint::EndpointTable,
error::KernelError,
irq::IrqRoutingTable,
memory::{RegionTable, page_pool::PagePool, process_memory::ProcessMemory},
memory::{RegionTable, frame::FrameTable, quota::Quota},
message::Message,
process::{MAX_PROCESSES, ProcessTable},
resource::Resource,
@@ -26,6 +27,7 @@ use domain::{
};
use platform::Platform;
use types::{
address::PhysicalAddress,
bounded::{CapSlot, Priority},
generation::Generation,
id::{ProcessId, ThreadId},
@@ -39,8 +41,8 @@ pub struct Kernel<P: Platform> {
threads: ThreadTable,
endpoints: EndpointTable,
scheduler: MultiCoreScheduler,
page_pool: PagePool,
process_memory: [Option<ProcessMemory>; MAX_PROCESSES],
frames: FrameTable,
quotas: [Quota; MAX_PROCESSES],
regions: RegionTable,
irqs: IrqRoutingTable,
pending_messages: [Option<Message>; MAX_THREADS],
@@ -49,7 +51,7 @@ pub struct Kernel<P: Platform> {
}
impl<P: Platform> Kernel<P> {
pub fn new(platform: P, page_pool: PagePool) -> Self {
pub fn new(platform: P, pool_base: PhysicalAddress, pool_pages: usize) -> Self {
let num_cores = platform.num_cores();
Self {
platform,
@@ -57,8 +59,8 @@ impl<P: Platform> Kernel<P> {
threads: ThreadTable::new(),
endpoints: EndpointTable::new(),
scheduler: MultiCoreScheduler::new(num_cores),
page_pool,
process_memory: [const { None }; MAX_PROCESSES],
frames: FrameTable::new(pool_base, pool_pages),
quotas: [Quota::default(); MAX_PROCESSES],
regions: RegionTable::new(),
irqs: IrqRoutingTable::new(),
pending_messages: [const { None }; MAX_THREADS],
@@ -67,18 +69,51 @@ impl<P: Platform> Kernel<P> {
}
}
/// Builds the kernel directly in `slot`, one table at a time, so the
/// whole structure never has to fit on the (small) boot stack.
///
/// # Safety
/// `slot` must be valid for writes and properly aligned. Any previous
/// value is overwritten without being dropped.
pub unsafe fn init_in_place(
slot: *mut Self,
platform: P,
pool_base: PhysicalAddress,
pool_pages: usize,
) {
use core::ptr::addr_of_mut;
let num_cores = platform.num_cores();
// SAFETY: the caller guarantees `slot` is valid and aligned; each
// field is written exactly once.
unsafe {
addr_of_mut!((*slot).platform).write(platform);
addr_of_mut!((*slot).processes).write(ProcessTable::new());
addr_of_mut!((*slot).threads).write(ThreadTable::new());
addr_of_mut!((*slot).endpoints).write(EndpointTable::new());
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
addr_of_mut!((*slot).frames).write(FrameTable::new(pool_base, pool_pages));
addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]);
addr_of_mut!((*slot).regions).write(RegionTable::new());
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
addr_of_mut!((*slot).pending_messages).write([const { None }; MAX_THREADS]);
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
addr_of_mut!((*slot).wait_sets).write([const { None }; MAX_THREADS]);
}
}
pub fn boot(
&mut self,
priority: Priority,
time_slice: TimeSlice,
) -> Result<(ProcessId, ThreadId), KernelError> {
let pid = self.processes.create(CapabilityTable::new(), None)?;
if let Err(e) = self.create_address_space(pid) {
let _ = self.processes.destroy(pid);
return Err(e);
}
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
self.processes.get_mut(pid)?.add_thread(tid)?;
self.process_memory[pid.raw() as usize] =
Some(ProcessMemory::new(pid, ProcessMemory::DEFAULT_PAGE_LIMIT));
let cap = Capability::new(
Resource::ProcessCreate,
PermissionSet::empty().with(Permission::Execute),
@@ -116,10 +151,6 @@ impl<P: Platform> Kernel<P> {
self.threads.get_mut(tid)?.set_running(core)
}
pub fn free_page_count(&self) -> usize {
self.page_pool.free_count()
}
pub fn process_exists(&self, pid: ProcessId) -> bool {
self.processes.get(pid).is_ok()
}
@@ -155,11 +186,10 @@ impl<P: Platform> Kernel<P> {
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
self.process_memory[pid.raw() as usize] =
Some(domain::memory::process_memory::ProcessMemory::new(
pid,
ProcessMemory::DEFAULT_PAGE_LIMIT,
));
if self.create_address_space(pid).is_err() {
let _ = self.processes.destroy(pid);
return None;
}
Some(pid)
}
@@ -245,12 +275,7 @@ impl<P: Platform> Kernel<P> {
self.cleanup_threads(&thread_ids);
// Free allocated pages back to pool
if let Some(pm) = &self.process_memory[pid.raw() as usize] {
for &(pa, count) in pm.allocations() {
self.page_pool.free(pa, count);
}
}
self.teardown_memory(pid);
// Unblock threads in other processes that were blocked waiting
// on endpoints where this process's threads were the other side.
@@ -260,7 +285,6 @@ impl<P: Platform> Kernel<P> {
// when the endpoint is revoked.
let _ = self.processes.destroy(pid);
self.process_memory[pid.raw() as usize] = None;
}
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {