StinkOS

A 32-bit x86 operating system written from scratch. Boots from a custom ELF-aware bootloader, runs PAE paging with W^X, multitasking with COW fork, ACPI shutdown, VFS multi-mount, talks TCP/IP, ships a package manager, runs Doom, and hosts a Rust userland.

i386 freestanding PAE + W^X COW fork ACPI S5 VFS multi-mount Rust userland MBR boot (no GRUB) multiboot-compatible kernel QEMU + real PC GPLv3
Freedoom1 running as a userland app on StinkOS, picking up a shotgun in Phase 1 Map01

Freedoom1 running as a userland app -- ring 3, no GRUB, no Multiboot loader. Bootblock -> ELF kernel -> doomgeneric.

~110 KiBstripped kernel
~80syscalls
28 + 5C/asm + Rust apps
64+host unit tests
v0.10latest release
0third-party libs in kernel

What is inside

Boot & kernel

  • ELF-aware bootloader: 16-sector bootblock loads kernel.elf from disk, walks PT_LOAD program headers, jumps to entry.
  • Multiboot-1 header in the kernel ELF so GRUB can boot it too.
  • Higher-half kernel linked at virt 0x80100000, loaded at phys 0x100000 via AT().
  • PAE paging (PDPT/PD/PT, 8-byte entries, NX bit 63); CPUID-gated EFER.NXE.
  • W^X across kernel image + every user process: .text R-X, .rodata R-NX, .data/.bss RW-NX; wxattack app proves it.
  • ACPI RSDP scan → RSDT/XSDT → FADT (S5 soft-off via PM1a_CNT_BLK) + MADT (CPU + IOAPIC topology).
  • GDT + IDT + PIT @ 100 Hz, PIC remapped, full IRQ dispatch.

Multitasking

  • Per-process page directory; CR3 swap in scheduler; PROC_MAX = 16.
  • COW fork: PG_COW PTE bit 9 + 8 KiB PMM refcount; first writer takes #PF, kernel privatizes (alloc + memcpy + install RW) or just lifts RW (refcount==1).
  • Kernel-stack context switch; round-robin scheduler driven by the PIT IRQ.
  • Anonymous pipes, cooperative signals (SIGINT/SIGTERM), per-process VFS.
  • SYS_FORK / EXEC / WAIT / WAITPID / EXIT_CODE / KILL / SETPRIO / SUSPEND / RESUME / PROC_INFO.
  • Mid-syscall preemption (interrupts re-enabled inside syscall_dispatch).

Network stack

  • TCP RFC 793: retransmit + RTO, slow start, cwnd, fast retransmit, OOO reassembly, MSS, window-scale, SACK, keepalive, persist probe, TIME_WAIT reap.
  • IPv4 with martian filter, Teardrop guard, 2-slot fragment reassembly, source-route drop.
  • DHCP (with retry + secondary DNS), DNS cache, ARP cache with TTL eviction.
  • e1000 NIC driver, IRQ-driven RX.
  • BSD-style sockets: bind / listen / accept / connect.

Userland & shell

  • 30+ shell commands: ps kill suspend resume mem netstat dns arp dmesg vol audio keymap alarm shutdown reboot clock hostname.
  • 256-line scrollback, persisted command history (SHELL.HIS).
  • BR ABNT2 + US keymaps, runtime switch.
  • UTF-8 multi-byte collapse in scrollback.

Storage & FS

  • StinkFS: contiguous-file FS on a raw partition, ~100 MiB data region, 80 file slots (4-sector dir).
  • VFS multi-mount via fs_mount + fs_ops dispatch; DOS-style A: / B: prefix routes per-slot.
  • SYS_MOUNT registers an additional StinkFS region at runtime (second disk image under B:).
  • VFS layer with per-process fd table, open/read/write/seek/close.
  • ATA driver with PIIX bus-master DMA + PIO fallback.
  • Real MBR partition tables on install media.

Rust userland

  • Nightly toolchain + rust-src + custom i686-stinkos target spec (i686 bare-metal, panic=abort, no SSE).
  • -Z build-std=core,alloc compiles core/alloc from source per target.
  • Shared apps/rust/libstink rlib: println!, eprintln!, read_line, exit(code), default #[panic_handler], #[global_allocator] shim over K&R malloc.
  • lib/libstink_syms.c non-inline shims (libstink.h is static inline only -- Rust extern "C" needs linkable symbols).
  • Apps: rs-hello (toolchain), rs-alloc (Box/Vec/free), rs-stdio (println shim), rs-json (~390 LOC recursive-descent parser, zero crates), rs-life (Conway's Game of Life on the framebuffer).

Audio

  • SB16 driver, ISA DMA on channels 1 (8-bit) and 5 (16-bit stereo).
  • 8-channel software mixer with Q16.16 per-channel resampling.
  • Half-buffer IRQ for low latency, master + per-channel volume.
  • Doom SFX wired to the kernel mixer.

Doom port

  • doomgeneric + Freedoom1 / Freedoom2 / FreeDM ship as three apps.
  • Fullscreen via SYS_BLIT_SCALED (kernel-side nearest-neighbour).
  • Save games via VFS; demos via DOOMARGS.CFG (-record, -playdemo).

Package manager

  • .stinkpkg archive format with optional compression.
  • stink-pkg install / query / upgrade / verify / replay.
  • Recursive dependency resolution.
  • SHA-256-hashed manifests, lockfile (STINKPKG.LCK).

Screenshots

StinkOS boot menu listing every userland app on the disk including Rust apps and Doom variants
The boot menu lists every userland app on the disk -- 28 C/asm + 5 Rust apps, picked by ENTER on the highlighted entry.
Freedoom1 mid-combat with pistol drawn, status bar showing health and ammo
Freedoom1 mid-combat. Status bar (health, ammo, armor, weapons grid) is the original Doom HUD running through doomgeneric.
StinkOS shell running help, mem, ps, and netinfo commands back to back
The shell chained through help / mem / ps / netinfo. DHCP bound, gateway and DNS picked up from QEMU's user-mode SLIRP, free-page count exposed via SYS_MEMINFO.
FBDEMO app writing colour bars directly to the linear framebuffer
SYS_MAP_FB in action -- the framebuffer is mapped read-write into the user address space, so each pixel write is one store instruction, zero syscalls.
rs-life: Rust Conway Game of Life with a Gosper glider gun seed emitting gliders diagonally across the framebuffer
rs-life -- Conway's Game of Life in pure Rust. Gosper glider gun seed (Conway/Gosper 1970), 128x96 toroidal grid, 8x8 px cells, diff-painting between generations. Zero external crates; allocator routed through libstink K&R malloc.
Shell bg anim demo: ps listing shows two procs, parent shell and ANIM child forked into the background
Multi-process proof: shell forks anim into the background, then runs ps against itself. The listing shows TWO procs alive -- parent shell + ANIM child -- each with its own page directory (COW-shared at fork, privatized on first write).

Get started

01.

Boot the latest ISO

Download from GitHub Releases. The ISO is a raw disk image -- write to a USB stick or run in QEMU:

qemu-system-i386 -drive format=raw,file=stinkos-install.iso \
  -netdev user,id=net0 -device e1000,netdev=net0 \
  -audiodev sdl,id=snd0 -device sb16,audiodev=snd0

02.

Build from source

Linux or WSL. Needs an i386-elf cross-toolchain (the build script grabs it):

git clone https://github.com/Jovinull/StinkOS.git
cd StinkOS
bash tools/build-cross-toolchain.sh   # one-time, ~15 min
make                                  # builds os.bin + kernel.elf
make run                              # boots in QEMU

03.

Run the tests

62 host-side unit tests pin the kernel's decision logic without QEMU:

make unittest          # all 62 host tests
make test-headless     # full QEMU boot + every app
make readelf-kernel    # PT_LOAD inspection

Package repository

This site hosts a sample .stinkpkg repository so a fresh StinkOS install can fetch software out of the box. Point your install at this URL by writing STINKPKG.CONF:

repo_url=https://jovinull.github.io/StinkOS

Then on the running system:

stink-pkg install snake
stink-pkg install pong
stink-pkg upgrade

Browse the live package index →

Documentation

The kernel is documented in tree -- every subsystem has a markdown doc tied to it.

Contributing

The repo accepts PRs. Branch protection on master requires the headless boot test + 11 dedicated smoke tests to pass; CI runs the cross-toolchain build, every host-side unit test, and the full Rust toolchain build on every push.

Open issues for any of the items in the changelog backlog -- multi-TTY console, AML interpreter for real-laptop ACPI, OPL/MIDI Doom music, SMP bring-up (LAPIC/IOAPIC + atomic ops in PMM refcount), AC97/HDA audio, PXE install, rtl8139 / virtio-net, IPv6 -- and more Rust apps that exploit Rust's safety wins.

Pick something small to start: there is a list of TODO §X.Y markers across the kernel that are explicitly tagged for new contributors.