Writing an Operating System Kernel from Scratch Overview This article details the implementation of a minimal proof-of-concept time-sharing operating system kernel on the RISC-V architecture using the Zig programming language. It targets readers interested in low-level system software, drivers, system calls, and operating systems fundamentals, particularly students. The kernel functions as a unikernel, bundling application and kernel into a single binary to simplify execution management. Time-sharing threads run on a single-core machine with static thread definitions, running indefinitely in user mode, making system calls to the kernel running in supervisor mode (S-mode). The use of modern tools like Zig (instead of C) and RISC-V highlights contemporary approaches to kernel design and development. Key Concepts and Background RISC-V Privilege Modes: M-mode (Machine): Bare-metal highest privilege (boot mode). S-mode (Supervisor): Runs the OS kernel. U-mode (User): Runs application threads. OpenSBI provides a base SBI (Supervisor Binary Interface) layer managing low-level services like console output and timer hardware, facilitating portability. Unikernel: Combines kernel and user code into one executable, removing complexity around dynamic loading and linking. Time-sharing threads emulate multiple concurrent workloads on a single core using timer interrupts for preemptive scheduling. Virtualization and Thread Model Threads have isolated register states and stacks, but share other memory. Programmatically, threads run uninterrupted without explicit yield calls. The kernel leverages timer interrupts to preempt and switch contexts between these threads transparently. This achieves virtualization of the CPU resource, each thread perceiving exclusive access to the core. Interrupt Context The S-mode interrupt handler saves/restores all registers and control/status registers (CSRs). This mechanism allows safe preemption without corrupting thread states. The author shows assembly code that demonstrates prologue/epilogue for interrupt handling. Implementation Details Assembly Startup In startup.S, assembly sets up stack and initializes BSS before jumping to Zig main. Kernel Main and I/O Drivers (kernel.zig) Attempts console output via OpenSBI; if unavailable falls back to UART using memory-mapped I/O. Defines three user threads printing different messages identified by thread ID. Sets up timer interrupts and installs the S-mode interrupt handler. Interrupt Handler and Context Switching The handler is implemented in Zig with the naked calling convention so the author can inject assembly for custom prologue/epilogue. Saves/restores S-mode CSRs (sstatus, sepc, scause, stval) in addition to registers. Calls handlekernel() in Zig to differentiate whether the interrupt is a system call or timer interrupt. On timer interrupt, calls schedule() that: Saves current thread stack pointer. Enqueues the current thread (ready queue). Dequeues the next thread and switches to its stack pointer. If no threads are ready, halts until next interrupt. Thread Management Threads and stacks are statically allocated and managed in a simple queue structure. Initial stack frames are seeded so threads can accept arguments (such as thread ID). User threads make system calls (e.g., printing) by issuing ecall. User Threads Example A sample user thread function prints its thread ID in a loop. It calls syscall.debugprint() to invoke kernel print system call. Includes a busy-wait delay to spread output. Running the Kernel Build kernel with zig build. Run with QEMU, specifying the OpenSBI firmware with -bios and kernel binary with -kernel. Output shows OpenSBI startup followed by interleaved print messages from threads, demonstrating successful