From 09982d80c45805715984cc1cae96c37e4fc148c3 Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 25 Sep 2026 21:21:09 +0200 Subject: feat: scheduler + cleanup --- boot/user.zig | 181 +++++++++++++++++----------------------------------------- 1 file changed, 51 insertions(+), 130 deletions(-) (limited to 'boot/user.zig') diff --git a/boot/user.zig b/boot/user.zig index 4272269..1d0839f 100644 --- a/boot/user.zig +++ b/boot/user.zig @@ -1,22 +1,18 @@ -// ring3 entry + the syscall boundary -// drop user mode with iretq, and come back to the kernel +// the syscall boundary (int 0x80). ring 3 entry itself now lives in the +// scheduler (boot/sched.zig), which iretqs into tasks, this file is just the +// syscall gate + handlers // -// see: - https://wiki.osdev.org/System_Calls -// - https://wiki.osdev.org/Getting_to_Ring_3 +// see: https://wiki.osdev.org/System_Calls const serial = @import("serial.zig"); const idt = @import("idt.zig"); -const vmm = @import("vmm.zig"); -const pmm = @import("pmm.zig"); +const sched = @import("sched.zig"); +const regs = @import("regs.zig"); +const Frame = regs.Frame; -// where enterUserAsm parked the kernel stack -export var kernel_return_rsp: u64 = 0; -// args to enterUserAsm, passed via globals so it can stay a clean naked fn :flushed: -export var g_user_entry: u64 = 0; -export var g_user_stack: u64 = 0; - -// what the syscall stub leaves on the stack (GP regs it pushed, then the frame the CPU pushed on the int 0x80) -// rax holds the syscall number +// what the syscall stub leaves on the stack: the gp regs it pushed, then the +// frame the cpu pushed on int 0x80. rax holds the syscall number in, and the +// handler writes the return value back into it (restored by the stubs pop) pub const SyscallFrame = extern struct { r15: u64, r14: u64, @@ -40,140 +36,65 @@ pub const SyscallFrame = extern struct { ss: u64, }; -// install the int 0x80 gate at DPL 3 so ring3 code is allowed to invoke it. +// install the int 0x80 gate at dpl 3 so ring3 code is allowed to invoke it pub fn installSyscall() void { - idt.setGate(0x80, @intFromPtr(&syscallStub), 3); -} - -// drop to ring 3 at g_user_entry with stack g_user_stack -// returns (via the exit syscall s longjmp) once the program calls exit -pub fn enterUser(entry: u64, ustack: u64) void { - g_user_entry = entry; - g_user_stack = ustack; - // this is the truest most correct statement ever - // get this man a true - asm volatile ("call enterUserAsm" ::: .{ .memory = true, .rax = true, .rcx = true, .rdx = true, .rsi = true, .rdi = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true }); -} - -export fn enterUserAsm() callconv(.naked) void { - asm volatile ( - \\ push %rbx - \\ push %rbp - \\ push %r12 - \\ push %r13 - \\ push %r14 - \\ push %r15 - \\ mov %rsp, kernel_return_rsp(%rip) - \\ pushq $0x23 // SS = user data (0x20) | RPL 3 - \\ mov g_user_stack(%rip), %rax - \\ push %rax // user RSP - \\ pushq $0x2 // RFLAGS: reserved bit only (IF=0 for the demo) - \\ pushq $0x1b // CS = user code (0x18) | RPL 3 - \\ mov g_user_entry(%rip), %rax - \\ push %rax // RIP = entry - \\ iretq - ); + idt.setGate(0x80, @intFromPtr(&syscallStub), 3, 1); // ist1: big stack for vm calls } +// save gp regs, hand the frame to the zig handler, restore, iretq. the handler +// returns via syscallframe.rax (restored by the pop below), exit() never returns export fn syscallStub() callconv(.naked) void { - asm volatile ( - \\ push %rax - \\ push %rbx - \\ push %rcx - \\ push %rdx - \\ push %rsi - \\ push %rdi - \\ push %rbp - \\ push %r8 - \\ push %r9 - \\ push %r10 - \\ push %r11 - \\ push %r12 - \\ push %r13 - \\ push %r14 - \\ push %r15 - \\ mov %rsp, %rdi - \\ call syscallHandler // rax = 0 continue, 1 exit - \\ test %rax, %rax - \\ jnz 1f - \\ pop %r15 - \\ pop %r14 - \\ pop %r13 - \\ pop %r12 - \\ pop %r11 - \\ pop %r10 - \\ pop %r9 - \\ pop %r8 - \\ pop %rbp - \\ pop %rdi - \\ pop %rsi - \\ pop %rdx - \\ pop %rcx - \\ pop %rbx - \\ pop %rax - \\ iretq - \\ 1: - \\ mov kernel_return_rsp(%rip), %rsp - \\ pop %r15 - \\ pop %r14 - \\ pop %r13 - \\ pop %r12 - \\ pop %rbp - \\ pop %rbx - \\ ret + asm volatile (regs.PUSH_GPRS ++ + \\ + \\ mov %rsp, %rdi + \\ call syscallHandler + \\ + ++ regs.POP_GPRS ++ + \\ + \\ iretq ); } -pub fn runProcess(pml4: u64, entry: u64, ustack: u64) void { - const prev = vmm.activePml4(); - vmm.loadPml4(pml4); - enterUser(entry, ustack); - vmm.loadPml4(prev); -} - -export fn syscallHandler(frame: *SyscallFrame) callconv(.c) u64 { +export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void { switch (frame.rax) { 0 => { // exit(code): code in rdi - return 1; // tell the stub to unwind back into the kernel + sched.exitCurrent(); // mark dead, the next tick reclaims + switches away + asm volatile ("sti"); + while (true) asm volatile ("hlt"); // never returns to this task }, 1 => { // write(ptr, len): rdi = user pointer, rsi = length const len = frame.rsi; - // the process's address space is active during the syscall, so the + // the processs address space is active during the syscall, so the // kernel can read the user pointer directly. cap the length so a - // bogus arg can't run us off into unmapped memory. + // bogus arg cant run us off into unmapped memory if (len > 0 and len <= 4096) { const bytes: [*]const u8 = @ptrFromInt(frame.rdi); - serial.write(bytes[0..len]); // mirrored -> the revo console renders it + serial.write(bytes[0..len]); // mirrored -> kernel tasks on_tick renders it } frame.rax = len; // syscall return value - return 0; }, - else => { - serial.write("[syscall] unknown\r\n"); - return 0; + 2 => { // read(): return a buffered byte, else block for one + // become the foreground reader, if a byte is already queued, take it + if (sched.readReady()) |byte| { + frame.rax = byte; + return; + } + // otherwise save a context that resumes right after this int 0x80 + // (with rax = the delivered byte), mark the task blocked, and yield + // the kernel task decodes keys and calls sched.deliverinput to wake us + const resume_frame: Frame = .{ + .r15 = frame.r15, .r14 = frame.r14, .r13 = frame.r13, .r12 = frame.r12, + .r11 = frame.r11, .r10 = frame.r10, .r9 = frame.r9, .r8 = frame.r8, + .rbp = frame.rbp, .rdi = frame.rdi, .rsi = frame.rsi, .rdx = frame.rdx, + .rcx = frame.rcx, .rbx = frame.rbx, .rax = frame.rax, + .vector = 0, .error_code = 0, + .rip = frame.rip, .cs = frame.cs, .rflags = frame.rflags, + .rsp = frame.rsp, .ss = frame.ss, + }; + sched.blockCurrentOnRead(resume_frame); + asm volatile ("sti"); + while (true) asm volatile ("hlt"); // yield, resumed via rf when input arrives }, + else => serial.write("[syscall] unknown\r\n"), } } - -// mov $1,%eax ; int $0x80 (hello) -// xor %eax,%eax ; int $0x80 (exit) -pub fn runTestProgram() void { - const as = vmm.createAddressSpace() orelse return; - const code_frame = pmm.alloc() orelse return; - const stack_frame = pmm.alloc() orelse return; - const code_virt: u64 = 0x400000; // 4 MiB - const stack_virt: u64 = 0x800000; // 8 MiB - _ = vmm.map(as, code_virt, code_frame, vmm.WRITE | vmm.USER); - _ = vmm.map(as, stack_virt, stack_frame, vmm.WRITE | vmm.USER); - - const code = [_]u8{ 0xB8, 0x01, 0x00, 0x00, 0x00, 0xCD, 0x80, 0x31, 0xC0, 0xCD, 0x80 }; - const dst: [*]u8 = @ptrFromInt(pmm.physToVirt(code_frame)); // write via HHDM - @memcpy(dst[0..code.len], &code); - - const prev = vmm.activePml4(); - vmm.loadPml4(as); - serial.write("[user] entering ring 3...\r\n"); - enterUser(code_virt, stack_virt + pmm.PAGE_SIZE); - vmm.loadPml4(prev); - serial.write("[user] back in the kernel, continuing\r\n"); -} -- cgit v1.3