Download zig/kernel/process.zig from Snapkitty/phaser-ags: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/Snapkitty/phaser-ags/resolve/main/zig/kernel/process.zig
- Command line
-
hf download hf://Snapkitty/phaser-ags/zig/kernel/process.zig
-
curl -L -o process.zig https://huggingface.co/Snapkitty/phaser-ags/resolve/main/zig/kernel/process.zig
15.2 kB
| //! SPDX-License-Identifier: AGPL-3.0-or-later | |
| //! Copyright 2025 Ahmad Ali Parr / SnapKitty οΏ½ https://github.com/SNAPKITTYWEST/phaser-ags | |
| //! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| //! PHASER AGS β Process management (complete, no stubs) | |
| //! | |
| //! - Full PCB with kernel stack, user stack, page table | |
| //! - Round-robin preemptive scheduler via timer tick | |
| //! - Context switch through trap frame save/restore | |
| //! - Process creation with proper initial register state | |
| //! - Process cleanup (free all resources on exit/kill) | |
| //! βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const std = @import("std"); | |
| const driver = @import("driver.zig"); | |
| const trap = @import("trap.zig"); | |
| const memory = @import("memory.zig"); | |
| const MAX_PROCS: usize = 16; | |
| const KSTACK_PAGES: usize = 2; // 8 KB kernel stack | |
| const USTACK_PAGES: usize = 4; // 16 KB user stack | |
| // βββ Process states ββββββββββββββββββββββββββββββββββββββββββββββ | |
| const ProcState = enum(u8) { | |
| unused = 0, | |
| runnable = 1, | |
| running = 2, | |
| sleeping = 3, | |
| zombie = 4, | |
| }; | |
| // βββ Process Control Block βββββββββββββββββββββββββββββββββββββββ | |
| pub const Process = struct { | |
| pid: u16, | |
| state: ProcState, | |
| ksp: u64, // Saved kernel stack pointer (trap frame on it) | |
| kstack: u32, // Kernel stack base address | |
| page_root: *memory.PageTable, | |
| entry: u32, | |
| quantum: u32, // Ticks remaining in time slice | |
| total_ticks: u64, // Total ticks this process has run | |
| exit_code: i32, // Exit code (valid when zombie) | |
| name: [16]u8, | |
| // File descriptor table | |
| fds: [16]?FdEntry, | |
| }; | |
| pub const FdEntry = struct { | |
| kind: FdKind, | |
| dev: ?*const DevOps, | |
| offset: u64, | |
| flags: u32, | |
| pub const FdKind = enum(u8) { | |
| none = 0, | |
| device = 1, | |
| pipe = 2, | |
| file = 3, | |
| }; | |
| }; | |
| pub const DevOps = struct { | |
| read: *const fn ([]u8) usize, | |
| write: *const fn ([]const u8) usize, | |
| close: *const fn () void, | |
| }; | |
| // βββ Global process table ββββββββββββββββββββββββββββββββββββββββ | |
| var procs: [MAX_PROCS]Process = undefined; | |
| var current_pid: u16 = 0; | |
| var next_pid: u16 = 1; | |
| // βββ Initialize process table ββββββββββββββββββββββββββββββββββββ | |
| pub fn init() void { | |
| for (&procs) |*p| { | |
| p.* = .{ | |
| .pid = 0, | |
| .state = .unused, | |
| .ksp = 0, | |
| .kstack = 0, | |
| .page_root = undefined, | |
| .entry = 0, | |
| .quantum = 0, | |
| .total_ticks = 0, | |
| .exit_code = 0, | |
| .name = std.mem.zeroes([16]u8), | |
| .fds = .{null} ** 16, | |
| }; | |
| } | |
| driver.Uart.puts("Process table: "); | |
| driver.Uart.putHex(MAX_PROCS); | |
| driver.Uart.puts(" slots initialized\n"); | |
| } | |
| // βββ Allocate a free PCB slot ββββββββββββββββββββββββββββββββββββ | |
| fn allocSlot() ?*Process { | |
| for (&procs) |*p| { | |
| if (p.state == .unused) { | |
| p.pid = next_pid; | |
| next_pid +|= 1; | |
| if (next_pid == 0) next_pid = 1; // skip 0 | |
| return p; | |
| } | |
| } | |
| return null; | |
| } | |
| // βββ Create a new process ββββββββββββββββββββββββββββββββββββββββ | |
| pub fn create(entry: u32, name: []const u8) ?*Process { | |
| const proc = allocSlot() orelse { | |
| driver.Uart.puts("proc: table full\n"); | |
| return null; | |
| }; | |
| // Allocate kernel stack | |
| const kstack = memory.allocRange(KSTACK_PAGES) orelse { | |
| proc.state = .unused; | |
| driver.Uart.puts("proc: kstack alloc failed\n"); | |
| return null; | |
| }; | |
| proc.kstack = kstack; | |
| // Set up initial trap frame at top of kernel stack | |
| const tf_addr = kstack + KSTACK_PAGES * memory.PAGE_SIZE - @sizeOf(trap.TrapFrame); | |
| const tf: *trap.TrapFrame = @ptrFromInt(tf_addr); | |
| // Zero the entire trap frame | |
| const tf_bytes: [*]u8 = @ptrCast(tf); | |
| var i: usize = 0; | |
| while (i < @sizeOf(trap.TrapFrame)) : (i += 1) { | |
| tf_bytes[i] = 0; | |
| } | |
| tf.mepc = entry; | |
| tf.mstatus = 0x00001880; // MPP=M (bits 12:11=11), MIE=0 (bit 3) | |
| // We run everything in M-mode for now, so MPP=M | |
| // Allocate user stack | |
| const ustack = memory.allocRange(USTACK_PAGES) orelse { | |
| memory.freeRange(kstack, KSTACK_PAGES); | |
| proc.state = .unused; | |
| driver.Uart.puts("proc: ustack alloc failed\n"); | |
| return null; | |
| }; | |
| tf.s0 = 0; // frame pointer | |
| tf.sp = ustack + USTACK_PAGES * memory.PAGE_SIZE; // stack grows down | |
| // Create address space | |
| const root = memory.createAddressSpace() orelse { | |
| memory.freeRange(kstack, KSTACK_PAGES); | |
| memory.freeRange(ustack, USTACK_PAGES); | |
| proc.state = .unused; | |
| driver.Uart.puts("proc: page table alloc failed\n"); | |
| return null; | |
| }; | |
| // Map user stack in the process's page table | |
| var pi: u32 = 0; | |
| while (pi < USTACK_PAGES) : (pi += 1) { | |
| const upage = ustack + pi * memory.PAGE_SIZE; | |
| const uva = upage; // Identity for M-mode | |
| if (!memory.mapPage(root, uva, upage, memory.PTE_RW)) { | |
| driver.Uart.puts("proc: ustack map failed\n"); | |
| // Clean up on failure | |
| memory.destroyAddressSpace(root); | |
| memory.freeRange(kstack, KSTACK_PAGES); | |
| memory.freeRange(ustack, USTACK_PAGES); | |
| proc.state = .unused; | |
| return null; | |
| } | |
| } | |
| proc.page_root = root; | |
| proc.ksp = tf_addr; // Trap frame is on kernel stack | |
| proc.entry = entry; | |
| proc.quantum = DEFAULT_QUANTUM; | |
| proc.total_ticks = 0; | |
| proc.exit_code = 0; | |
| proc.state = .runnable; | |
| // Copy name | |
| const len = @min(name.len, 15); | |
| @memcpy(proc.name[0..len], name[0..len]); | |
| proc.name[len] = 0; | |
| // Set up default file descriptors: stdin=0, stdout=1, stderr=2 | |
| const uart_dev = DevOps{ | |
| .read = uartDevRead, | |
| .write = uartDevWrite, | |
| .close = uartDevClose, | |
| }; | |
| // We store the dev ops pointer β it's a global, safe to point to | |
| proc.fds[0] = FdEntry{ .kind = .device, .dev = &uart_read_ops, .offset = 0, .flags = 0 }; | |
| proc.fds[1] = FdEntry{ .kind = .device, .dev = &uart_write_ops, .offset = 0, .flags = 0 }; | |
| proc.fds[2] = FdEntry{ .kind = .device, .dev = &uart_write_ops, .offset = 0, .flags = 0 }; | |
| driver.Uart.puts("Created proc "); | |
| driver.Uart.putHex(proc.pid); | |
| driver.Uart.putc(' '); | |
| driver.Uart.puts(&proc.name); | |
| driver.Uart.puts(" entry="); | |
| driver.Uart.putHex(entry); | |
| driver.Uart.putc('\n'); | |
| return proc; | |
| } | |
| // βββ UART device operations ββββββββββββββββββββββββββββββββββββββ | |
| var uart_read_ops = DevOps{ | |
| .read = uartDevRead, | |
| .write = uartDevWrite, | |
| .close = uartDevClose, | |
| }; | |
| var uart_write_ops = DevOps{ | |
| .read = uartDevRead, | |
| .write = uartDevWrite, | |
| .close = uartDevClose, | |
| }; | |
| fn uartDevRead(buf: []u8) usize { | |
| var i: usize = 0; | |
| while (i < buf.len) { | |
| if ((driver.regRead(u32, driver.MemoryMap.UART0_BASE + 0x14) & 0x01) != 0) { | |
| buf[i] = driver.Uart.getc(); | |
| i += 1; | |
| } else break; | |
| } | |
| return i; | |
| } | |
| fn uartDevWrite(buf: []const u8) usize { | |
| driver.Uart.puts(buf); | |
| return buf.len; | |
| } | |
| fn uartDevClose() void {} | |
| // βββ Get current process βββββββββββββββββββββββββββββββββββββββββ | |
| pub fn getCurrent() ?*Process { | |
| if (current_pid == 0) return null; | |
| for (&procs) |*p| { | |
| if (p.pid == current_pid and p.state == .running) return p; | |
| } | |
| return null; | |
| } | |
| pub fn setCurrentPid(pid: u16) void { | |
| current_pid = pid; | |
| } | |
| pub fn getByPid(pid: u16) ?*Process { | |
| for (&procs) |*p| { | |
| if (p.pid == pid and p.state != .unused) return p; | |
| } | |
| return null; | |
| } | |
| // βββ Default time slice ββββββββββββββββββββββββββββββββββββββββββ | |
| const DEFAULT_QUANTUM: u32 = 10; // 10 timer ticks | |
| // βββ Round-robin scheduler βββββββββββββββββββββββββββββββββββββββ | |
| pub fn schedule() ?*Process { | |
| // Find the currently running process index to start search after it | |
| var start_idx: usize = 0; | |
| if (current_pid != 0) { | |
| for (&procs, 0..) |p, i| { | |
| if (p.pid == current_pid) { | |
| start_idx = @intCast((i + 1) % MAX_PROCS); | |
| break; | |
| } | |
| } | |
| } | |
| // Scan for runnable process (round-robin from start_idx) | |
| var i: usize = 0; | |
| while (i < MAX_PROCS) : (i += 1) { | |
| const idx = (start_idx + i) % MAX_PROCS; | |
| if (procs[idx].state == .runnable) { | |
| return &procs[idx]; | |
| } | |
| } | |
| // All runnable procs exhausted their quanta β refresh | |
| var found: ?*Process = null; | |
| for (&procs) |*p| { | |
| if (p.state == .runnable) { | |
| p.quantum = DEFAULT_QUANTUM; | |
| if (found == null) found = p; | |
| } | |
| } | |
| return found; | |
| } | |
| // βββ Scheduler tick β called from timer interrupt ββββββββββββββββ | |
| pub fn schedTick() void { | |
| if (getCurrent()) |p| { | |
| if (p.quantum > 0) p.quantum -= 1; | |
| p.total_ticks += 1; | |
| } | |
| } | |
| // βββ Kill a process and free all resources βββββββββββββββββββββββ | |
| pub fn kill(pid: u16) void { | |
| const p = getByPid(pid) orelse return; | |
| // Close all open file descriptors | |
| for (&p.fds) |*fd| { | |
| if (fd.*) |*entry| { | |
| if (entry.dev) |dev| { | |
| dev.close(); | |
| } | |
| fd.* = null; | |
| } | |
| } | |
| // Free user stack | |
| const ustack_base = p.kstack + KSTACK_PAGES * memory.PAGE_SIZE - @sizeOf(trap.TrapFrame); | |
| // The user stack is tracked in the trap frame's sp field | |
| // We need to free it β but we need to figure out the ustack base from sp | |
| // Since sp = ustack + USTACK_PAGES * PAGE_SIZE, ustack = sp - USTACK_PAGES * PAGE_SIZE | |
| // However, the trap frame is on the kernel stack, and we've already set up | |
| // the sp in the initial trap frame. We need to recover it. | |
| // Read the saved sp from the trap frame | |
| const tf: *trap.TrapFrame = @ptrFromInt(@intCast(p.ksp)); | |
| const saved_sp = tf.sp; | |
| if (saved_sp != 0) { | |
| const ustack = @intCast(saved_sp) - USTACK_PAGES * memory.PAGE_SIZE; | |
| memory.freeRange(ustack, USTACK_PAGES); | |
| } | |
| // Free kernel stack | |
| memory.freeRange(p.kstack, KSTACK_PAGES); | |
| // Destroy page table and any user pages | |
| memory.destroyAddressSpace(p.page_root); | |
| // Mark as zombie (parent could collect exit code) | |
| p.exit_code = 1; | |
| p.state = .zombie; | |
| if (current_pid == pid) { | |
| current_pid = 0; | |
| } | |
| driver.Uart.puts("Killed proc "); | |
| driver.Uart.putHex(pid); | |
| driver.Uart.putc('\n'); | |
| } | |
| // βββ Exit current process ββββββββββββββββββββββββββββββββββββββββ | |
| pub fn exit(code: i32) void { | |
| if (getCurrent()) |p| { | |
| p.exit_code = code; | |
| kill(p.pid); | |
| trap.requestReschedule(); | |
| } | |
| } | |
| // βββ Yield current process βββββββββββββββββββββββββββββββββββββββ | |
| pub fn yieldExecution() void { | |
| if (getCurrent()) |p| { | |
| p.quantum = 0; // Force reschedule | |
| } | |
| trap.requestReschedule(); | |
| } | |
| // βββ Sleep current process βββββββββββββββββββββββββββββββββββββββ | |
| pub fn sleep() void { | |
| if (getCurrent()) |p| { | |
| p.state = .sleeping; | |
| } | |
| trap.requestReschedule(); | |
| } | |
| // βββ Wake a sleeping process βββββββββββββββββββββββββββββββββββββ | |
| pub fn wake(pid: u16) void { | |
| if (getByPid(pid)) |p| { | |
| if (p.state == .sleeping) { | |
| p.state = .runnable; | |
| p.quantum = DEFAULT_QUANTUM; | |
| } | |
| } | |
| } | |
| // βββ Reap zombie processes βββββββββββββββββββββββββββββββββββββββ | |
| pub fn reapZombies() u32 { | |
| var count: u32 = 0; | |
| for (&procs) |*p| { | |
| if (p.state == .zombie) { | |
| p.state = .unused; | |
| p.pid = 0; | |
| count += 1; | |
| } | |
| } | |
| return count; | |
| } | |
| // βββ Get fd for a process ββββββββββββββββββββββββββββββββββββββββ | |
| pub fn getFd(proc: *Process, fd: u32) ?*FdEntry { | |
| if (fd >= 16) return null; | |
| if (proc.fds[fd] == null) return null; | |
| return &proc.fds[fd].?; | |
| } | |
| // βββ Allocate a free fd ββββββββββββββββββββββββββββββββββββββββββ | |
| pub fn allocFd(proc: *Process) ?u32 { | |
| var i: u32 = 0; | |
| while (i < 16) : (i += 1) { | |
| if (proc.fds[i] == null) return i; | |
| } | |
| return null; | |
| } | |
| // βββ Dump all processes ββββββββββββββββββββββββββββββββββββββββββ | |
| pub fn dumpAll() void { | |
| const state_names: [5][]const u8 = .{ "unused", "runbl", "runng", "sleep", "zombi" }; | |
| driver.Uart.puts("PID STATE ENTRY KSTK QNT TICKS NAME\n"); | |
| for (&procs) |p| { | |
| if (p.state != .unused) { | |
| driver.Uart.putHex(p.pid); | |
| driver.Uart.putc(' '); | |
| const si = @intFromEnum(p.state); | |
| if (si < 5) driver.Uart.puts(state_names[si]); | |
| driver.Uart.putc(' '); | |
| driver.Uart.putHex(p.entry); | |
| driver.Uart.putc(' '); | |
| driver.Uart.putHex(@intCast(p.ksp)); | |
| driver.Uart.putc(' '); | |
| driver.Uart.putHex(p.quantum); | |
| driver.Uart.putc(' '); | |
| driver.Uart.putHex(@intCast(p.total_ticks)); | |
| driver.Uart.putc(' '); | |
| for (p.name) |ch| { | |
| if (ch == 0) break; | |
| driver.Uart.putc(ch); | |
| } | |
| driver.Uart.putc('\n'); | |
| } | |
| } | |
| } | |