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:
@@ -78,6 +78,8 @@ pub const Task = struct {
|
||||
aspace: u64 = 0,
|
||||
user_ip: u64 = 0, // user-mode entry point (user task only)
|
||||
user_sp: u64 = 0, // user-mode stack pointer (user task only)
|
||||
user_arg: u64 = 0, // value delivered in the user's rdi at first entry: 0 for a
|
||||
// process (its _start ignores it), the closure pointer for a thread (docs/threading.md)
|
||||
// Next free virtual address in this task's mmap grant arena (0 = uninitialised;
|
||||
// process.zig lazily seeds it to the arena base on the first mmap). Bumped up
|
||||
// as the user heap grows; user task only.
|
||||
@@ -388,7 +390,7 @@ pub fn spawnOn(entry: *const fn () void, priority: Priority, cpu: u32) bool {
|
||||
/// out of memory.
|
||||
/// **Caller must hold the kernel lock** (the loader that builds `aspace` holds it
|
||||
/// across the whole spawn, so the address space and the task appear atomically).
|
||||
pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque) ?u32 {
|
||||
pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, user_arg: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque) ?u32 {
|
||||
const t = freeSlot() orelse return null;
|
||||
const stack = heap.allocator().alloc(u8, stack_size) catch return null;
|
||||
// Take this task's reference to the address space before we commit the slot, so a
|
||||
@@ -405,6 +407,7 @@ pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, priority: Priority
|
||||
.aspace = aspace,
|
||||
.user_ip = entry,
|
||||
.user_sp = user_sp,
|
||||
.user_arg = user_arg,
|
||||
.supervisor = supervisor,
|
||||
.exit_endpoint = exit_endpoint,
|
||||
};
|
||||
@@ -421,6 +424,17 @@ pub fn spawnUserLocked(aspace: u64, entry: u64, user_sp: u64, priority: Priority
|
||||
return t.id;
|
||||
}
|
||||
|
||||
/// Spawn a **thread**: a user task that shares an *existing* address space `aspace`
|
||||
/// (docs/threading.md), starting at `entry` on `user_sp` with `arg` delivered in its
|
||||
/// rdi. Takes a reference to `aspace` (destroyed only when the last thread on it
|
||||
/// exits). Acquires the kernel lock itself. `supervisor` is the spawning process.
|
||||
/// Returns the new thread's id, or null if the task table is full / out of memory.
|
||||
pub fn spawnThread(aspace: u64, entry: u64, user_sp: u64, arg: u64, priority: Priority, supervisor: u32) ?u32 {
|
||||
const flags = sync.enter();
|
||||
defer sync.leave(flags);
|
||||
return spawnUserLocked(aspace, entry, user_sp, arg, priority, "thread", supervisor, null);
|
||||
}
|
||||
|
||||
/// The first thing a fresh user task runs (in ring 0, via task_trampoline). It
|
||||
/// drops to ring 3 at the task's recorded entry/stack. Reading them from the
|
||||
/// Task avoids smuggling values through callee-saved registers across the
|
||||
@@ -430,7 +444,7 @@ fn startUserTask() void {
|
||||
// No serial chatter here: this runs on every spawn, unserialized against
|
||||
// user-space writes, and its output used to shear concurrent log lines in
|
||||
// half — the largest source of corrupted markers in the QEMU scenarios.
|
||||
architecture.jumpToUser(t.user_ip, t.user_sp); // noreturn
|
||||
architecture.jumpToUserArg(t.user_ip, t.user_sp, t.user_arg); // noreturn (arg0 = 0 for a process)
|
||||
}
|
||||
|
||||
/// The unlocked task-creation primitive. Caller must hold the kernel lock (or be the
|
||||
|
||||
Reference in New Issue
Block a user