threads(M7): thread-safe allocation (per-aspace mmap arena + locked heap)
Move the mmap/mmio grant-arena cursors off Task into the per-address-space object (scheduler aspace_refs, exposed via aspaceMmapNextPtr/aspaceDeviceMapNextPtr), so sibling threads in one address space hand out disjoint grants. systemMmap reserves a range under a brief lock then maps per page under a short-held lock (not the whole grant): the big lock runs with interrupts disabled, so pinning it across a multi-MiB memset+map froze other cores. Guard the runtime heap's rawAlloc/rawFree with a Thread.Mutex, gated on !single_threaded so ordinary binaries compile it out. thread-test gains an alloc mode: 4 threads x 500 alloc/fill/verify/free cycles; any overlap between concurrent allocations is caught by the pattern check. Also fix the affinity guardrail: its 3-billion-iteration busy-loop had codegen-dependent wall-time (adding a function to tests.zig swung it ~4s -> ~63s and timed it out). Reworked to wait on the wall clock instead. Gate thread-alloc PASS (3x); full guardrail 23/23 green; build + host tests clean.
This commit is contained in:
+53
-4
@@ -151,6 +151,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
threadMutexTest(boot_information);
|
||||
} else if (eql(case, "thread-id")) {
|
||||
threadIdTest(boot_information);
|
||||
} else if (eql(case, "thread-alloc")) {
|
||||
threadAllocTest(boot_information);
|
||||
} else if (eql(case, "args")) {
|
||||
argsTest(boot_information);
|
||||
} else if (eql(case, "init")) {
|
||||
@@ -779,11 +781,15 @@ fn affinityTest() void {
|
||||
return;
|
||||
}
|
||||
|
||||
var spins: u64 = 0;
|
||||
while (spins < 3_000_000_000) spins +%= 1; // many time slices across the cores
|
||||
// Let many time slices pass so the scheduler runs the pinned worker across ticks.
|
||||
// Wait on the wall clock, not a raw iteration count: a fixed-count busy-loop's
|
||||
// wall-time is a codegen lottery (the optimiser may elide or vectorise it), so an
|
||||
// unrelated change elsewhere in this file could swing this test from ~4 s to ~50 s.
|
||||
const run_until = architecture.millis() + 400;
|
||||
while (architecture.millis() < run_until) {}
|
||||
affinity_running = false;
|
||||
var settle: u64 = 0;
|
||||
while (settle < 200_000_000) settle +%= 1; // let the worker see the flag and exit
|
||||
const settle_until = architecture.millis() + 50;
|
||||
while (architecture.millis() < settle_until) {} // let the worker see the flag and exit
|
||||
|
||||
var others: u32 = 0;
|
||||
for (affinity_cores, 0..) |seen, c| {
|
||||
@@ -1689,6 +1695,49 @@ fn threadIdTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Thread-safe allocation (docs/threading-plan.md M7): `thread-test` in alloc mode runs N
|
||||
/// threads that each do many `alloc`/fill/verify/`free` cycles of varied sizes on the
|
||||
/// shared runtime heap. If the heap lock or the per-address-space mmap arena were unsafe,
|
||||
/// two threads' blocks would overlap and a thread would read another's pattern; the
|
||||
/// verdict marker is emitted only when every thread completes with every block intact.
|
||||
fn threadAllocTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: thread-alloc\n", .{});
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
var started = false;
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
if (!eql(item.name, "thread-test")) continue;
|
||||
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "alloc" })) true else |_| false;
|
||||
break;
|
||||
}
|
||||
check("thread-test (alloc mode) spawned", started);
|
||||
|
||||
const ok_marker = "thread-alloc: ok";
|
||||
const fail_marker = "thread-alloc: FAIL";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 20000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
|
||||
check("concurrent heap allocation stayed corruption-free (shared heap + per-aspace arena)", bufferHas(ok_marker) and !bufferHas(fail_marker));
|
||||
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