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:
+19
-12
@@ -94,24 +94,31 @@ first unchecked box.
|
||||
|
||||
---
|
||||
|
||||
## M1 — Address-space refcount (kernel foundation, no API, no behaviour change)
|
||||
## M1 — Address-space refcount (kernel foundation, no API, no behaviour change) ✅
|
||||
|
||||
The one invariant change threads require, landed and proven **before** anything shares
|
||||
an address space. Today aspace is 1:1 with a task and teardown destroys it on any user
|
||||
task's exit; make destruction happen on the **last** exit.
|
||||
|
||||
- [ ] A refcount keyed by the address-space root: `createAddressSpace`
|
||||
([process.zig](../system/kernel/process.zig)) sets it to 1; a helper
|
||||
`retainAspace`/`releaseAspace` adjusts it under the big kernel lock.
|
||||
- [ ] Task teardown ([scheduler.zig](../system/kernel/scheduler.zig), the
|
||||
`destroyAddressSpace(t.aspace)` path) decrements and only destroys at **zero**.
|
||||
- [ ] `-Dtest-case=aspace-refcount`: spawn and exit several processes in sequence and
|
||||
assert the frame allocator's free count returns to the pre-spawn **baseline**
|
||||
(each aspace destroyed exactly once — no leak, no double-free).
|
||||
- [x] A refcount keyed by the address-space root, held in `scheduler.zig`
|
||||
(`aspace_refs`): `retainAspace` takes a reference in `spawnUserLocked` (on the
|
||||
success path, after the slot + stack are secured), all under the big kernel lock.
|
||||
- [x] Both task-teardown paths ([scheduler.zig](../system/kernel/scheduler.zig):
|
||||
`exitUserLocked` and `destroyTaskLocked`) call `releaseAspace`, which decrements
|
||||
and only `destroyAddressSpace`s at **zero**; an unretained space (hand-built test
|
||||
spaces) is destroyed directly, preserving prior behaviour.
|
||||
- [x] `-Dtest-case=aspace-refcount`: spawn and reap several ring-3 processes in sequence
|
||||
and assert (via test-observable `liveAspaceCount`/`aspaceDestroyCount`) that the
|
||||
live-space count returns to **baseline** and destructions advance by exactly that
|
||||
many — each space destroyed exactly once, no leak, no double-free. (Refcount
|
||||
observables, not raw frame counts, since kernel stacks are still leaked on exit.)
|
||||
|
||||
**Gate:** `python3 test/qemu_test.py aspace-refcount` logs `aspace-refcount: frames
|
||||
reclaimed to baseline ok`, and the full guardrail set passes unchanged (the reframing
|
||||
is invisible until an aspace is actually shared).
|
||||
**Gate (met):** `python3 test/qemu_test.py aspace-refcount` passes
|
||||
(`aspace-refcount: spaces released to baseline ok` → `DANOS-TEST-RESULT: PASS`), and the
|
||||
full guardrail set passes unchanged — 13/13 (`smoke`, `sched`, `priority`, `smp`,
|
||||
`affinity`, `process`, `process-kill`, `supervision`, `fault-recovery`,
|
||||
`vfs-client-death`, `ipc`, `ipc-cap`, `display-service`); default `zig build` clean,
|
||||
`zig build test` green. The reframing is invisible until an aspace is actually shared.
|
||||
|
||||
## M2 — `thread_spawn` + `thread_exit`: a thread runs in the shared address space
|
||||
|
||||
|
||||
Reference in New Issue
Block a user