[Kernel] Slab-recycle handle allocator (AUDIT-059 R34)
Adds a FIFO free list of closed handle slots so alloc_handle returns recycled IDs before bumping next_handle. Mirrors canary's slab-style ObjectTable: F8000098 reused 130x per 30s window in canary, but ours' monotonic bump allocator never reused slots — so a recycled slot in canary maps to a fresh, never-reused slot in ours, drifting kernel object identity per AUDIT-042's analysis. release_handle_slot is wired into nt_close's refcount==0 branch and gated to the canonical [0x1000, 0xF000_0000) range so synthetic XAudio park handles (AUDIT-048) are never recycled. Verified: all 655 workspace tests green, smoke tests at -n 50M show NtClose 115/run with handle table renumbering active (round-34 max handle 0x12ac vs round-16 baseline 0x12b8 over same workload). γ- cluster #2 wedge unchanged — silph wait still parks tid=13 on the renumbered handle (4216=0x1078 here vs 0x12a4 baseline), confirming the wedge is independent of allocator policy. Lands as a parity fix to bring our kernel-object identity in line with canary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1977,6 +1977,9 @@ fn nt_close(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
|
||||
// so a later scheduler round doesn't try to signal a dead handle.
|
||||
// `disarm_timer` is a no-op for non-timer handles.
|
||||
state.disarm_timer(handle);
|
||||
// AUDIT-059 R34: return the slot to the recycle FIFO so a later
|
||||
// `alloc_handle` mints the same ID (matching canary's slab).
|
||||
state.release_handle_slot(handle);
|
||||
}
|
||||
ctx.gpr[3] = 0;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,18 @@ pub struct KernelState {
|
||||
/// publish; observers (the kernel object table) are guarded by
|
||||
/// their own synchronization.
|
||||
next_handle: std::sync::atomic::AtomicU32,
|
||||
/// AUDIT-059 R34: FIFO free list of closed handle slots, mirroring
|
||||
/// canary's slab/free-list `ObjectTable`. Without this, ours' bump
|
||||
/// allocator monotonically grows so a recycled slot in canary
|
||||
/// (e.g. `F8000098` reused 130× per 30s) corresponds to a fresh,
|
||||
/// never-reused slot in ours — the kernel-object identity drifts.
|
||||
/// Recycling closes that gap and (per AUDIT-042 / R30) may
|
||||
/// side-effect-unwedge γ-cluster #2 by letting silph signals land
|
||||
/// on the same handle slot the wait registered for. Population is
|
||||
/// gated on `KernelState::release_handle_slot` (only IDs in
|
||||
/// `[HANDLE_BASE, 0xF000_0000)` are recycled — synthetic XAudio
|
||||
/// handles at `0xF000_0000+` are reserved and must never be reused).
|
||||
free_handles: std::collections::VecDeque<u32>,
|
||||
/// Scheduler managing all emulated HW threads + their per-slot
|
||||
/// runqueues. Starts empty — the app installs the initial guest thread
|
||||
/// on slot 0 via `KernelState::install_initial_thread` once it has the
|
||||
@@ -338,6 +350,7 @@ impl KernelState {
|
||||
let mut state = Self {
|
||||
exports: HashMap::new(),
|
||||
next_handle: AtomicU32::new(0x1000),
|
||||
free_handles: std::collections::VecDeque::new(),
|
||||
scheduler,
|
||||
next_tls_index: AtomicU32::new(0),
|
||||
cs_waiters: HashMap::new(),
|
||||
@@ -660,12 +673,29 @@ impl KernelState {
|
||||
}
|
||||
|
||||
pub fn alloc_handle(&mut self) -> u32 {
|
||||
// AUDIT-059 R34: prefer recycling a closed slot (FIFO, matching
|
||||
// canary's `ObjectTable` slab) before bumping. The Arc<Mutex<
|
||||
// KernelState>> already serializes us; no extra synchronization.
|
||||
if let Some(slot) = self.free_handles.pop_front() {
|
||||
return slot;
|
||||
}
|
||||
// M2.4: lock-free fetch_add. Relaxed is sufficient — IDs are
|
||||
// opaque tokens; no payload is sequenced against the counter.
|
||||
self.next_handle
|
||||
.fetch_add(4, std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// AUDIT-059 R34. Return a freshly-closed handle slot to the FIFO
|
||||
/// recycle queue. No-op for the synthetic XAudio range (`>= 0xF000_0000`,
|
||||
/// AUDIT-048) and the reserved `< 0x1000` band. Call site: `nt_close`'s
|
||||
/// `objects.remove` branch when refcount reaches zero.
|
||||
pub fn release_handle_slot(&mut self, handle: u32) {
|
||||
if handle < 0x1000 || handle >= 0xF000_0000 {
|
||||
return;
|
||||
}
|
||||
self.free_handles.push_back(handle);
|
||||
}
|
||||
|
||||
pub fn alloc_handle_for(&mut self, obj: KernelObject) -> u32 {
|
||||
let h = self.alloc_handle();
|
||||
self.objects.insert(h, obj);
|
||||
|
||||
Reference in New Issue
Block a user