Active open-source learning project · MIT licensed
A Unix-like 32-bit x86 operating system built from scratch in C and assembly for systems programmers and students. It boots from a real ISO and runs in your browser through v86.
Boot SimpleOS in your browser →
The emulator opens at the shell prompt. Run help, ps, ls, or a pipeline such as
echo hello | wc to exercise the same kernel and userspace built by this repository.
SimpleOS is a learning project — a from-scratch kernel that implements processes, virtual memory, syscalls, pipes, signals, a filesystem, and a shell, all in roughly 8,000 lines of code. It boots as a real ISO image via GRUB, and an optional web app can run the OS in-browser using the v86 x86 emulator.
This isn't a production operating system. It's a proof of concept and a learning tool — built to understand how operating systems actually work by writing one from the ground up.
- What's Inside
- Boot Flow
- Memory Layout
- Syscalls
- Shell
- Repository Layout
- Building
- Browser Demo
- Reading the Code
- Verification
- Contributing
- Raintree open-source system
- License
| Subsystem | Key files | What it does |
|---|---|---|
| Boot | boot.s, grub.cfg, linker.ld | Multiboot2 entry, GDT, stack setup, jumps to kernel_main |
| Kernel core | kernel.c | Initializes every subsystem, creates test processes, starts scheduler |
| Processes | process.c, scheduler.c | Round-robin preemptive scheduling (10-tick quantum), kernel threads and user processes, up to 64 |
| Context switching | context_switch.s | Saves/restores register state, interrupt-return trampoline for user mode |
| Memory | pmm.c, vmm.c, kmalloc.c | Bitmap physical allocator, two-level paging with copy-on-write, free-list heap with block coalescing |
| Syscalls | syscall.c | 23 syscalls via int 0x80 — fork, execve, wait, pipe, dup2, kill, signal, chdir, and more |
| IPC | pipe.c, signal.c | 4 KB blocking circular pipes, Unix-style signals (SIGINT, SIGKILL, SIGSTOP, etc.) |
| Filesystem | fs.c | In-memory ramfs — 64 nodes, 512KB of 512-byte blocks, pre-populated with /bin/* |
| Drivers | terminal.c, keyboard.c, timer.c, vt.c | VGA text mode, PS/2 keyboard with Ctrl+C/Z, PIT timer, virtual terminal switching (Alt+F1–F4) |
| ELF loader | elf.c | Loads 32-bit ELF binaries into user address space |
| User mode | usermode.c, tss.c | Ring 0 → Ring 3 via IRET, TSS for kernel stack on interrupts |
| Userspace | shell.c, grep.c, wc.c, hello.c | Freestanding programs built as ELF, converted to C byte arrays, embedded in the kernel image |
| Web demo | Emulator.tsx, CRTFrame.tsx | v86 emulator with asset preflight, CRT-styled terminal with animated ASCII background |
GRUB (Multiboot2)
→ _start src/arch/i386/boot.s load GDT, set up stack
→ kernel_main src/kernel/kernel.c init all subsystems
→ scheduler_enable src/kernel/scheduler.c start round-robin loop
→ idle process PID 0 runs when nothing else is ready
The kernel is linked at 1MB (linker.ld). The heap lives at 2–3.5MB. User processes get virtual addresses starting at 128MB, with stacks below 3GB.
0x00100000 (1MB) Kernel code (.multiboot, .text, .data, .bss)
0x00200000 (2MB) Kernel heap start
0x00380000 (3.5MB) Kernel heap end
0x00400000 (4MB) Physical memory allocator starts here
0x04000000 (64MB) End of kernel identity-mapped window
0x08000000 (128MB) User virtual address space begins
0xBFFFF000 (~3GB) User stack top (grows down)
0xC0000000 (3GB) Kernel boundary
All syscalls use int 0x80 with arguments in eax (number), ebx, ecx, edx.
Userspace wrappers live in ulib.h.
| # | Name | Description |
|---|---|---|
| 1 | exit |
Terminate process |
| 2 | write |
Write to fd (terminal, file, or pipe) |
| 3 | read |
Read from fd |
| 4 | getpid |
Get current PID |
| 5 | sleep |
Sleep N milliseconds |
| 6 | sbrk |
Grow user heap |
| 7 | fork |
Fork process (COW page tables) |
| 8 | wait |
Wait for child, reap zombie |
| 9 | execve |
Load and run ELF binary |
| 10 | ps |
List all processes |
| 11 | open |
Open file |
| 12 | close |
Close fd |
| 13 | stat |
File metadata |
| 14 | mkdir |
Create directory |
| 15 | readdir |
Read directory entries |
| 16 | kill |
Send signal to process |
| 17 | pipe |
Create pipe pair |
| 18 | dup2 |
Duplicate fd |
| 19 | signal |
Install a user-mode signal handler |
| 20 | sigreturn |
Return from a signal handler (used by the trampoline) |
| 21 | setfg |
Set the terminal foreground job (Ctrl+C/Z target) |
| 22 | chdir |
Change working directory |
| 23 | getcwd |
Get working directory |
The shell supports:
- Built-ins:
help,ps,echo,cd,pwd,ls,cat,clear,history,jobs,fg,bg,kill,exit - External programs:
/bin/hello,/bin/grep,/bin/wc(via fork + execve) - Pipes:
echo hello | wc— multi-stage too (cmd1 | cmd2 | cmd3) - Redirection:
cmd > file,cmd >> file(append),cmd < file - Background jobs:
cmd &, thenjobs,fg,bg - Job control: Ctrl+C (SIGINT), Ctrl+Z (SIGTSTP) routed to the foreground job
- History: up/down arrows (10 entries)
src/
arch/i386/ boot.s, asm_functions.s, context_switch.s, tss.c, usermode.c
boot/ exceptions.c (ISR/IDT setup)
kernel/ kernel.c, process.c, scheduler.c, syscall.c, panic.c
mm/ kmalloc.c, pmm.c, vmm.c
drivers/ terminal.c, keyboard.c, timer.c, ports.c, vt.c, serial.c
fs/ fs.c
ipc/ pipe.c, signal.c
lib/ elf.c, string.c
include/ headers mirroring src/ layout
userspace/ shell.c, grep.c, wc.c, hello.c, selftest.c, ulib.h, Makefile
boot/grub/ grub.cfg
web/ Next.js app (Emulator.tsx, CRTFrame.tsx, page.tsx)
scripts/ dev-shell.sh, setup-toolchain.sh, smoke-test.sh, selftest.sh
.devcontainer/ Dockerfile + devcontainer.json
The kernel targets 32-bit freestanding x86. The Makefile cross-compiles
with i686-elf-gcc and produces simpleos.iso via grub-mkrescue.
Userspace programs are compiled separately (userspace/Makefile),
then converted to C byte arrays with xxd -i and #included into the kernel's
filesystem init.
Open the repo in VS Code, Cursor, or Zed — the devcontainer config has everything pre-installed.
make
cd web && npm install && npm run devSee scripts/setup-toolchain.sh for install instructions. You need:
i686-elf-gcc/i686-elf-asgrub-mkrescueandxorrisoqemu-system-i386(for local testing)
make # build simpleos.iso
make run # boot in QEMUTo build and boot just the kernel (no ISO/GRUB needed), use the Multiboot1 path:
make kernel.bin
qemu-system-i386 -kernel kernel.bin./build.sh # builds simpleos.iso → web/public/os/The Dockerfile extends a cross-compiler image with GRUB and xorriso. You can also get an interactive shell with scripts/dev-shell.sh.
The web app boots the ISO in a v86 emulator with a CRT-styled UI. It expects three assets:
web/public/os/simpleos.iso— built bymakeor./build.shweb/public/bios/seabios.bin— SeaBIOS ROMweb/public/bios/vgabios.bin— VGA BIOS ROM
It also expects web/public/v86/libv86.js and web/public/v86/v86.wasm from
an official v86 release. These
third-party build artifacts and BIOS files are intentionally not checked in.
If anything is missing, the preflight check shows exactly what's absent instead of booting into a broken state.
cd web
bun install
bun run dev # starts on port 3500
bun run build:os # rebuild kernel and copy ISO in one stepFor a production build, prepare the pinned browser runtime after building the
ISO, then build from the web/ directory:
./build.sh
bun run prepare:web-assets
cd web
vercel build --prod
vercel deploy --prebuilt --prodStart here: src/kernel/kernel.c — kernel_main()
initializes every subsystem in order and is the best map of how the pieces
connect.
From there:
- Boot: boot.s → how the CPU gets from GRUB to
kernel_main - Interrupts: asm_functions.s → ISR stubs, IDT/GDT loading, paging enable
- Processes: process.c → PCB structure, creation, destruction
- Scheduling: scheduler.c → preemptive round-robin, context switch calls
- Memory: vmm.c → page tables, COW fork, address space management
- Syscalls: syscall.c →
int 0x80dispatch, all 23 implementations - Userspace: ulib.h → syscall wrappers that user programs call
- Shell: shell.c → pipes, redirection, job control in ~700 lines
The kernel can be exercised headlessly — COM1 serial mirrors all terminal output,
and the kernel boots directly via its Multiboot1 header with qemu -kernel (no
ISO/GRUB needed). Two scripts drive this:
- scripts/smoke-test.sh — build, boot, and assert the kernel reaches full init without panicking.
- scripts/selftest.sh — build a self-test kernel that runs
userspace/selftest.c as init and verifies fork/wait,
pipes (including blocking transfers larger than the buffer), signals (default
actions and user-mode handlers),
sbrk, BSS zeroing,O_APPEND, working-directory resolution, and the terminal's ANSI escape parser.
A separate verify_os.py checks code integrity without compiling — include dependencies, function signatures, syscall coverage, and common issues.
Read CONTRIBUTING.md for the code style, generated-artifact boundary, and required web and kernel checks.
SimpleOS is an independent systems-learning project. Raintree also maintains DocPull for context acquisition, HIG Doctor for interface audits, PolicyStrata for policy regression testing, Trellis for shared code policy, and Raintree Standards for governed requirements. See the Raintree open-source portfolio.
Contributing · Code of Conduct · Security · Changelog · Source repository · MIT License
SimpleOS source is MIT licensed. Third-party tools and runtime assets used to build or run the project keep their own licenses.