threads(M1): address-space reference counting
Route address-space lifetime through a refcount keyed by the page-table root (scheduler.zig aspace_refs): retainAspace on the spawnUserLocked success path, releaseAspace from both teardown paths (exitUserLocked, destroyTaskLocked), destroying the space only when the last task on it exits. Behaviour is identical today (every space has exactly one task); this is the foundation shared-address- space threads (docs/threading.md) build on. Test-observable liveAspaceCount/aspaceDestroyCount + a new aspace-refcount kernel self-test and QEMU case: spawn and reap 5 ring-3 probes, assert live spaces return to baseline and destructions advance by exactly 5 (destroyed once each, no leak, no double-free). Gate passes; 13 guardrail cases green; build + host tests clean.
This commit is contained in:
@@ -139,6 +139,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
userPfTest();
|
||||
} else if (eql(case, "fault-recovery")) {
|
||||
faultRecoveryTest(boot_information);
|
||||
} else if (eql(case, "aspace-refcount")) {
|
||||
aspaceRefcountTest(boot_information);
|
||||
} else if (eql(case, "args")) {
|
||||
argsTest(boot_information);
|
||||
} else if (eql(case, "init")) {
|
||||
@@ -1429,6 +1431,41 @@ fn faultRecoveryTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Address-space refcount (docs/threading-plan.md M1): every process holds exactly one
|
||||
/// reference to its address space, released when it dies, so `destroyAddressSpace` runs
|
||||
/// exactly once per space — no leak, no double-free. Spawn and kill several ring-3
|
||||
/// processes (the faulting probe, reaped by the kernel) and confirm the count of live
|
||||
/// address spaces returns to baseline while destructions advance by exactly that many.
|
||||
/// This is the foundation threads (shared address spaces) build on: the refactor must be
|
||||
/// invisible while every space still has exactly one task.
|
||||
fn aspaceRefcountTest(boot_information: *const BootInformation) void {
|
||||
_ = boot_information;
|
||||
log("DANOS-TEST-BEGIN: aspace-refcount\n", .{});
|
||||
const base_live = scheduler.liveAspaceCount();
|
||||
const base_destroyed = scheduler.aspaceDestroyCount();
|
||||
const rounds: u32 = 5;
|
||||
var killed: u32 = 0;
|
||||
var round: u32 = 0;
|
||||
while (round < rounds) : (round += 1) {
|
||||
process.fault_kill_count = 0;
|
||||
const probe = spawnFaultingProcess() orelse break;
|
||||
_ = probe;
|
||||
// Let the probe fault on its first instruction and be reaped.
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 5000;
|
||||
while (process.fault_kill_count < 1 and architecture.millis() < deadline) scheduler.yield();
|
||||
scheduler.setPriority(4);
|
||||
if (process.fault_kill_count >= 1) killed += 1;
|
||||
}
|
||||
check("all probes spawned and were killed", killed == rounds);
|
||||
check("live address-space count returned to baseline", scheduler.liveAspaceCount() == base_live);
|
||||
check("each address space destroyed exactly once", scheduler.aspaceDestroyCount() == base_destroyed + rounds);
|
||||
if (killed == rounds and scheduler.liveAspaceCount() == base_live and
|
||||
scheduler.aspaceDestroyCount() == base_destroyed + rounds)
|
||||
log("aspace-refcount: spaces released to baseline ok\n", .{});
|
||||
result();
|
||||
}
|
||||
|
||||
/// The full PID-1 path: the bootloader read /system/services/init off the boot volume and
|
||||
/// handed it over; load it as a user ELF and spawn it as a real ring-3 process
|
||||
/// — the same call the normal boot path makes — then confirm it beats. init
|
||||
|
||||
Reference in New Issue
Block a user