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

@@ -0,0 +1,188 @@
use super::*;
use types::address::{PhysicalAddress, VirtualAddress};
use types::id::{ProcessId, RegionId};
use types::mapping::PagePerms;
const BASE: usize = 0x8400_0000;
fn table() -> FrameTable<8> {
FrameTable::new(PhysicalAddress::from_raw(BASE), 8)
}
fn pid(n: u16) -> ProcessId {
ProcessId::from_raw(n)
}
fn va(addr: usize) -> VirtualAddress {
VirtualAddress::from_raw(addr)
}
fn user(owner: u16, addr: usize) -> FrameUse {
FrameUse::user(pid(owner), va(addr), PagePerms::ReadWrite).unwrap()
}
#[test]
fn new_table_has_every_frame_free() {
let t = table();
assert_eq!(t.free_count(), 8);
assert_eq!(t.capacity(), 8);
}
#[test]
fn total_is_clamped_to_capacity() {
let t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 100);
assert_eq!(t.free_count(), 4);
}
#[test]
fn user_use_requires_page_aligned_va() {
assert!(FrameUse::user(pid(0), va(0x1234), PagePerms::ReadOnly).is_none());
assert!(FrameUse::user(pid(0), va(0x1000), PagePerms::ReadOnly).is_some());
}
#[test]
fn alloc_hands_out_distinct_frames_inside_the_pool() {
let mut t = table();
let a = t.alloc(user(0, 0x1000)).unwrap();
let b = t.alloc(user(0, 0x2000)).unwrap();
assert_ne!(a, b);
for pa in [a, b] {
assert!(pa.raw() >= BASE && pa.raw() < BASE + 8 * 4096);
assert_eq!(pa.raw() % 4096, 0);
}
assert_eq!(t.free_count(), 6);
}
#[test]
fn alloc_fails_when_exhausted() {
let mut t = table();
for i in 0..8 {
t.alloc(user(0, (i + 1) * 0x1000)).unwrap();
}
assert!(t.alloc(user(0, 0x9000)).is_none());
}
#[test]
fn free_returns_the_frame_and_its_use() {
let mut t = table();
let pa = t.alloc(user(3, 0x5000)).unwrap();
assert_eq!(t.free(pa), Some(user(3, 0x5000)));
assert_eq!(t.free_count(), 8);
}
#[test]
fn freeing_a_free_frame_is_rejected() {
let mut t = table();
let pa = t.alloc(user(0, 0x1000)).unwrap();
t.free(pa).unwrap();
assert_eq!(t.free(pa), None);
assert_eq!(t.free_count(), 8);
}
#[test]
fn freeing_outside_the_pool_or_unaligned_is_rejected() {
let mut t = table();
t.alloc(user(0, 0x1000)).unwrap();
assert_eq!(t.free(PhysicalAddress::from_raw(BASE - 4096)), None);
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 8 * 4096)), None);
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 1)), None);
assert_eq!(t.free_count(), 7);
}
#[test]
fn find_user_matches_owner_and_va() {
let mut t = table();
let pa = t.alloc(user(1, 0x3000)).unwrap();
assert_eq!(
t.find_user(pid(1), va(0x3000)),
Some((pa, PagePerms::ReadWrite))
);
assert_eq!(t.find_user(pid(2), va(0x3000)), None);
assert_eq!(t.find_user(pid(1), va(0x4000)), None);
}
#[test]
fn retag_moves_a_frame_to_a_new_use() {
let mut t = table();
let pa = t.alloc(user(1, 0x3000)).unwrap();
assert!(t.retag(pa, user(2, 0x7000)));
assert_eq!(t.find_user(pid(1), va(0x3000)), None);
assert_eq!(t.find_user(pid(2), va(0x7000)).map(|(p, _)| p), Some(pa));
}
#[test]
fn retag_of_a_free_frame_is_rejected() {
let mut t = table();
let pa = t.alloc(user(1, 0x3000)).unwrap();
t.free(pa);
assert!(!t.retag(pa, user(2, 0x7000)));
assert_eq!(t.free_count(), 8);
}
#[test]
fn alloc_contiguous_returns_adjacent_frames() {
let mut t = table();
let base = t
.alloc_contiguous(3, |_| FrameUse::Region {
region: RegionId::from_raw(0),
})
.unwrap();
assert_eq!(t.free_count(), 5);
for i in 0..3 {
assert_eq!(
t.get(base.offset(i)),
Some(&FrameUse::Region {
region: RegionId::from_raw(0)
})
);
}
}
#[test]
fn alloc_contiguous_fails_without_a_long_enough_run() {
let mut t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 4);
let a = t.alloc(user(0, 0x1000)).unwrap();
let _b = t.alloc(user(0, 0x2000)).unwrap();
let _c = t.alloc(user(0, 0x3000)).unwrap();
t.free(a);
// free frames: 0 and 3 — no run of 2
assert!(
t.alloc_contiguous(2, |_| FrameUse::PageTable { owner: pid(0) })
.is_none()
);
assert_eq!(t.free_count(), 2);
}
#[test]
fn free_owned_by_releases_only_that_processes_frames() {
let mut t = table();
t.alloc(user(1, 0x1000)).unwrap();
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
let other = t.alloc(user(2, 0x1000)).unwrap();
let region = t
.alloc(FrameUse::Region {
region: RegionId::from_raw(1),
})
.unwrap();
assert_eq!(t.free_owned_by(pid(1)), 2);
assert_eq!(t.free_count(), 6);
assert!(t.get(other).is_some());
assert!(t.get(region).is_some());
}
#[test]
fn user_frames_of_lists_mappings_of_one_process() {
let mut t = table();
t.alloc(user(1, 0x1000)).unwrap();
t.alloc(user(2, 0x2000)).unwrap();
t.alloc(user(1, 0x3000)).unwrap();
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
let mut vas = [0usize; 4];
let mut n = 0;
for (_, v) in t.user_frames_of(pid(1)) {
vas[n] = v.raw();
n += 1;
}
assert_eq!(&vas[..n], &[0x1000, 0x3000]);
}