threads(M2): thread_spawn/thread_exit + runtime.Thread.spawn
A thread is a task sharing the caller's address space. New private syscalls thread_spawn(entry, stack_top, arg)=37 and thread_exit=38: thread_spawn goes through scheduler.spawnThread (retains the shared aspace), thread_exit ends the task like a process exit(0) (terminateCurrent -> releaseAspace, so the space survives while siblings hold it). The closure pointer reaches the new thread in rdi via a new jump_to_user_arg asm path and a per-task user_arg (0 for a normal process, whose _start ignores it) - so the runtime trampoline is a plain C-ABI Zig function, no naked asm. runtime.Thread (library/runtime/thread.zig) mirrors std.Thread.spawn: mmap a stack, heap-allocate the args closure, hand the kernel the trampoline + closure. addThreadedUserBinary opts a binary into single_threaded=false; thread-test is the first, and proves a worker runs in the shared address space via a shared global the main thread polls. Gate thread-spawn PASS; 16 guardrail cases green (incl. args/init/process on the new jump_to_user_arg path) + aspace-refcount; build + host tests clean.
This commit is contained in:
@@ -227,6 +227,16 @@ fn system_call(state: *architecture.CpuState) void {
|
||||
.shm_create => systemShmCreate(state),
|
||||
.shm_map => systemShmMap(state),
|
||||
.shm_physical => systemShmPhysical(state),
|
||||
.thread_spawn => systemThreadSpawn(state),
|
||||
.thread_exit => {
|
||||
// A thread ends like a process exit(0), but only this task: its
|
||||
// resources are released and its address-space reference dropped (the
|
||||
// space survives while sibling threads hold it). docs/threading.md.
|
||||
if (scheduler.currentIsUserProcess()) {
|
||||
scheduler.current().exit_reason = .exited;
|
||||
terminateCurrent();
|
||||
} else architecture.userExit();
|
||||
},
|
||||
_ => fail(state),
|
||||
}
|
||||
}
|
||||
@@ -642,6 +652,23 @@ fn systemSpawn(state: *architecture.CpuState) void {
|
||||
fail(state); // no bundled binary by that name
|
||||
}
|
||||
|
||||
/// thread_spawn(entry, stack_top, arg) -> tid: start a task that shares the **caller's**
|
||||
/// address space (docs/threading.md). The runtime supplies `entry` (its thread
|
||||
/// trampoline), a stack it mmap'd, and the closure pointer, which the kernel delivers in
|
||||
/// the new thread's rdi. The entry and stack must lie in the user half; the new thread is
|
||||
/// supervised by the caller and inherits its priority. Only a user process may spawn.
|
||||
fn systemThreadSpawn(state: *architecture.CpuState) void {
|
||||
const entry = architecture.systemCallArg(state, 0);
|
||||
const stack_top = architecture.systemCallArg(state, 1);
|
||||
const arg = architecture.systemCallArg(state, 2);
|
||||
const t = scheduler.current();
|
||||
if (t.aspace == 0) return fail(state); // kernel tasks own no address space to share
|
||||
if (entry == 0 or entry >= user_half_end) return fail(state);
|
||||
if (stack_top == 0 or stack_top > user_half_end) return fail(state);
|
||||
const tid = scheduler.spawnThread(t.aspace, entry, stack_top, arg, t.priority, t.id) orelse return fail(state);
|
||||
architecture.setSystemCallResult(state, tid);
|
||||
}
|
||||
|
||||
/// process_enumerate(buffer, maximum) -> total: snapshot the task table into the
|
||||
/// caller's buffer (up to `maximum` `abi.ProcessDescriptor` entries), returning
|
||||
/// the total live-task count — the exact shape of `device_enumerate`, so a `ps`
|
||||
@@ -1433,7 +1460,7 @@ pub fn spawnProcessSupervised(image: []const u8, priority: u3, argv: []const []c
|
||||
architecture.mapUserPageInto(aspace, page_virtual, stack_frame, true, false); // RW + NX
|
||||
}
|
||||
|
||||
const child = scheduler.spawnUserLocked(aspace, parsed.entry, user_sp, priority, argv[0], supervisor, if (exit_endpoint) |endpoint| @ptrCast(endpoint) else null) orelse
|
||||
const child = scheduler.spawnUserLocked(aspace, parsed.entry, user_sp, 0, priority, argv[0], supervisor, if (exit_endpoint) |endpoint| @ptrCast(endpoint) else null) orelse
|
||||
return error.OutOfMemory;
|
||||
// The child holds a reference to its exit endpoint from birth to death. Taken
|
||||
// only now, after nothing can fail; the lock is still held, so the child
|
||||
|
||||
Reference in New Issue
Block a user