kernel+tests: M4 shared-fate — nine group-death test cases, per-space arena cursors
Eight new QEMU cases (thread-fault-group, kill-threaded-group, kill-via-worker-tid, racing-triggers, exit-group, leader-thread-exit, thread-exit-solo, shm-mapping-ref) driving seven new thread-test modes; a shared checkGroupDead asserts the contract everywhere: one notification, badged with the leader, reason on the leader's record, no member listed, claims released first. The shm-mapping-ref case flushed out the per-task DMA/shared-memory arena cursor bug directly (a sibling's regions mapped over the worker's), so both cursors moved to the AddressSpaceRef like the mmap/MMIO cursors before them (threading M7 pattern). Runtime gains Thread.tryExitCurrent for the leader -EPERM refusal path. Docs updated: threading.md's shared-fate gap is closed, process-management.md and process-lifecycle.md describe the leader re-key, plan status = implemented. Full suite: 100/100.
This commit is contained in:
+55
-21
@@ -78,15 +78,15 @@ pub const device_arena_end: u64 = device_arena_base + (4 << 30);
|
||||
|
||||
/// The DMA arena: where `dma_alloc` places coherent DMA buffers, in PML4[228] — a
|
||||
/// user-exclusive region distinct from the MMIO arena. Unlike MMIO grants these back
|
||||
/// real RAM (contiguous frames), so they are reclaimed on teardown. Per-process cursor
|
||||
/// in `Task.dma_map_next`.
|
||||
/// real RAM (contiguous frames), so they are reclaimed on teardown. Per-SPACE cursor
|
||||
/// on the AddressSpaceRef (sibling threads share the arena).
|
||||
pub const dma_arena_base: u64 = 0x0000_7200_0000_0000;
|
||||
pub const dma_arena_end: u64 = dma_arena_base + (256 << 20); // 256 MiB per process
|
||||
|
||||
/// The shared-memory arena: where `shared_memory_create`/`shared_memory_map` place shared cacheable regions, in
|
||||
/// PML4[230] — a user-exclusive region distinct from the DMA arena. The frames are owned by
|
||||
/// a refcounted shared-memory object and freed when its last capability drops, not on teardown, so the
|
||||
/// mapping carries `device_grant`. Per-process cursor in `Task.shared_memory_map_next` (docs/display-v2.md).
|
||||
/// mapping carries `device_grant`. Per-SPACE cursor on the AddressSpaceRef (docs/display-v2.md).
|
||||
pub const shared_memory_arena_base: u64 = 0x0000_7300_0000_0000;
|
||||
pub const shared_memory_arena_end: u64 = shared_memory_arena_base + (256 << 20); // 256 MiB per process
|
||||
|
||||
@@ -501,19 +501,31 @@ fn systemDmaAlloc(state: *architecture.CpuState) void {
|
||||
const max_phys: u64 = if (flags & abi.dma_below_4g != 0) (@as(u64, 4) << 30) else ~@as(u64, 0);
|
||||
const phys = pmm.allocContiguous(pages, max_phys) orelse return fail(state);
|
||||
|
||||
if (t.dma_map_next == 0) t.dma_map_next = dma_arena_base;
|
||||
const base_v = t.dma_map_next;
|
||||
if (base_v + pages * page_size > dma_arena_end) {
|
||||
for (0..pages) |i| pmm.free(phys + i * page_size); // arena exhausted; give the frames back
|
||||
return fail(state);
|
||||
// Reserve arena virtual space from the per-SPACE cursor, under the lock —
|
||||
// sibling threads must hand out disjoint windows of the one shared arena.
|
||||
var base_v: u64 = 0;
|
||||
{
|
||||
const lock_flags = sync.enter();
|
||||
const cursor = scheduler.addressSpaceDmaNextPtr(t.address_space) orelse {
|
||||
sync.leave(lock_flags);
|
||||
for (0..pages) |i| pmm.free(phys + i * page_size);
|
||||
return fail(state);
|
||||
};
|
||||
if (cursor.* == 0) cursor.* = dma_arena_base;
|
||||
base_v = cursor.*;
|
||||
if (base_v + pages * page_size > dma_arena_end) {
|
||||
sync.leave(lock_flags);
|
||||
for (0..pages) |i| pmm.free(phys + i * page_size); // arena exhausted; give the frames back
|
||||
return fail(state);
|
||||
}
|
||||
cursor.* = base_v + pages * page_size;
|
||||
sync.leave(lock_flags);
|
||||
}
|
||||
|
||||
// Zero through the physmap (the frames aren't mapped in the caller yet), then map.
|
||||
const kernel_view: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(phys));
|
||||
@memset(kernel_view[0 .. pages * page_size], 0);
|
||||
architecture.mapUserDmaInto(t.address_space, base_v, phys, pages * page_size);
|
||||
|
||||
t.dma_map_next = base_v + pages * page_size;
|
||||
architecture.setSystemCallResult(state, base_v); // virtual address for the CPU
|
||||
architecture.setSystemCallResult2(state, phys); // physical address for the device
|
||||
}
|
||||
@@ -557,10 +569,24 @@ fn systemSharedMemoryCreate(state: *architecture.CpuState) void {
|
||||
const pages: usize = @intCast((len + page_size - 1) / page_size);
|
||||
if (pages == 0 or pages > maximum_shared_memory_pages) return fail(state);
|
||||
|
||||
// Reserve arena virtual space up front, so a mapping failure needs no rollback.
|
||||
if (t.shared_memory_map_next == 0) t.shared_memory_map_next = shared_memory_arena_base;
|
||||
const base_v = t.shared_memory_map_next;
|
||||
if (base_v + pages * page_size > shared_memory_arena_end) return fail(state); // arena exhausted
|
||||
// Reserve arena virtual space up front (per-SPACE cursor: sibling threads
|
||||
// hand out disjoint windows), so a mapping failure needs no rollback.
|
||||
var base_v: u64 = 0;
|
||||
{
|
||||
const lock_flags = sync.enter();
|
||||
const cursor = scheduler.addressSpaceSharedMemoryNextPtr(t.address_space) orelse {
|
||||
sync.leave(lock_flags);
|
||||
return fail(state);
|
||||
};
|
||||
if (cursor.* == 0) cursor.* = shared_memory_arena_base;
|
||||
base_v = cursor.*;
|
||||
if (base_v + pages * page_size > shared_memory_arena_end) {
|
||||
sync.leave(lock_flags);
|
||||
return fail(state); // arena exhausted
|
||||
}
|
||||
cursor.* = base_v + pages * page_size;
|
||||
sync.leave(lock_flags);
|
||||
}
|
||||
|
||||
const phys = pmm.allocContiguous(pages, ~@as(u64, 0)) orelse return fail(state);
|
||||
// Zero through the physmap (the frames aren't mapped in the caller yet).
|
||||
@@ -595,7 +621,6 @@ fn systemSharedMemoryCreate(state: *architecture.CpuState) void {
|
||||
}
|
||||
|
||||
architecture.mapUserSharedInto(t.address_space, base_v, phys, pages * page_size);
|
||||
t.shared_memory_map_next = base_v + pages * page_size;
|
||||
architecture.setSystemCallResult(state, base_v); // virtual_address for the CPU
|
||||
architecture.setSystemCallResult2(state, @intCast(handle)); // capability handle to pass on
|
||||
}
|
||||
@@ -610,25 +635,34 @@ fn systemSharedMemoryMap(state: *architecture.CpuState) void {
|
||||
if (t.address_space == 0) return fail(state);
|
||||
|
||||
const shared_memory = ipc.resolveSharedMemory(t, cap) orelse return fail(state); // not a shared-memory handle we hold
|
||||
if (t.shared_memory_map_next == 0) t.shared_memory_map_next = shared_memory_arena_base;
|
||||
const base_v = t.shared_memory_map_next;
|
||||
const size = shared_memory.pages * page_size;
|
||||
if (base_v + size > shared_memory_arena_end) return fail(state);
|
||||
|
||||
// This mapping holds its own reference, recorded on the space and dropped at
|
||||
// its destruction (docs/shared-fate-plan.md M3) — the handle's reference is
|
||||
// Reserve arena space (per-SPACE cursor) and record the mapping's own
|
||||
// reference in one locked section — the reference is dropped at space
|
||||
// destruction (docs/shared-fate-plan.md M3); the handle's reference is
|
||||
// separate and may be closed while the mapping lives on.
|
||||
var base_v: u64 = 0;
|
||||
{
|
||||
const flags = sync.enter();
|
||||
const cursor = scheduler.addressSpaceSharedMemoryNextPtr(t.address_space) orelse {
|
||||
sync.leave(flags);
|
||||
return fail(state);
|
||||
};
|
||||
if (cursor.* == 0) cursor.* = shared_memory_arena_base;
|
||||
base_v = cursor.*;
|
||||
if (base_v + size > shared_memory_arena_end) {
|
||||
sync.leave(flags);
|
||||
return fail(state); // arena exhausted
|
||||
}
|
||||
if (!scheduler.recordSpaceMappingLocked(t.address_space, @ptrCast(shared_memory))) {
|
||||
sync.leave(flags);
|
||||
return fail(state); // mapping table full: refuse rather than map unrecorded
|
||||
}
|
||||
ipc.retainSharedMemory(shared_memory);
|
||||
cursor.* = base_v + size;
|
||||
sync.leave(flags);
|
||||
}
|
||||
architecture.mapUserSharedInto(t.address_space, base_v, shared_memory.phys, size);
|
||||
t.shared_memory_map_next = base_v + size;
|
||||
architecture.setSystemCallResult(state, base_v);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user