kernel: M1 shared-fate — Task.leader id, kill/signal re-keyed to the leader
Every task carries its process leader's id (main task: own id; threads: copied from the spawner; kernel tasks: 0, never followed). process_kill and process_signal resolve any member id to the leader and authorize against the leader's supervisor, making both capabilities per-process. ProcessDescriptor gains the leader field. No fan-out yet (docs/shared-fate-plan.md M1).
This commit is contained in:
@@ -49,6 +49,12 @@ pub const Task = struct {
|
||||
// Id of the process that spawned this one (0 = the kernel). The supervision
|
||||
// link is the kill authority: only the supervisor may process_kill a child.
|
||||
supervisor: u32 = 0,
|
||||
// Id of this task's process leader — the main task's own id, copied to every
|
||||
// thread it (transitively) spawns; 0 for kernel tasks and never followed.
|
||||
// Equal `leader` is what makes two tasks one process; the leader's id is the
|
||||
// id `system_spawn` returned, so it is the process id the supervisor speaks
|
||||
// (docs/shared-fate-plan.md).
|
||||
leader: u32 = 0,
|
||||
// Endpoint to notify when this process ends (any way: exit, fault, kill), or
|
||||
// null. Holds its own reference, dropped when the notification is posted.
|
||||
// Opaque here for the same reason as `handles` below.
|
||||
@@ -460,14 +466,16 @@ pub fn spawnOn(entry: *const fn () void, priority: Priority, cpu: u32) bool {
|
||||
/// in user mode at `entry` on `user_sp`, recorded under `name` (its argv[0]).
|
||||
/// `supervisor` is the id of the spawning process (0 = the kernel) — the kill
|
||||
/// authority — and `exit_endpoint` (an *ipc.Endpoint whose reference the caller
|
||||
/// has already taken, or null) is notified when this process ends.
|
||||
/// has already taken, or null) is notified when this process ends. `leader` is
|
||||
/// the process leader's id for a thread, or 0 to make the new task its own
|
||||
/// leader (a process spawn).
|
||||
/// It gets a fresh kernel stack for syscalls/interrupts, and its first switch-in
|
||||
/// lands in `user_task_trampoline`.
|
||||
/// Returns the new process id, or null (creating nothing) if the table is full or
|
||||
/// out of memory.
|
||||
/// **Caller must hold the kernel lock** (the loader that builds `address_space` holds it
|
||||
/// across the whole spawn, so the address space and the task appear atomically).
|
||||
pub fn spawnUserLocked(address_space: u64, entry: u64, user_sp: u64, user_arg: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque) ?u32 {
|
||||
pub fn spawnUserLocked(address_space: u64, entry: u64, user_sp: u64, user_arg: u64, priority: Priority, task_name: []const u8, supervisor: u32, exit_endpoint: ?*anyopaque, leader: u32) ?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
|
||||
@@ -488,6 +496,7 @@ pub fn spawnUserLocked(address_space: u64, entry: u64, user_sp: u64, user_arg: u
|
||||
.user_arg = user_arg,
|
||||
.supervisor = supervisor,
|
||||
.exit_endpoint = exit_endpoint,
|
||||
.leader = if (leader == 0) next_id else leader,
|
||||
};
|
||||
const name_length = @min(task_name.len, maximum_task_name);
|
||||
@memcpy(t.name_buffer[0..name_length], task_name[0..name_length]);
|
||||
@@ -1035,6 +1044,7 @@ pub fn enumerate(out: []abi.ProcessDescriptor) u64 {
|
||||
d.* = .{
|
||||
.id = t.id,
|
||||
.supervisor = t.supervisor,
|
||||
.leader = t.leader,
|
||||
.state = @intFromEnum(@as(abi.ProcessState, switch (t.state) {
|
||||
.ready => .ready,
|
||||
.running => .running,
|
||||
|
||||
Reference in New Issue
Block a user