Add process management: enumerate, supervisor-gated kill, exit notifications
process_enumerate snapshots the task table (the device_enumerate shape, so ps is a user program); system_spawn returns the child id, records the caller as supervisor, and takes an exit endpoint; process_kill is allowed only for the supervisor. Every death — exit, fault, or kill — posts a child-exit badge to that endpoint (the IRQ-as-IPC pattern as SIGCHLD). A target caught off-CPU is reaped in place; a running one is condemned and finished at its next system call or tick, guarded so teardown never lands mid-kernel-operation. Tested by process-list, process-kill, and supervision (a ring-3 supervisor exercising the whole surface); design notes in docs/process-management.md.
This commit is contained in:
@@ -47,6 +47,8 @@ pub const ENOENT: i64 = 4; // no such registered service
|
||||
pub const ENOSPC: i64 = 5; // handle table or registry full
|
||||
pub const ENOMEM: i64 = 6; // out of memory
|
||||
pub const EPEER: i64 = 7; // peer died before replying (its process exited or was killed)
|
||||
pub const ESRCH: i64 = 8; // no such process (process_kill of an unknown/dead id)
|
||||
pub const EPERM: i64 = 9; // not permitted (process_kill by anyone but the supervisor)
|
||||
|
||||
/// A badge with this bit set is an asynchronous notification (e.g. an IRQ), not a
|
||||
/// message from a client — there is no reply owed. The low bits carry the source
|
||||
@@ -93,6 +95,7 @@ pub fn dropRef(endpoint: *Endpoint) void {
|
||||
// --- sender FIFO (endpoint-local, via Task.next) ----------------------------
|
||||
|
||||
fn enqueueSender(endpoint: *Endpoint, t: *Task) void {
|
||||
t.ipc_wait_endpoint = @ptrCast(endpoint); // so a kill can unlink a parked caller
|
||||
t.next = null;
|
||||
if (endpoint.sender_tail) |tail| tail.next = t else endpoint.sender_head = t;
|
||||
endpoint.sender_tail = t;
|
||||
@@ -102,10 +105,34 @@ fn dequeueSender(endpoint: *Endpoint) ?*Task {
|
||||
const t = endpoint.sender_head orelse return null;
|
||||
endpoint.sender_head = t.next;
|
||||
if (endpoint.sender_head == null) endpoint.sender_tail = null;
|
||||
t.ipc_wait_endpoint = null;
|
||||
t.next = null;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// Unlink `t` from the sender FIFO it queues in, if any — the kill path for a
|
||||
/// client parked in `call` that no server has received yet. Without this, a dead
|
||||
/// caller would later be dequeued as a dangling pointer. The endpoint is still
|
||||
/// alive here: `t`'s own handle table holds a reference until closeHandles runs
|
||||
/// (which the kill path does *after* this). Precondition: the big kernel lock is
|
||||
/// held.
|
||||
pub fn abandonSenderLocked(t: *Task) void {
|
||||
const endpoint: *Endpoint = @ptrCast(@alignCast(t.ipc_wait_endpoint orelse return));
|
||||
t.ipc_wait_endpoint = null;
|
||||
var previous: ?*Task = null;
|
||||
var node = endpoint.sender_head;
|
||||
while (node) |n| : ({
|
||||
previous = n;
|
||||
node = n.next;
|
||||
}) {
|
||||
if (n != t) continue;
|
||||
if (previous) |p| p.next = t.next else endpoint.sender_head = t.next;
|
||||
if (endpoint.sender_tail == t) endpoint.sender_tail = previous;
|
||||
t.next = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// --- cross-address-space copy ----------------------------------------------
|
||||
|
||||
/// Copy `len` bytes from `source_va` in address space `source_as` to `destination_va` in
|
||||
|
||||
Reference in New Issue
Block a user