<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about systems programming)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/systems-programming.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 A.C. Jokela 
&lt;!-- div style="width: 100%" --&gt;
&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /&gt; Creative Commons Attribution-ShareAlike&lt;/a&gt;&amp;nbsp;|&amp;nbsp;
&lt;!-- /div --&gt;
</copyright><lastBuildDate>Wed, 11 Mar 2026 00:05:47 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>JokelaOS: Writing a Bare-Metal x86 Kernel from Scratch</title><link>https://tinycomputers.io/posts/jokelaos-bare-metal-x86-kernel.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/jokelaos-bare-metal-x86-kernel_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;30 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;There's a moment early in any OS project where the serial port prints its first character and you realize that nothing you've written has a safety net. No libc. No kernel underneath. No syscall to fall back on. If the byte appears on the terminal, it's because you programmed the UART (Universal Asynchronous Receiver-Transmitter) divisor latch, polled the line status register, and wrote to the data port. If it doesn't appear, you stare at register dumps until you find the mistake. There's no debugger — you haven't written one yet.&lt;/p&gt;
&lt;p&gt;The closest thing I can compare it to is the first time I got a &lt;a href="https://tinycomputers.io/posts/arduino-z80-+-forth.html"&gt;RetroShield Z80&lt;/a&gt; talking over serial — that moment where a processor you wired up yourself pushes a character out of an emulated ACIA and it appears on your screen. The Z80 version involves physical hardware and solder. The x86 version is virtual — QEMU, a cross-compiler, and a Multiboot header — but the feeling is the same. You built the entire path from CPU to character. Nothing was given to you.&lt;/p&gt;
&lt;p&gt;JokelaOS started there: a Multiboot header, a stack, and a &lt;code&gt;call kmain&lt;/code&gt;. Everything that followed — GDT (Global Descriptor Table), IDT (Interrupt Descriptor Table), memory management, a network stack, preemptive multitasking, paging, user mode, a shell — was built one subsystem at a time, tested after every change, with no external code. No forks of existing kernels. No libc. No shortcuts.&lt;/p&gt;
&lt;p&gt;To be clear about what this is: JokelaOS is a toy. It's a learning project. The memory allocator is a linear scan. The scheduler has no concept of priority. The file system can't delete files. The user authentication stores passwords in plaintext in a static array. Nothing here is production-grade, and none of it is intended to be. The value is in the building — understanding what each subsystem actually does by writing it from scratch, making the mistakes, and fixing them with nothing between you and the hardware.&lt;/p&gt;
&lt;p&gt;This is the story of what it takes to go from twenty lines of NASM to a kernel that boots, manages memory, runs user programs in Ring 3, handles syscalls, responds to pings, and gives you a command prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/jokelaos/jokelaos0.png" alt="JokelaOS boot sequence in QEMU — GDT, IDT, PCI enumeration, memory map, paging init, RTL8139 driver, and network stack initialization" style="max-width: 100%; border-radius: 6px; box-shadow: 0 10px 20px rgba(0,0,0,.1); margin: 1em 0;" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;The Target&lt;/h3&gt;
&lt;p&gt;JokelaOS targets 32-bit x86 (i686) and runs under QEMU. The toolchain is a cross-compiler (&lt;code&gt;i686-elf-gcc&lt;/code&gt;, &lt;code&gt;i686-elf-ld&lt;/code&gt;) with NASM (Netwide Assembler) for the assembly files. The C standard is &lt;code&gt;gnu11&lt;/code&gt; — GNU extensions are required for inline assembly. There are no external libraries whatsoever, not even a freestanding &lt;code&gt;string.h&lt;/code&gt;. Every &lt;code&gt;memcpy&lt;/code&gt;, every &lt;code&gt;memset&lt;/code&gt;, every &lt;code&gt;printf&lt;/code&gt;-like function is written from scratch.&lt;/p&gt;
&lt;p&gt;The only console is the serial port. COM1 at 0x3F8, 115200 baud, 8N1 (8 data bits, no parity, 1 stop bit). All kernel output goes through &lt;code&gt;serial_printf()&lt;/code&gt;. This is a deliberate choice: serial is simpler than VGA text mode, works perfectly with QEMU's &lt;code&gt;-serial stdio&lt;/code&gt;, and means the kernel's output appears directly in the host terminal. No framebuffer driver needed, no font rendering, no cursor management. Just bytes on a wire.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;$&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;run
qemu-system-i386&lt;span class="w"&gt; &lt;/span&gt;-kernel&lt;span class="w"&gt; &lt;/span&gt;build/jokelaos.bin&lt;span class="w"&gt; &lt;/span&gt;-serial&lt;span class="w"&gt; &lt;/span&gt;stdio&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-display&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="w"&gt; &lt;/span&gt;-device&lt;span class="w"&gt; &lt;/span&gt;rtl8139,netdev&lt;span class="o"&gt;=&lt;/span&gt;net0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-netdev&lt;span class="w"&gt; &lt;/span&gt;user,id&lt;span class="o"&gt;=&lt;/span&gt;net0&lt;span class="w"&gt; &lt;/span&gt;-no-reboot
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Kernel Architecture: Where JokelaOS Fits&lt;/h3&gt;
&lt;p&gt;Before getting into implementation, it's worth understanding the design space. Not all kernels are structured the same way, and the choice of architecture has consequences that ripple through every subsystem.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;monolithic kernel&lt;/strong&gt; puts everything — memory management, scheduling, file systems, device drivers, the network stack — into a single binary running in Ring 0. All kernel code shares one address space. A function call from the scheduler into the memory manager is just that: a function call. No context switches, no message serialization, no copying buffers between address spaces. Linux is monolithic. So are the BSDs. So is JokelaOS.&lt;/p&gt;
&lt;p&gt;The advantage is performance and simplicity. When the network stack needs to allocate a page, it calls &lt;code&gt;pmm_alloc_frame()&lt;/code&gt; directly. When the shell wants to load a program, it calls the loader, which calls the PMM (Physical Memory Manager), which calls the paging subsystem — all in the same address space, all with the same privilege level, all at the cost of a function call. There's no overhead beyond what the work itself requires.&lt;/p&gt;
&lt;p&gt;The disadvantage is that every driver, every subsystem, every line of kernel code has full access to every other line of kernel code's memory. A bug in the RTL8139 driver can corrupt the process table. A buffer overrun in the serial port handler can overwrite page tables. In a production monolithic kernel like Linux, this is mitigated by code review, testing, and an enormous community of contributors. In a toy kernel written by one person, it means bugs are spectacular.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;microkernel&lt;/strong&gt; takes the opposite approach. Only the absolute minimum runs in Ring 0: the scheduler, IPC (Inter-Process Communication) message passing, and basic memory management. Everything else — file systems, device drivers, the network stack — runs as separate user-space processes (called "servers") that communicate through message passing. Mach, developed at Carnegie Mellon in the 1980s, is the canonical example. MINIX 3 is a modern realization of the idea, designed by Andrew Tanenbaum specifically to demonstrate microkernel reliability. L4 and its descendants (seL4, which has a formal mathematical proof of correctness) represent the performance-optimized end of the microkernel spectrum.&lt;/p&gt;
&lt;p&gt;The advantage is isolation. If the network driver crashes, it crashes in its own address space. The kernel restarts it. The file system never noticed. This matters enormously for reliability and security — seL4 is used in military and aviation systems where "the driver crashed and took the kernel with it" is not acceptable.&lt;/p&gt;
&lt;p&gt;The disadvantage is IPC overhead. Every interaction between subsystems that would be a function call in a monolithic kernel becomes a message: marshal the arguments, trap into the kernel, copy the message to the destination server's address space, schedule that server, let it process the request, marshal the reply, trap back. Mach's original implementation was notoriously slow — sometimes 50-70% slower than monolithic equivalents for system-call-heavy workloads. L4 demonstrated that much of this overhead was implementation quality rather than an inherent property of the architecture, but the fundamental cost of crossing address space boundaries doesn't disappear.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;hybrid kernel&lt;/strong&gt; tries to split the difference. Windows NT is the most commercially successful example: it has a microkernel-like separation of concerns in its architecture, but runs most of the subsystems that a pure microkernel would put in user space (the window manager, parts of the file system, device drivers) in kernel mode for performance. macOS runs XNU, which is a Mach microkernel fused with a BSD monolithic kernel — Mach handles the low-level primitives (memory management, IPC, scheduling), while the BSD layer provides the POSIX API, the file system, and networking, all running in Ring 0. It's a microkernel by lineage but monolithic in practice.&lt;/p&gt;
&lt;p&gt;There are more exotic designs. &lt;strong&gt;Exokernels&lt;/strong&gt;, researched at MIT in the 1990s, eliminate almost all kernel abstractions and let applications manage hardware resources directly, with the kernel only enforcing protection. &lt;strong&gt;Unikernels&lt;/strong&gt; (MirageOS, IncludeOS) compile the application and a minimal OS library into a single binary that runs directly on the hypervisor — no ring separation at all, because there's only one program and it's trusted by definition.&lt;/p&gt;
&lt;p&gt;JokelaOS is monolithic, and deliberately so. It's the simplest architecture to implement, it's the easiest to debug (everything is in one address space, so a &lt;code&gt;serial_printf()&lt;/code&gt; anywhere can see anything), and it's what you build when you're trying to understand how each subsystem works in isolation before worrying about how to decouple them. A microkernel JokelaOS would be a more interesting engineering artifact, but it would also be three times as much code before you could print a single character — you'd need working IPC before the serial driver could talk to anything.&lt;/p&gt;
&lt;h3&gt;Booting: The First 33 Lines&lt;/h3&gt;
&lt;p&gt;The entire boot sequence fits in &lt;code&gt;boot.asm&lt;/code&gt;. Multiboot v1 requires a magic number (&lt;code&gt;0x1BADB002&lt;/code&gt;), flags, and a checksum in a specific header format. GRUB or QEMU's &lt;code&gt;-kernel&lt;/code&gt; loader scans for this header, loads the binary, and jumps to &lt;code&gt;_start&lt;/code&gt; in protected mode with paging disabled.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;section&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;.multiboot&lt;/span&gt;
&lt;span class="k"&gt;align&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;dd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x1BADB002&lt;/span&gt;&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="c1"&gt;; Multiboot magic&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;dd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x00000003&lt;/span&gt;&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="c1"&gt;; Flags: page-align + memory map&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;dd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x1BADB002&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x00000003&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;; Checksum&lt;/span&gt;

&lt;span class="k"&gt;section&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;.text&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;_start&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kmain&lt;/span&gt;

&lt;span class="nl"&gt;_start:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;esp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stack_top&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;popf&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;; Clear EFLAGS&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ebx&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;; Multiboot info struct pointer&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eax&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;; Multiboot magic number&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kmain&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cli&lt;/span&gt;
&lt;span class="nl"&gt;.hang:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;hlt&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;jmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;.hang&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's it. Set up a stack, clear the flags register, push the two values the Multiboot spec guarantees (magic number in EAX, info struct pointer in EBX), and call C. If &lt;code&gt;kmain&lt;/code&gt; ever returns, disable interrupts and halt forever.&lt;/p&gt;
&lt;p&gt;The 16 KB stack is allocated in the BSS (Block Started by Symbol) section — the region where uninitialized global data lives, zeroed at load time. The linker script places the kernel at 1 MB (the standard x86 protected-mode load address), with &lt;code&gt;.multiboot&lt;/code&gt; first so the bootloader can find the header within the first 8 KB of the binary.&lt;/p&gt;
&lt;h3&gt;Protection Rings: Hardware-Enforced Privilege&lt;/h3&gt;
&lt;p&gt;x86 protected mode provides four privilege levels, numbered 0 through 3, called rings. Ring 0 is the most privileged — the kernel runs here. Ring 3 is the least privileged — user programs run here. Rings 1 and 2 exist in the hardware but almost nobody uses them. Linux doesn't. Windows doesn't. JokelaOS doesn't. The practical x86 privilege model is two rings: kernel and user.&lt;/p&gt;
&lt;p&gt;The ring system isn't a software convention. It's enforced by the CPU itself, in silicon. The processor tracks the Current Privilege Level (CPL) — the ring the currently executing code belongs to — and checks it against every sensitive operation. A Ring 3 process that executes &lt;code&gt;cli&lt;/code&gt; (disable interrupts), &lt;code&gt;hlt&lt;/code&gt; (halt the CPU), &lt;code&gt;lgdt&lt;/code&gt; (load a new GDT), or &lt;code&gt;mov cr3&lt;/code&gt; (change the page directory) triggers a General Protection Fault. The CPU literally refuses to execute the instruction. A Ring 3 process can't touch I/O ports unless the kernel has explicitly granted access through the I/O Permission Bitmap in the TSS. It can't modify its own segment registers to escalate privilege, because the CPU validates every segment load against the descriptor's DPL (Descriptor Privilege Level).&lt;/p&gt;
&lt;p&gt;The only way for Ring 3 code to enter Ring 0 is through a gate — an interrupt gate, a trap gate, or a call gate. Gates are entries in the IDT or GDT that the kernel sets up in advance. They define the exact entry points where Ring 3 code can cross into Ring 0, what the new code and stack segments will be, and what privilege level is required to use them. There's no way for user code to jump to an arbitrary kernel address. It can only enter the kernel through the doors the kernel has built.&lt;/p&gt;
&lt;p&gt;This is what makes an operating system an operating system rather than a library. Without ring separation, a buggy user program can corrupt kernel memory, disable interrupts, reprogram the PIC, or overwrite the page tables. With ring separation, the worst it can do is crash itself.&lt;/p&gt;
&lt;p&gt;The mechanism that implements all of this is the Global Descriptor Table.&lt;/p&gt;
&lt;h3&gt;The GDT: Defining the World&lt;/h3&gt;
&lt;p&gt;The GDT defines memory segments — their base addresses, sizes, privilege levels, and whether they hold code or data. Each segment descriptor is an 8-byte structure with fields packed into non-obvious bit positions (a consequence of backward compatibility with the 286, which had a different descriptor format that the 386 had to extend without breaking).&lt;/p&gt;
&lt;p&gt;JokelaOS uses a flat memory model: every segment covers the full 4 GB address space with base 0 and limit 0xFFFFFFFF. The segmentation hardware is effectively nullified, which is what you want on modern x86 where paging handles memory protection. But the GDT is still mandatory — the CPU requires it for the ring system to function. Even with flat segments, the DPL field in each descriptor is what tells the CPU "code using this segment is Ring 0" or "code using this segment is Ring 3."&lt;/p&gt;
&lt;p&gt;The GDT has six entries:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;Selector&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0x00&lt;/td&gt;
&lt;td&gt;Null descriptor (required by x86)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0x08&lt;/td&gt;
&lt;td&gt;Kernel code (Ring 0, execute/read)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0x10&lt;/td&gt;
&lt;td&gt;Kernel data (Ring 0, read/write)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0x18&lt;/td&gt;
&lt;td&gt;User code (Ring 3, execute/read)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0x20&lt;/td&gt;
&lt;td&gt;User data (Ring 3, read/write)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0x28&lt;/td&gt;
&lt;td&gt;Task State Segment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Entries 1 and 2 are identical to entries 3 and 4 in every way except the DPL field — two bits in the access byte that say &lt;code&gt;00&lt;/code&gt; (Ring 0) versus &lt;code&gt;11&lt;/code&gt; (Ring 3). That two-bit difference is the entire kernel/user boundary.&lt;/p&gt;
&lt;p&gt;When a user process runs, the CPU's CS register is loaded with 0x1B — that's selector 0x18 (pointing to GDT entry 3, the user code segment) OR'd with RPL 3 (Requested Privilege Level, the bottom two bits of the selector). The data segment registers get 0x23 (GDT entry 4, user data, RPL 3). The CPU sets CPL to match, and from that point on, every instruction is checked against Ring 3 privileges. The kernel runs with CS=0x08 (GDT entry 1, RPL 0) and DS=0x10 (GDT entry 2, RPL 0).&lt;/p&gt;
&lt;p&gt;The TSS (Task State Segment) is the bridge between rings. When the CPU takes an interrupt while running Ring 3 code, it needs to switch to a Ring 0 stack — you can't trust the user's stack pointer to be valid, and you certainly can't run kernel interrupt handlers on a user-controlled stack. The TSS holds the Ring 0 stack pointer (&lt;code&gt;esp0&lt;/code&gt;). Every context switch updates the TSS with the current process's kernel stack, so the CPU always knows where to land when transitioning from user mode to kernel mode.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;gdt_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gdt_set_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="c1"&gt;// Null&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gdt_set_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x9A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xCF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Kernel code&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gdt_set_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xCF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Kernel data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gdt_set_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xCF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// User code&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gdt_set_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xF2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xCF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// User data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TSS entry built separately&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The access byte &lt;code&gt;0x9A&lt;/code&gt; means: present, Ring 0, code segment, executable, readable. &lt;code&gt;0xFA&lt;/code&gt; means the same thing but Ring 3. These magic numbers come straight from the Intel manuals and they're the kind of thing you get wrong three times before you get right once.&lt;/p&gt;
&lt;h3&gt;Interrupts: Exceptions, IRQs, and the PIC&lt;/h3&gt;
&lt;p&gt;The IDT (Interrupt Descriptor Table) maps interrupt vectors to handler functions. JokelaOS sets up 256 entries: CPU exceptions (0-31), hardware IRQs (32-47), and the syscall gate (0x80).&lt;/p&gt;
&lt;p&gt;The x86 PIC (Programmable Interrupt Controller) needs remapping. By default, the master PIC maps IRQs 0-7 to interrupt vectors 8-15, which collide with CPU exceptions (double fault is vector 8, for instance). The standard fix is to remap the master PIC to vectors 32-39 and the slave to 40-47. This requires sending four Initialization Command Words to each PIC in the correct sequence — the kind of hardware protocol that hasn't changed since the IBM PC/AT in 1984.&lt;/p&gt;
&lt;p&gt;ISR (Interrupt Service Routine) stubs are written in NASM. Each one pushes an error code (or a dummy zero for exceptions that don't push one), pushes the interrupt number, saves all general-purpose registers, calls the C handler, restores registers, and does an &lt;code&gt;iret&lt;/code&gt;. The stubs are generated with macros:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;%macro ISR_NOERRCODE 1&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;isr&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;isr&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;dword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;; dummy error code&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;dword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;; interrupt number&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;jmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;isr_common&lt;/span&gt;
&lt;span class="cp"&gt;%endmacro&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The C-side dispatcher checks the interrupt number. For exceptions (0-31), it prints the register state and halts — there's no recovery from a page fault when you don't have a page fault handler yet. For IRQs (32-47), it calls the registered handler function and sends an EOI (End of Interrupt) command to the PIC. For interrupt 0x80, it dispatches to the syscall handler.&lt;/p&gt;
&lt;p&gt;One critical detail: interrupt 0x80 is set as a &lt;strong&gt;trap gate&lt;/strong&gt; with DPL 3, not an interrupt gate. This means Ring 3 code can trigger it with &lt;code&gt;int 0x80&lt;/code&gt;. All other interrupt gates are DPL 0 — a user program that tries to execute &lt;code&gt;int 0x00&lt;/code&gt; gets a General Protection Fault instead. This is the mechanism that makes syscalls work while keeping everything else protected.&lt;/p&gt;
&lt;h3&gt;Memory: Three Allocators&lt;/h3&gt;
&lt;p&gt;JokelaOS has three layers of memory management, each built on top of the previous one.&lt;/p&gt;
&lt;h4&gt;The Bump Allocator&lt;/h4&gt;
&lt;p&gt;The simplest possible allocator. A pointer starts at the first page boundary after the kernel image (&lt;code&gt;_kernel_end&lt;/code&gt; from the linker script) and only moves forward. &lt;code&gt;kmalloc(size)&lt;/code&gt; aligns the pointer to 16 bytes, returns it, and advances by &lt;code&gt;size&lt;/code&gt;. There is no &lt;code&gt;kfree()&lt;/code&gt;. Memory allocated with the bump allocator is permanent.&lt;/p&gt;
&lt;p&gt;This sounds primitive, and it is. But it's also exactly right for kernel initialization. The GDT, IDT, page tables, file system metadata, user table — these are allocated once and never freed. The bump allocator handles all of them with zero fragmentation and zero overhead.&lt;/p&gt;
&lt;h4&gt;The Physical Memory Manager&lt;/h4&gt;
&lt;p&gt;Once the kernel needs to allocate and free pages dynamically (for process stacks, program code, page tables), it needs a real allocator. The PMM uses a bitmap: one bit per 4 KB physical frame, supporting up to 256 MB of RAM (65,536 frames, 8 KB bitmap).&lt;/p&gt;
&lt;p&gt;Initialization parses the Multiboot memory map to find usable RAM regions, then marks everything from frame 0 through the end of the bump heap as reserved. This protects the IVT (Interrupt Vector Table), BIOS data area, kernel image, and all bump-allocated structures from being handed out as free pages.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pmm_alloc_frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_frames&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;free_count&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// out of memory&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Linear scan, no free lists, no buddy system. It's O(n) per allocation, which is fine when n is measured in thousands and allocations are infrequent. A production kernel would use something smarter. This kernel allocates a few dozen pages total.&lt;/p&gt;
&lt;h4&gt;Paging&lt;/h4&gt;
&lt;p&gt;With physical frames available, the kernel can enable paging. &lt;code&gt;paging_init()&lt;/code&gt; builds a page directory and 32 page tables, identity-mapping the first 128 MB of physical memory (virtual address = physical address). The page directory goes into CR3, and setting the PG bit in CR0 turns the MMU (Memory Management Unit) on.&lt;/p&gt;
&lt;p&gt;Identity mapping means the kernel doesn't need to worry about virtual-to-physical translation for its own code and data. Kernel pointers just work. When user processes need memory, the loader allocates physical frames and maps them into the process's address space with the PG_USER flag set, allowing Ring 3 access.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;paging_map_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dir_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tbl_idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x3FF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_directory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dir_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PG_PRESENT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tbl_frame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pmm_alloc_frame&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;tbl_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;page_directory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dir_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tbl_frame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PG_PRESENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PG_WRITE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;page_directory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dir_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFF000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tbl_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFFFF000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;asm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invlpg (%0)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;virt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;invlpg&lt;/code&gt; instruction flushes the TLB (Translation Lookaside Buffer) entry for the mapped virtual address, which is critical — without it, the CPU might use a stale translation from its cache and access the wrong physical page.&lt;/p&gt;
&lt;h3&gt;The Network Stack&lt;/h3&gt;
&lt;p&gt;JokelaOS has a working network stack — the one subsystem where "toy" undersells it slightly. It resolves ARP (Address Resolution Protocol), constructs IPv4 (Internet Protocol version 4) packets with correct checksums, and handles ICMP (Internet Control Message Protocol) echo request/reply with measured round-trip times. There's no TCP (Transmission Control Protocol), no UDP (User Datagram Protocol), no sockets. But the packets that leave this kernel are real packets that traverse real networks.&lt;/p&gt;
&lt;p&gt;The NIC (Network Interface Controller) is an emulated RTL8139, the simplest PCI (Peripheral Component Interconnect) Ethernet controller that QEMU supports. The driver initializes the chip by writing to its configuration registers: reset, enable transmitter and receiver, set up a receive ring buffer, configure the interrupt mask, and unmask IRQ 11. Packet transmission uses a four-descriptor TX ring; reception is interrupt-driven through the RTL8139's ring buffer.&lt;/p&gt;
&lt;p&gt;PCI enumeration scans the configuration space to find the RTL8139 by vendor/device ID (0x10EC:0x8139), reads the I/O base address from BAR0 (Base Address Register 0), and enables bus mastering. This is the only driver in the system — there's no USB, no disk, no display. One NIC, one network.&lt;/p&gt;
&lt;p&gt;The stack is layered:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ethernet.c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Frame demux by EtherType&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arp.c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Table + request/reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ipv4.c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routing, header checksum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;&lt;code&gt;icmp.c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Echo reply + outgoing ping&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On boot, the kernel sends an ARP request for the gateway (10.0.2.2, QEMU's default) and waits for the reply. Once the gateway's MAC (Media Access Control) address is resolved, the kernel can ping arbitrary hosts through QEMU's SLIRP (Session-Level IP Redirect Protocol) NAT (Network Address Translation). A &lt;code&gt;ping 10.1.1.1&lt;/code&gt; from the shell constructs an ICMP echo request, wraps it in an IPv4 packet, wraps that in an Ethernet frame, and pushes it out through the RTL8139's TX ring. When the reply comes back, the receive ISR fires, the Ethernet layer demuxes by EtherType, the IP layer validates the checksum, and the ICMP handler matches the echo reply to the outstanding request and computes the RTT (round-trip time).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pinging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.1.1.1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Getting here required writing every byte-order conversion (&lt;code&gt;htons&lt;/code&gt;, &lt;code&gt;htonl&lt;/code&gt;), every checksum computation (the IP header checksum is a one's complement sum of 16-bit words), every packet layout (Ethernet header is 14 bytes, IP header is 20, ICMP is 8 plus payload). None of this is hard individually. Together, it's a thousand places to put a byte in the wrong order.&lt;/p&gt;
&lt;h3&gt;Processes and Preemptive Multitasking&lt;/h3&gt;
&lt;p&gt;The process subsystem manages up to 16 processes in a static table. Each process has a state (UNUSED, READY, RUNNING, DEAD), a kernel stack pointer, and a user-mode entry point and stack.&lt;/p&gt;
&lt;p&gt;Process creation doesn't follow the UNIX &lt;code&gt;fork()&lt;/code&gt;/&lt;code&gt;exec()&lt;/code&gt; model. There's no cloning of address spaces, no copy-on-write, no replacing the current process image. Instead, the loader allocates fresh physical frames for the program's code and stack, copies the flat binary into the code pages, and calls &lt;code&gt;proc_create()&lt;/code&gt;, which allocates a 4 KB kernel stack and builds a fake stack frame on it. This stack frame is what &lt;code&gt;context_switch()&lt;/code&gt; will "return" into on the process's first schedule — it contains saved registers and a return address pointing to &lt;code&gt;proc_entry_user()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proc_entry_user()&lt;/code&gt; is a small assembly sequence that performs the Ring 0 to Ring 3 transition. It sets the data segment registers to the user data selector (0x23), pushes a fake interrupt frame (SS, ESP, EFLAGS with IF=1, CS, EIP), and executes &lt;code&gt;iret&lt;/code&gt;. The CPU pops the frame, switches to Ring 3, and starts executing the user program. From the hardware's perspective, this looks identical to returning from an interrupt that happened to interrupt a user-mode program — which is exactly the trick.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;proc_entry_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;process_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;asm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"mov $0x23, %%ax &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"mov %%ax, %%ds  &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"mov %%ax, %%es  &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"mov %%ax, %%fs  &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"mov %%ax, %%gs  &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"push $0x23      &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// SS&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"push %0         &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// ESP&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"pushf           &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"pop %%eax       &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"or $0x200, %%eax&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// Set IF&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"push %%eax      &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// EFLAGS&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"push $0x1B      &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// CS (user code)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"push %1         &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// EIP&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;"iret"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_esp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_eip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"eax"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"memory"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Context switching uses a simple assembly stub in &lt;code&gt;switch.asm&lt;/code&gt;. It saves the callee-saved registers (EBP, EBX, ESI, EDI), stores ESP into the old process's slot, loads the new process's ESP, restores registers, and returns. The &lt;code&gt;ret&lt;/code&gt; instruction pops the return address from the new stack and resumes where that process left off.&lt;/p&gt;
&lt;p&gt;Scheduling is preemptive round-robin. The PIT (Programmable Interval Timer) fires at 1000 Hz. Every 10 ticks (10 ms), the IRQ handler calls &lt;code&gt;proc_schedule()&lt;/code&gt;, which finds the next READY process and switches to it. If no user processes are ready, control stays with PID 0 (the kernel/shell). This is the minimum viable scheduler — no priorities, no time slices, no fairness guarantees. But it works: two user programs printing characters to serial run concurrently, interleaved by the timer.&lt;/p&gt;
&lt;h3&gt;Syscalls&lt;/h3&gt;
&lt;p&gt;User programs communicate with the kernel through &lt;code&gt;int 0x80&lt;/code&gt;. The mechanism — a software interrupt that transitions from Ring 3 to Ring 0 — is the same one Linux used on i386 before &lt;code&gt;sysenter&lt;/code&gt; replaced it. The register convention is borrowed too: syscall number in EAX, arguments in EBX/ECX/EDX/ESI/EDI, return value in EAX. But that's where the resemblance ends.&lt;/p&gt;
&lt;p&gt;JokelaOS is not a UNIX. The syscall numbers are custom — exit is 0, write is 1, getpid is 2, read is 3 — not Linux's i386 table (where exit is 1, read is 3, write is 4, getpid is 20). There's no &lt;code&gt;fork()&lt;/code&gt;, no &lt;code&gt;exec()&lt;/code&gt;, no &lt;code&gt;open()&lt;/code&gt;, no &lt;code&gt;close()&lt;/code&gt;, no signals, no pipes. File descriptors 0 and 1 exist as concepts (stdin maps to the keyboard buffer, stdout maps to the serial port) but there's no file descriptor table behind them. The syscall handler just checks &lt;code&gt;if (fd == 1)&lt;/code&gt; and calls &lt;code&gt;serial_putchar()&lt;/code&gt;. The process model isn't UNIX either — there's no parent/child relationship, no &lt;code&gt;wait()&lt;/code&gt;, no process groups. Processes are created by the loader and scheduled round-robin until they exit. It's closer to a microcontroller RTOS (Real-Time Operating System) than to anything in the UNIX lineage.&lt;/p&gt;
&lt;p&gt;Four syscalls are implemented:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Arguments&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;SYS_EXIT&lt;/td&gt;
&lt;td&gt;ebx=status&lt;/td&gt;
&lt;td&gt;Terminate process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SYS_WRITE&lt;/td&gt;
&lt;td&gt;ebx=fd, ecx=buf, edx=len&lt;/td&gt;
&lt;td&gt;Write to serial (fd=1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SYS_GETPID&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Return current PID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SYS_READ&lt;/td&gt;
&lt;td&gt;ebx=fd, ecx=buf, edx=len&lt;/td&gt;
&lt;td&gt;Read from keyboard (fd=0)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is enough to write programs that print output, read input, identify themselves, and exit cleanly. The syscall dispatcher validates file descriptors (only 0 and 1 are legal) and bounds-checks lengths. SYS_WRITE sends bytes to the serial port; SYS_READ drains the keyboard buffer non-blocking.&lt;/p&gt;
&lt;p&gt;User programs are flat binaries — raw machine code with no headers, no relocations, no ELF (Executable and Linkable Format) parsing. The loader copies the binary to freshly allocated pages and jumps to byte zero. Programs that need to reference their own data use position-independent tricks:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;next&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;; push EIP&lt;/span&gt;
&lt;span class="nl"&gt;next:&lt;/span&gt;
&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ebp&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;; EBP = address of this instruction&lt;/span&gt;
&lt;span class="nf"&gt;lea&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ecx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;ebp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;offset_to_data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the same technique used by shellcode and position-independent code on x86. It works because &lt;code&gt;call&lt;/code&gt; pushes the address of the next instruction, which gives you a known reference point relative to the code's actual load address.&lt;/p&gt;
&lt;h3&gt;The Shell&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/jokelaos/jokelaos1.png" alt="JokelaOS running in QEMU — ping output, login prompt, and ps command showing process table" style="max-width: 100%; border-radius: 6px; box-shadow: 0 10px 20px rgba(0,0,0,.1); margin: 0 0 1em 0;" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;With all the subsystems in place, the shell ties them together into something interactive. &lt;code&gt;shell_run()&lt;/code&gt; is the kernel's main loop after initialization — it presents a login prompt, authenticates against the user table, and drops into a command interpreter.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="o"&gt;==============================&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;JokelaOS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;
&lt;span class="o"&gt;==============================&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GDT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TSS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IDT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remapped&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Multiboot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;confirmed&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Multiboot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x9500&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allocator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;

&lt;span class="n"&gt;PCI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;03.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;EC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8139&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTL8139&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTL8139&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IRQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;ramfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;guest&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;PMM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31269&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;free&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Paging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hz&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Keyboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;

&lt;span class="n"&gt;JokelaOS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;
&lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;****&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The shell supports: &lt;code&gt;help&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;run &amp;lt;program&amp;gt;&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;mem&lt;/code&gt;, &lt;code&gt;ping &amp;lt;ip&amp;gt;&lt;/code&gt;, &lt;code&gt;uptime&lt;/code&gt;, &lt;code&gt;whoami&lt;/code&gt;, and &lt;code&gt;logout&lt;/code&gt;. The line editor handles backspace. Password input echoes asterisks. The &lt;code&gt;run&lt;/code&gt; command loads a flat binary from ramfs, creates a process, and the scheduler picks it up on the next timer tick.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ps&lt;/code&gt; shows the process table:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;root$ ps
  PID  STATE
    0  RUNNING
    1  READY
    2  DEAD
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;mem&lt;/code&gt; shows memory usage:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;root$ mem
Heap used: 8832 bytes
PMM free:  31267 frames (122 MB)
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The keyboard input path is worth noting. The PS/2 keyboard controller fires IRQ 1. The handler reads the scancode from port 0x60, converts it to ASCII using a US QWERTY lookup table (with shift modifier tracking), and drops it into a 256-byte circular buffer. Serial input takes the same path — the UART's receive interrupt (IRQ 4) reads the incoming byte and injects it into the keyboard buffer. This means the shell works identically whether you're typing on a PS/2 keyboard or through the QEMU serial console.&lt;/p&gt;
&lt;h3&gt;The RAM File System&lt;/h3&gt;
&lt;p&gt;User programs need to live somewhere. With no disk driver, the file system is purely in-memory. &lt;code&gt;ramfs&lt;/code&gt; stores up to 32 files, each with a name (28 bytes), a data pointer, and a size. &lt;code&gt;ramfs_create()&lt;/code&gt; allocates space with the bump allocator and copies the binary in. &lt;code&gt;ramfs_find()&lt;/code&gt; does a linear search by name.&lt;/p&gt;
&lt;p&gt;During boot, two test programs are embedded directly in &lt;code&gt;kmain.c&lt;/code&gt; as byte arrays of hand-assembled x86 machine code. One prints the character '1' ten times; the other prints '2' ten times. Both use SYS_WRITE to output through the serial port and SYS_EXIT to terminate cleanly. They're loaded into ramfs, and &lt;code&gt;run print1&lt;/code&gt; from the shell executes them in user mode.&lt;/p&gt;
&lt;p&gt;This is about as minimal as a file system gets. No directories, no permissions, no deletion. But it demonstrates the complete path from "bytes in kernel memory" to "user-mode process executing with its own address space."&lt;/p&gt;
&lt;h3&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Writing a kernel from scratch teaches you things that no amount of reading about kernels will teach you. Some of these are technical. Most are about the nature of systems programming itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The boot process is the hardest part.&lt;/strong&gt; Not because the code is complex — &lt;code&gt;boot.asm&lt;/code&gt; is 33 lines — but because when something goes wrong, you have zero diagnostic capability. The serial port isn't initialized yet. The IDT isn't loaded. If your Multiboot header checksum is wrong by one bit, QEMU silently fails. You're debugging with QEMU's &lt;code&gt;-d int&lt;/code&gt; flag and reading hex dumps of interrupt frames.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;x86 protected mode is an archaeology project.&lt;/strong&gt; The PIC remapping sequence dates from the IBM PC/AT (1984). The GDT access bytes encode information in bit patterns designed for hardware that predates flat memory models. The TSS exists because Intel's original vision for the 286 involved hardware task switching that nobody ended up using. You're programming against forty years of backward compatibility, and every one of those layers is still there, still mandatory, still silently breaking things if you get it wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The gap between "works in Ring 0" and "works in Ring 3" is enormous.&lt;/strong&gt; A kernel that runs entirely in supervisor mode can be surprisingly simple. The moment you add user mode, you need: the TSS (so the CPU knows where the kernel stack is), Ring 3 GDT segments, trap gates for syscalls, a mechanism to build fake interrupt frames for the initial &lt;code&gt;iret&lt;/code&gt; into user mode, and careful validation of every pointer that crosses the kernel boundary. Each of these is individually straightforward. Getting them all correct simultaneously is where the real difficulty lies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preemptive scheduling is simpler than it sounds.&lt;/strong&gt; The concept — save state, pick next process, restore state — translates almost directly into code. The context switch is twelve instructions of assembly. The scheduler is a for loop. What makes it tricky is the interaction with everything else: the TSS must be updated, the interrupt must send EOI before switching, the process's kernel stack must be set up so that restoring registers and returning lands in the right place. The scheduler itself is trivial. The invariants it depends on are not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Writing a network stack is an exercise in byte ordering.&lt;/strong&gt; Ethernet is big-endian. x86 is little-endian. IP addresses, port numbers, checksums, packet lengths — every multi-byte field requires explicit conversion. Miss one &lt;code&gt;htons()&lt;/code&gt; and your packets are valid-looking garbage. The RTL8139 driver, the ARP implementation, the IP checksum — each is maybe fifty lines. The debugging when a byte is swapped is hours.&lt;/p&gt;
&lt;h3&gt;The Numbers&lt;/h3&gt;
&lt;p&gt;JokelaOS in its current form:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;th&gt;Approximate LOC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boot (ASM)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;~120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kernel core&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;~1,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drivers&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;~250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network stack&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;~450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~2,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two thousand lines for a kernel that boots, manages memory with paging, runs preemptive multitasking with Ring 3 isolation, handles interrupts, implements syscalls, has a working network stack, and provides an interactive shell. No line is borrowed from another project. Every byte is accounted for.&lt;/p&gt;
&lt;p&gt;The entire thing builds in under a second and the binary is around 40 KB. &lt;code&gt;make run&lt;/code&gt; goes from source to a running kernel in QEMU in about two seconds. This fast iteration cycle is what made the project possible — every subsystem was tested immediately after being written, and bugs were caught before they could compound.&lt;/p&gt;
&lt;h3&gt;What's Next&lt;/h3&gt;
&lt;p&gt;JokelaOS is a foundation, not a finished product. The obvious next steps are a proper virtual memory manager (per-process page directories instead of a shared identity map), a real file system (even a simple FAT12 (File Allocation Table, 12-bit) would be a significant step up from ramfs), and ELF binary loading. Beyond that: a disk driver would unlock persistence, TCP would make the network stack actually useful, and a proper &lt;code&gt;fork()&lt;/code&gt;/&lt;code&gt;exec()&lt;/code&gt; would make the process model complete.&lt;/p&gt;
&lt;p&gt;But the point of JokelaOS was never to build a production operating system. The point was to understand what an operating system actually does — not in the abstract, not from a textbook diagram, but in the specific, concrete sense of "these bytes go into these ports in this order and then the hardware does this thing." Every subsystem in JokelaOS exists because I wanted to understand it, and the only way to truly understand a piece of systems software is to write it yourself.&lt;/p&gt;
&lt;p&gt;The source code is on &lt;a href="https://baud.rs/B9FPjG"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description><category>assembly</category><category>bare metal</category><category>c</category><category>kernel</category><category>multitasking</category><category>networking</category><category>osdev</category><category>paging</category><category>qemu</category><category>systems programming</category><category>x86</category><guid>https://tinycomputers.io/posts/jokelaos-bare-metal-x86-kernel.html</guid><pubDate>Tue, 10 Mar 2026 15:00:00 GMT</pubDate></item><item><title>Rue: Steve Klabnik's AI-Assisted Experiment in Memory Safety Without the Pain</title><link>https://tinycomputers.io/posts/rue-programming-language-review.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/rue-programming-language-review_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;30 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;The Pitch&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/rue-programming-language/rue-lang-dev-homepage.png" alt="The rue-lang.dev homepage showing the tagline 'Exploring memory safety that's easier to use' with feature cards for Early Stage, Familiar Syntax, and Native Compilation" style="float: right; max-width: 50%; margin: 0 0 1em 1.5em; border-radius: 4px;"&gt;&lt;/p&gt;
&lt;p&gt;Every few years, a new programming language appears that promises to fix what its predecessors got wrong. Most quietly vanish. A handful become infrastructure. What makes &lt;a href="https://baud.rs/rue-lang"&gt;Rue&lt;/a&gt; interesting is not that it promises to replace Rust—its creator explicitly says it won't—but that the person making it is one of the most qualified people alive to attempt the experiment.&lt;/p&gt;
&lt;p&gt;Steve Klabnik spent thirteen years in the Rust ecosystem. He co-authored &lt;a href="https://baud.rs/rust-book-nostarch"&gt;&lt;em&gt;The Rust Programming Language&lt;/em&gt;&lt;/a&gt;, the official book that has introduced more people to Rust than any other resource. He served on the Rust core team, led its documentation team, and worked at Mozilla during Rust's formative years. He later joined Oxide Computer Company, where he contributed to a scratch-built operating system written in Rust. Before all of that, he was a prolific contributor to Ruby on Rails and authored &lt;a href="https://baud.rs/rust-ruby-learn"&gt;&lt;em&gt;Rust for Rubyists&lt;/em&gt;&lt;/a&gt;, one of the earliest attempts to bridge the Ruby and Rust communities.&lt;/p&gt;
&lt;p&gt;If anyone has earned the right to look at Rust and say "I think we can do this differently," it's Klabnik. And that's exactly what Rue is: a research project asking whether memory safety without garbage collection can be achieved with less cognitive overhead than Rust demands.&lt;/p&gt;
&lt;p&gt;But Rue is also something else entirely—an experiment in whether a single person, assisted by an AI, can build a programming language from scratch without funding, without a team, and without a multi-year timeline. The compiler is written in Rust, designed by Klabnik, and implemented primarily by Claude, Anthropic's AI assistant. The project went from nothing to a working compiler in roughly two weeks, producing approximately 100,000 lines of Rust code across 700+ commits.&lt;/p&gt;
&lt;h3&gt;The Person Behind It&lt;/h3&gt;
&lt;p&gt;Understanding Rue requires understanding Klabnik's trajectory through programming language communities. He entered professional programming through Ruby, becoming one of the most prolific open-source contributors to the Rails ecosystem in the early 2010s. His final commits to Rails landed in late 2013, around the time he discovered Rust 0.5 during a Christmas visit to his parents' house in rural Pennsylvania.&lt;/p&gt;
&lt;p&gt;What followed was a thirteen-year involvement with Rust that few can match. Beyond the official book (now in its second edition, co-authored with Carol Nichols and published by No Starch Press), Klabnik shaped how the Rust community communicates, documents, and teaches. His work on Rust's documentation established patterns that other language communities later adopted. At Mozilla, he helped shepherd Rust through its 1.0 release. At Oxide, he worked on systems software at the lowest levels the language supports.&lt;/p&gt;
&lt;p&gt;Klabnik describes himself as having been an AI skeptic until 2025, when he found that large language models had crossed a threshold of genuine usefulness for programming. The shift was dramatic enough that he now writes most of his code with AI assistance. Rue is the product of that conversion—not just a language experiment but a methodology experiment, testing what happens when a deeply experienced language designer directs an AI to implement his vision.&lt;/p&gt;
&lt;p&gt;The name follows a deliberate pattern from his career: Ruby, Rust, Rue. He notes three associations—"rue the day" (the negative connotation, a nod to the skepticism any new language faces), the rue plant (paralleling Rust's fungal connotation), and brevity.&lt;/p&gt;
&lt;h3&gt;What Rue Is Today&lt;/h3&gt;
&lt;p&gt;Let's be direct about the current state: Rue is a version 0.1.0 research project. The website prominently warns that it is "not ready for real use" and to "expect bugs, missing features, and breaking changes." Klabnik himself has described the language as "still very janky" and cautions against reading too deeply into current implementation details. The &lt;a href="https://baud.rs/rue-readme"&gt;GitHub README&lt;/a&gt; puts it plainly: "Not everything in here is good, or accurate, or anything: I'm just messing around."&lt;/p&gt;
&lt;p&gt;With that caveat firmly in place, here's what exists.&lt;/p&gt;
&lt;p&gt;Rue compiles to native machine code targeting x86-64 and ARM64. There is no virtual machine, no interpreter, and no garbage collector. The compiler is written in Rust (95.8% of the repository) and produces binaries directly. It builds using &lt;a href="https://baud.rs/buck2-build"&gt;Buck2&lt;/a&gt;, Meta's build system, and the project includes a specification, a ten-chapter tutorial, a blog, and a benchmark dashboard that tracks compilation time, memory usage, and binary size across Linux and macOS.&lt;/p&gt;
&lt;p&gt;The language itself is statically typed with type inference. Variables are declared with &lt;code&gt;let&lt;/code&gt; (immutable by default) or &lt;code&gt;let mut&lt;/code&gt; (mutable), following Rust's convention. The type system includes signed and unsigned integers (&lt;code&gt;i8&lt;/code&gt; through &lt;code&gt;i64&lt;/code&gt;, &lt;code&gt;u8&lt;/code&gt; through &lt;code&gt;u64&lt;/code&gt;), booleans, fixed-size arrays, structs, and enums. There are no strings. There is no standard library. There are no traits, no closures, no iterators, no modules, no error handling beyond panics, and no heap allocation. Generics exist behind a &lt;code&gt;--preview comptime&lt;/code&gt; flag—more on that below—but they are not yet part of the stable language.&lt;/p&gt;
&lt;p&gt;Read that list of absences again. It is long. And it is honest.&lt;/p&gt;
&lt;h3&gt;The Type System and Memory Model&lt;/h3&gt;
&lt;p&gt;Rue's central technical bet is that affine types with mutable value semantics can deliver memory safety more intuitively than Rust's borrow checker and lifetime annotations.&lt;/p&gt;
&lt;p&gt;In practice, this means values in Rue are moved by default. When you assign a struct to a new variable or pass it to a function, the original becomes invalid—the compiler enforces single ownership at the type level. This is Rust's move semantics without the escape hatches that references and borrowing provide. If you want a type to be copied instead of moved, you annotate the struct definition with &lt;code&gt;@copy&lt;/code&gt;, which enables value duplication.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/rue-programming-language/rue-borrow-inout.png" alt="Rue's Borrow and Inout tutorial page showing the inout keyword at the call site with comparisons to Go and Python where mutation is invisible" style="float: right; max-width: 50%; margin: 0 0 1em 1.5em; border-radius: 4px;"&gt;&lt;/p&gt;
&lt;p&gt;Where Rust provides shared references (&lt;code&gt;&amp;amp;T&lt;/code&gt;) and mutable references (&lt;code&gt;&amp;amp;mut T&lt;/code&gt;) governed by the borrow checker's aliasing rules, Rue provides two simpler mechanisms: &lt;code&gt;borrow&lt;/code&gt; and &lt;code&gt;inout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;borrow&lt;/code&gt; parameter grants read-only access to a value without copying it. An &lt;code&gt;inout&lt;/code&gt; parameter grants temporary mutable access—the function can modify the value, and changes persist after the function returns. Critically, both are marked at the call site, not just in the function signature. When reading Rue code, you can immediately see which arguments a function will modify:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;sort(inout values, borrow config);
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The tutorial makes the design motivation explicit: "When you read code, you want to understand what it does without tracing through every function." In Go or Python, a function receiving a slice or object might mutate it invisibly. In Rue, mutation is always syntactically visible where the function is called.&lt;/p&gt;
&lt;p&gt;This is genuinely appealing. One of Rust's persistent pain points is that understanding what a function does to its arguments requires reading the signature carefully—and even then, interior mutability patterns like &lt;code&gt;RefCell&lt;/code&gt; can make the signature misleading. Rue's approach trades expressiveness for legibility.&lt;/p&gt;
&lt;p&gt;The trade-off, however, is severe. Without general references, you cannot build self-referential data structures. Linked lists, trees, and graphs—the bread and butter of systems programming data structures—have no obvious implementation path in current Rue. The Hacker News discussion around the language's announcement surfaced this concern immediately: without references that can be stored in data structures, you cannot implement iterators that borrow from containers. This is not a missing feature that will be added later; it is a fundamental consequence of the design choice.&lt;/p&gt;
&lt;p&gt;Klabnik has acknowledged this directly: "There is going to inherently be some expressiveness loss. There is no silver bullet." The question Rue poses is whether the expressiveness that remains is sufficient for a useful class of programs. The answer today is: we don't know yet.&lt;/p&gt;
&lt;h3&gt;What the Tutorial Reveals&lt;/h3&gt;
&lt;p&gt;The ten-chapter tutorial walks from installation through a capstone quicksort implementation. It covers variables, types, functions, control flow, arrays, structs, enums, and the borrow/inout system. The final chapter implements partitioning and recursive sorting, demonstrating how the language's features compose.&lt;/p&gt;
&lt;p&gt;Working through it reveals a language that feels like a simplified Rust with the hard parts surgically removed. Pattern matching on enums is exhaustive, as in Rust. Structs have named fields and move semantics. Arrays are fixed-size with runtime bounds checking that panics on out-of-bounds access. Integers overflow-check by default.&lt;/p&gt;
&lt;p&gt;The syntax is deliberately familiar to anyone who has written Rust, Go, or C. Function signatures look like Rust without lifetimes:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nt"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="cp"&gt;]&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;low&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;high&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;u64&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Control flow uses &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; and &lt;code&gt;while&lt;/code&gt; without parentheses around conditions. There is no &lt;code&gt;for&lt;/code&gt; loop—iteration requires manual index management with &lt;code&gt;while&lt;/code&gt;, which feels like a notable omission even for an early-stage language.&lt;/p&gt;
&lt;p&gt;What's conspicuously absent from the tutorial is any program that does something useful beyond computation. There is no file I/O, no string handling, no network access, no memory allocation. The only output mechanism is &lt;code&gt;@dbg()&lt;/code&gt;, a built-in debug print. The actual &lt;code&gt;hello.rue&lt;/code&gt; in the examples directory is revealing: it is &lt;code&gt;fn main() -&amp;gt; i32 { 42 }&lt;/code&gt;. There are no strings, so there is no "Hello, World." The fizzbuzz example is equally telling—it returns integers (1 for Fizz, 2 for Buzz, 3 for FizzBuzz) because it cannot print the words. The specification explicitly excludes coverage of a standard library "when one exists."&lt;/p&gt;
&lt;h3&gt;The AI Development Story&lt;/h3&gt;
&lt;p&gt;The most widely discussed aspect of Rue is not its type system but how it was built. Klabnik's blog posts describe a process where he provided architectural direction and design decisions while Claude wrote the vast majority of the implementation code. The project's blog posts are co-credited to both Klabnik and Claude, with some posts authored solely by the AI.&lt;/p&gt;
&lt;p&gt;The timeline is striking. The first commit landed on December 15, 2025. By December 22—one week later—the compiler could handle basic types, structs, control flow, and had accumulated 130 commits. By early January 2026, the project had 777 specification tests across two platforms and the compiler had grown to handle enums, pattern matching, arrays, and the borrow/inout system.&lt;/p&gt;
&lt;p&gt;The repository now contains over 700 commits from 4 contributors, with 1,100+ GitHub stars. For a personal hobby project by a well-known developer, this represents meaningful interest—though not the kind of momentum that suggests organic community adoption.&lt;/p&gt;
&lt;p&gt;This development model raises legitimate questions. When Claude writes the compiler and Claude writes the blog posts, what does it mean for a human to have "designed" the language? Klabnik's answer is that he makes all architectural and design decisions—what features to include, how the type system works, what trade-offs to accept—while the AI handles implementation. He compares it to an architect and a construction crew: the architect doesn't lay bricks, but the building reflects the architect's vision.&lt;/p&gt;
&lt;p&gt;The analogy is imperfect. A construction crew doesn't suggest design changes mid-build based on patterns learned from every other building ever constructed. But the broader point—that directing implementation is itself a form of authorship—is reasonable, and Klabnik's thirteen years of language implementation experience give him credibility that a less experienced designer directing an AI would lack.&lt;/p&gt;
&lt;h3&gt;What the Source Code Reveals&lt;/h3&gt;
&lt;p&gt;The documentation and tutorial tell one story. The &lt;a href="https://baud.rs/eWuAO3"&gt;GitHub repository&lt;/a&gt; tells a more nuanced one. Digging into the examples directory and compiler crates reveals features in various stages of development that the official tutorial has not yet caught up with.&lt;/p&gt;
&lt;p&gt;Most notably, generics exist as a preview feature. The &lt;code&gt;examples/generics.rue&lt;/code&gt; file demonstrates Zig-style comptime type parameters:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kd"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comptime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;comptime T: type&lt;/code&gt; syntax tells the compiler to monomorphize—creating specialized versions (&lt;code&gt;max__i32&lt;/code&gt;, &lt;code&gt;max__bool&lt;/code&gt;, etc.) for each concrete type used at call sites. This is a meaningful step beyond what the tutorial covers, and it follows the Zig model rather than Rust's trait-bounded generics. Whether this approach will scale to real-world code—and whether it can support the kind of type-level abstraction that traits and interfaces provide—remains to be seen. You must compile with &lt;code&gt;rue --preview comptime&lt;/code&gt; to access this feature, signaling that it is experimental even by Rue's standards.&lt;/p&gt;
&lt;p&gt;The compiler architecture itself is surprisingly mature for a project of this age. The repository contains 18 separate crates: a lexer, parser, intermediate representation (&lt;code&gt;rue-rir&lt;/code&gt;), an abstract intermediate representation (&lt;code&gt;rue-air&lt;/code&gt;), control flow graph analysis (&lt;code&gt;rue-cfg&lt;/code&gt;), code generation, linking, a fuzzer, a spec test runner, and a VS Code extension. This is not a toy single-file compiler—it is a modular pipeline that reflects real compiler engineering, even if the language it compiles remains minimal.&lt;/p&gt;
&lt;p&gt;Beyond generics, the gaps are still significant. There are no traits or interfaces, so there is no polymorphism beyond what enums and comptime monomorphization provide. There are no closures or first-class functions. There is no error handling mechanism—functions can panic, but there is no &lt;code&gt;Result&lt;/code&gt; type or equivalent. There are no modules or visibility controls. There are no methods on types. There is no string type.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;unchecked&lt;/code&gt; keyword provides an escape hatch similar to Rust's &lt;code&gt;unsafe&lt;/code&gt;, allowing low-level memory operations while keeping such code "visibly separate from normal safe code." The specification includes chapters on unchecked code syntax and unchecked intrinsics, suggesting that Klabnik recognizes the eventual need for the kind of low-level access that Rust provides through its unsafe system.&lt;/p&gt;
&lt;p&gt;The performance page tracks compilation metrics—time, memory, binary size—across x86-64 and ARM64 on both Linux and macOS, but provides no runtime benchmarks comparing Rue's generated code to C, Rust, or Go. This is understandable for a research project, but it means we cannot evaluate whether Rue's native compilation delivers competitive performance.&lt;/p&gt;
&lt;h3&gt;The Fundamental Question&lt;/h3&gt;
&lt;p&gt;Every new systems language must answer the same question: what programs can I write in this language that I couldn't write (or couldn't write as well) in an existing one?&lt;/p&gt;
&lt;p&gt;For Rust, the answer was clear from early on: memory-safe systems programs without garbage collection pauses. For Go, it was concurrent network services with fast compilation. For Zig, it was a C replacement with better defaults and comptime.&lt;/p&gt;
&lt;p&gt;Rue's answer, as it stands today, is: nothing. Not yet. You cannot write a program in Rue that you couldn't write more easily in any mainstream language, because Rue cannot perform I/O, allocate memory, manipulate strings, or interact with the operating system.&lt;/p&gt;
&lt;p&gt;This is not a criticism so much as a statement of development stage. The interesting question is what Rue's answer &lt;em&gt;could&lt;/em&gt; become if development continues. The borrow/inout model is genuinely simpler than Rust's borrow checker for the cases it handles. Generics are already emerging through the comptime preview. If Rue can stabilize them, grow a heap allocator, a string type, and enough standard library to write real programs—without reintroducing the complexity it was designed to avoid—it could serve programmers who find Rust's learning curve prohibitive but need more safety than C or Go provide.&lt;/p&gt;
&lt;p&gt;That is a big "if." History is littered with languages that simplified an existing language's hard parts and then discovered that the hard parts existed for good reasons. Rust's borrow checker is complex because the problems it solves are complex. Removing it and replacing it with a simpler system necessarily means either accepting less expressiveness or finding a genuinely novel solution that the Rust team somehow missed in over a decade of research.&lt;/p&gt;
&lt;p&gt;Klabnik is explicit that he doesn't claim to have found such a solution. Rue is a research project exploring a design space, not a product claiming to have mapped it.&lt;/p&gt;
&lt;h3&gt;Who Should Pay Attention&lt;/h3&gt;
&lt;p&gt;If you're looking for a language to write software in today, Rue is not it. The project's own documentation says so clearly and repeatedly.&lt;/p&gt;
&lt;p&gt;If you're interested in programming language design, Rue is worth following for two reasons. First, the borrow/inout model is a clean articulation of an alternative to Rust's reference system, and watching it encounter real-world requirements will be educational regardless of whether it succeeds. Second, the AI-assisted development methodology is itself a data point in the ongoing question of what role AI can play in building complex software systems.&lt;/p&gt;
&lt;p&gt;If you're a Rust programmer who has ever thought "there must be a simpler way to express this," Rue represents one concrete exploration of what "simpler" might look like—and what it costs. The language makes the trade-offs visible in a way that abstract arguments about borrow checker complexity do not.&lt;/p&gt;
&lt;p&gt;And if you're Steve Klabnik, messing around with a hobby project after thirteen years of working on someone else's language, Rue looks like exactly the kind of thing a deeply experienced language person should be doing with their evenings and weekends.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Rue is best understood as three things simultaneously: a technical experiment in memory safety ergonomics, a methodological experiment in AI-assisted language development, and a personal project by someone with unusually deep expertise in the problem space. As a technical experiment, it has articulated a clean alternative to Rust's borrow checker that is worth studying even if it proves too restrictive for general use. As a methodological experiment, the speed of development—700+ commits and a working multi-platform compiler in weeks—is genuinely remarkable, though the long-term maintainability of AI-generated compiler code remains unproven. As a personal project, it is honest about its limitations in a way that many language announcements are not.&lt;/p&gt;
&lt;p&gt;The language cannot do anything useful yet. It may never be able to. But the questions it asks—can memory safety be simpler? can one person with an AI build a compiler? what happens when you remove the borrow checker and try something else?—are worth asking. And the person asking them has spent over a decade earning the credibility to make the attempt interesting rather than naive.&lt;/p&gt;
&lt;p&gt;Watch the &lt;a href="https://baud.rs/eWuAO3"&gt;GitHub repository&lt;/a&gt;. Read the &lt;a href="https://baud.rs/8joWb8"&gt;specification&lt;/a&gt;. Try the &lt;a href="https://baud.rs/bUJS2D"&gt;tutorial&lt;/a&gt; if you're curious about what a post-Rust systems language might feel like. Just don't write anything important in it yet.&lt;/p&gt;</description><category>ai-assisted development</category><category>claude</category><category>compilers</category><category>language design</category><category>memory safety</category><category>programming languages</category><category>rue</category><category>rust</category><category>steve klabnik</category><category>systems programming</category><guid>https://tinycomputers.io/posts/rue-programming-language-review.html</guid><pubDate>Fri, 20 Feb 2026 12:00:00 GMT</pubDate></item><item><title>A Critical Analysis of "Tiny C Projects" by Dan Gookin</title><link>https://tinycomputers.io/posts/review-tiny-c-projects.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/review-tiny-c-projects_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;16 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Introduction &amp;amp; Book Overview&lt;/h3&gt;
&lt;p&gt;The era in which commentators delight in proclaiming C's death, the language remains one of the most in-demand programming languages, powering everything from operating systems as well as from embedded devices. Bridging this paradox is the book &lt;a href="https://baud.rs/vBKWgJ"&gt;&lt;em&gt;Tiny C Projects&lt;/em&gt;&lt;/a&gt; by Dan Gookis, which commemorates the command-line heritage of C in promising to refine the skill of programmers through small utility-based projects.&lt;/p&gt;
&lt;p&gt;Gookin rises to this challenge with some impressive credentials. The man who created the classic &lt;em&gt;DOS For Dummies&lt;/em&gt; and over 170 technical books came up with the idea of teaching technology through humor and accessibility. His new book expands this concept through C programming, with 15 chapters of increasingly complex projects that create practical command-line tools.&lt;/p&gt;
&lt;p&gt;The book's underlying argument is just wonderfully straightforward: learn through the development of small, practical programs that provide instant feedback. Starting from mundane greeting programs and culminating in game AI implementation, Gookin aims to take the reader through the stepwise acquisition of skill. Each project is presented as adozen-line demonstration and evolves through a fully-featured utility, but always "tiny" in nature that the reader can take in at one sitting.&lt;/p&gt;
&lt;p&gt;Nevertheless, this publicly accessible premise conceals a more complicated reality. Though &lt;em&gt;Tiny C Projects&lt;/em&gt; is exceptional in educating intermediate programmers in practical skill through its incremental development methodology, its limited focus on text-mode utility programs along with high prerequisite requirements may reduce its accessibility for the general programming community that is looking at contemporary C development methodologies.&lt;/p&gt;
&lt;h3&gt;Pedagogical Approach &amp;amp; Philosophy&lt;/h3&gt;
&lt;p&gt;Gookin's "start small and grow" strategy is an intentional rejection of the pedagogy of traditional programming texts. While classic texts offer blocklike programs that run from hundreds to over a thousand lines, &lt;em&gt;Tiny C Projects&lt;/em&gt; starts with programs as short as ten lines, growing the code incrementally as the concept matures. The strategy, as Gookin remarks, offers the "instant feedback" that makes the study of programs so delightful, rather than overwhelming.&lt;/p&gt;
&lt;p&gt;Practical use orientation sets the book apart from pedagogical texts with vacuous exercises. Instead of calculating Fibonacci sequences or using hypothetical data structures, the reader constructs useful tools: file finders, hex dumpers, password generators, and calendar programs. These are no pedagogical toys but programs the reader may indeed use in the everyday practice. The command-line integration instruction is the way to learn correct Unix philosophy—a small number of tools that all perform just one thing well and that blend nicely.&lt;/p&gt;
&lt;p&gt;This pedagogy is particularly effective in retention of skill. By systematic use in numerous scenarios—file I/O is covered in the hex dumper, directory tree, and file finder components—the reader cements retention through varied application rather than rote practice. The natural progression from simple string manipulation through complex recursive directory traversals feels organic rather than disorienting.&lt;/p&gt;
&lt;p&gt;However, this strategy is fraught with built-in shortcomings. The text-mode limitation, in keeping the learning curve low, discounts the fact that the bulk of current C development is graphical interface, network, or embedded system development. The book's consistent refusal to use outside libraries, in guaranteeing portability, loses the chance to instruct practical development techniques in the real world in which code reuse is frequently more beneficial than wheel reinvention.&lt;/p&gt;
&lt;p&gt;The "For Dummies" credentials of the book shine through in lucid, occasionally witty prose that is never condescending. Technical information is accurately outlined but with general accessibility so that esoteric topics like Unicode management or date maths are viable subjects without sacrificing rigour.&lt;/p&gt;
&lt;h3&gt;Content Analysis &amp;amp; Technical Coverage&lt;/h3&gt;
&lt;p&gt;The book's 15-chapter structure unfolds with skill progression carefully considered. The initial chapters (chapters 1-6) build fundamentals with configuration initialization, fundamental I/O, string manipulation, and trivial algorithms such as Caesar ciphers. They nicely invoke core topics--command-line argumentation, file I/O, random number generation--while in the context of something immediately useful instead of as an academic lesson.&lt;/p&gt;
&lt;p&gt;Part two (chapters 7-11) delves further into system programming material. The string utilities chapter puts together a whole library, teaches modular programming, and even deals with object orientation in C with the use of function pointers in structures. The Unicode chapter deals with wide character programming in remarkable detail, often missing in C books. The filesystem chapters on hex dumping, directory trees, and file finding teach recursion, binary data manipulations, and pattern matching—a fundamental skill in system programming.&lt;/p&gt;
&lt;p&gt;Advanced chapters (12-15) provide algorithmic complexity with practical applications. The holiday detector includes date arithmetic with the notorious Easter algorithm calculation. The calendar generator includes terminal color management and prudent formatting. The lottery simulator considers probability and combinatorics, and the tic-tac-toe game uses minimax-type AI decision-making.&lt;/p&gt;
&lt;p&gt;Code quality from the beginning is always good. Examples adhere to C conventions as learned in the classroom, with descriptive variable names and well-structured function decomposition. Error checking, often neglected in textbooks, receives proper discussion—though not thorough. Progression from the naive solution through optimizations (most prominently in the password generator and file find sections) mirrors the iterative development in the real world.&lt;/p&gt;
&lt;p&gt;Technical holes, however, become apparent upon second glance. The book deliberately eschews modern C standards (C11/C17/C23) and loses opportunities to teach modern best practices. Threading and concurrency are sidestepped although they are important in systems programming today. Networking, frequently C's killer app in the IoT and embedded systems decades, is gone. Advanced data structures are sparse, so the reader is poorly qualified to meet the real world.&lt;/p&gt;
&lt;h3&gt;Target Audience &amp;amp; Accessibility&lt;/h3&gt;
&lt;p&gt;The title creates an immediate expectation gap. "Tiny" creates the expectation of novice-friendliness, byte-sized newbee learning. However, Gookin specifically states people need "good knowledge of C"—experience is not called out, but certainly more than novice level. Such prerequisite is understanding of pointers, memory management, structures, compilation procedures that would discourage true beginners.&lt;/p&gt;
&lt;p&gt;The book's potential reader is thus the one who's had C-theory but is in pursuit of practical application—perhaps the computer science undergraduate who's taken a C course but hasn't built much themselves, or the programmer in another language who wants to discover C's systems-programming possibilities. Programmer-self-taught persons who are comfortable with the command-line modes will use the book the most.&lt;/p&gt;
&lt;p&gt;Platform assumptions also restrict the audience. While Gookin contends cross-platform compatibility under Linux, Windows (with WSL), and macOS, the illustrations prominently favor Unix-like systems. Windows programmers who don't have WSL experience will have trouble with shell script illustrations as well as terminal-related functionalities. The command-line focus, while pedagogically appropriate, makes assumptions regarding experience with terminal navigation, file management, and shell disciplines that are unfamiliar to GUI-based programmers.
 The book does a great job with its target audience: intermediate programmers who desire practical experience with projects. These are the readers who will appreciate the progression from simplest through more complex, practicality of utilities over exercises, and gaining insight through implementation.&lt;/p&gt;
&lt;p&gt;Nevertheless, some will be dissatisfied with the book. Newcomers will be inundated with assumed experience. Seasoned programmers who long for in-depth examination of modern C capabilities or high-level system programs will be disappointed with the contents. Web professionals or data wran glers who long to gain insight into C's role in their universe will find little that is useful.&lt;/p&gt;
&lt;h3&gt;Strengths &amp;amp; Unique Value&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Tiny C Projects&lt;/em&gt; is successful in the following fundamental areas, and the book warrants space on programmers' bookshelves. Its greatest strength is the portfolio of working projects. Unlike books that provoke the question "when would I ever use this?", each of the projects delivers some possible usable output. The hex dumper is on par with commercial offerings, the file finder does real glob pattern matching, and the password generator produces cryptographically reasonable passwords.&lt;/p&gt;
&lt;p&gt;The no-dependency policy of the book, while at times limiting, provides unique pedagogical value. The practitioner internalizes the application of functionality from scratch with the subtlety hidden in library calls. Such detailed understanding is priceless when debugging or optimizing production code. Portability because of the lack of external dependencies means the compilation and run of every program on any standard system with C compiler support—a no dependency hell, no version conflict.&lt;/p&gt;
&lt;p&gt;Gookin's pedagogical experience beams through. Difficult material is explained clearly, but not oversimplied. The algorithm for the moon phase, for example, is supplemented with sufficient astronomical context so that the reader knows what he is calculating but doesn't become an astronomy text. Humor breaks up possible dry material without distracting from technical information. Cues like "the cool kids" speaking in hip languages or "a tax levied on people bad at math" in describing lots add warmth without losing professionalism.&lt;/p&gt;
&lt;p&gt;The progressive complexity model owes special credit. The changes in each chapter from being simple to being sophisticated mimic genuine development processes. The reader doesn't only learn what to code but how code can be developed—from being simple, with the incorporation of features, to being nicely refactored. The meta-lesson in software development methodology is as valuable as the techniques themselves.&lt;/p&gt;
&lt;p&gt;The book also tacitly teaches professional practices. Version control is touched upon with mentions but no in-depth discussion. Code organization into headers and implementation files is natural. The string library chapter demonstrates proper API design. These lessons, instilled in the act of projects being developed rather than taught, stick with the reader.&lt;/p&gt;
&lt;h3&gt;Limitations &amp;amp; Missed Opportunities&lt;/h3&gt;
&lt;p&gt;Despite its strengths, &lt;em&gt;Tiny C Projects&lt;/em&gt; suffers from several significant limitations that prevent it from achieving greatness. The text-mode constraint, while simplifying examples, feels anachronistic in 2023. Modern C development encompasses GUIs, graphics, networking, and embedded systems—none of which appear here. Readers completing all projects still couldn't build a simple networked application or basic GUI program.&lt;/p&gt;
&lt;p&gt;The absence of up-to-date C standards is a lost opportunity of paramount importance. C11 introduced threading, atomics, and improved Unicode support. C17 and C23 improve upon this. The book, in its avoidance of the standards, imbues C as in decades past rather than contemporary best practices. A C11 threading chapter would be enormously useful in practice.&lt;/p&gt;
&lt;p&gt;Teaching holes frustrate the learning process. Debugging is marginal in discussions although vital in C development. Valgrind, GDB, and sanitizers are absent. Test methodology is given lip service but no systematic discussion—no unit testing, no test-driven development, no continuous integration. Optimizing for performance, so important in systems programming, is accorded little more than lip service. Memory management, the toughest part of C, sees no in-depth discussion.&lt;/p&gt;
&lt;p&gt;The book's positioning in the market is unclear. At $39.99, the book finds competition from free online materials, YouTube instruction, and encyclopedic works like &lt;em&gt;Modern C&lt;/em&gt; or &lt;em&gt;21st Century C&lt;/em&gt; that span more territory. The value proposition—to create practical utilities—is unlikely to be worth the money when GitHub is saturated with similar projects.&lt;/p&gt;
&lt;p&gt;Structural problems also become apparent. Chapter transitions sometimes come across as random. Why is Unicode handling followed by the hex dumper that can illustrate byte-level Unicode representation? The complexity spike of the holiday detector may deter readers. The tic-tac-toe game, though entertaining, feels out of touch with the utility focus.&lt;/p&gt;
&lt;h3&gt;Conclusion &amp;amp; Recommendations&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Tiny C Projects&lt;/em&gt; occupies a special place among C programming texts: true skill development in intermediate programmers through stepwise development of projects. At that special place, it succeeds. The projects are genuinely practical, the descriptions brief, and the sequence uniform. Gookin's experience makes the learning experience an entertaining one that avoids the academic dullness that plagues so many texts on programming.&lt;/p&gt;
&lt;p&gt;The book provides great value for its assumed reader count--intermediate C programmers who seek genuine experience, the practitioner of the transition from theory to practice, and command-line utility practitioner who wants polish--as they build a portfolio of useful tools while solidifying fundamental concepts through diversified application.&lt;/p&gt;
&lt;p&gt;Nevertheless, general audiences will have to go elsewhere. New programmers require more lenient introduction texts such as &lt;em&gt;C Programming: A Modern Approach.&lt;/em&gt; Experienced programmers in quest of modern C may find &lt;em&gt;Modern C&lt;/em&gt; or &lt;em&gt;21st Century C&lt;/em&gt; more appropriate. Systems programmers may find &lt;em&gt;The Linux Programming Interface&lt;/em&gt; or &lt;em&gt;Advanced Programming in the UNIX Environment&lt;/em&gt; more desirable.&lt;/p&gt;
&lt;p&gt;The book scores a solid 7/10 in terms of target audience but only 5/10 in terms of general C programming instruction. Its narrow focus is both the greatest advantage as well as the biggest weakness.  Future revisions may overcome present limitations with the inclusion of recent C standards, network programming assignments, chapters on debugging and testing, or optional GUI extensions. Supplements in the form of web-based video lectures along with community challenges could push the value beyond the page.  As a whole, &lt;em&gt;Tiny C Projects&lt;/em&gt; is an effective short, practical guide to building command-line programs in C. Readers who accept its limitations will find an enjoyable, pedagogical experience through stepwise program development. Those who crave through contemporary C instruction should accompany it with other texts.&lt;/p&gt;</description><category>c programming</category><category>code tutorials</category><category>command-line utilities</category><category>dan gookin</category><category>intermediate programmers</category><category>practical programming</category><category>programming education</category><category>project-based learning</category><category>systems programming</category><category>unix philosophy</category><guid>https://tinycomputers.io/posts/review-tiny-c-projects.html</guid><pubDate>Tue, 09 Sep 2025 18:50:31 GMT</pubDate></item></channel></rss>