Skip to content

andrewhinh/browser_os

 
 

Repository files navigation

Browser OS

An OS written in Rust and Svelte on the web. Inspired by:

Quickstart

Install dependencies:

rustup toolchain install nightly  # at least nightly 2020-07-15
cargo install bootimage
cargo install bacon --locked --features "clipboard"
npm install
uv venv
uv pip install pre-commit
source .venv/bin/activate
pre-commit install
flyctl auth login

Commands:

# test the kernel
cargo test -p os --target crates/os/x86_64-os.json -Z build-std=core,compiler_builtins,alloc -Z build-std-features=compiler-builtins-mem

# build the kernel
cargo bootimage -p os --target crates/os/x86_64-os.json -Z build-std=core,compiler_builtins,alloc -Z build-std-features=compiler-builtins-mem

# run the kernel
qemu-system-x86_64 -d int -no-reboot -no-shutdown -serial stdio -D qemu.log -drive format=raw,file=target/x86_64-os/debug/bootimage-os.bin

# test the app
cargo test -p app

# run the app
mprocs

# run pre-commit hooks
uvx pre-commit run --all-files

# deploy to fly.io
flyctl launch  # once
flyctl deploy

Roadmap

Boot, CPU, and Interrupts

  • Bootloader: bootable disk image via bootloader crate
  • GDT/TSS: segment descriptors with double-fault IST stack
  • IDT: exception handlers, PIC remap, timer (100 Hz) + keyboard IRQs
  • Syscall entry: syscall/sysret, MSR setup (IA32_STAR, IA32_LSTAR, IA32_FMASK)
  • Ring 3 transition: enter user mode and return via syscall
  • Context switching: process table, PCB, CR3 tracking, kernel/user stacks
  • Preemptive scheduler: timer-driven round-robin with context save/restore
  • Program loading: flat binary + ELF64 loader (PT_LOAD segments)
  • Trap coverage: page fault/GP/DF/invalid-opcode paths with panic-safe reporting and tests
  • Stack guards: guard pages for kernel/user stacks; IST coverage for nested faults

Memory Management

  • Paging: 4-level page tables via x86_64, per-process roots
  • Frame allocator: allocate physical frames from BootInfo memory map
  • Heap allocator: kernel heap via linked_list_allocator
  • User pointer safety: copyin/copyout helpers that fault on invalid user buffers
  • User heap: sbrk growth/shrink with guard pages

Processes and Scheduling

  • Process lifecycle: fork/exec/exit/wait/waitpid with zombie reaping and reparenting
  • Blocking primitives: sleep queues for nanosleep, waitpid, IPC pipes/mailboxes
  • Sleep/wakeup channels for generic I/O wait; blocking mutexes and condition variables
  • Voluntary yield and per-process time accounting
  • Process control extras: kill, pause, uptime

IPC

  • Message-passing: per-process mailboxes with bounded queues and blocking receive
  • Pipes: pipe, pipe_read, pipe_write, pipe_close (anonymous byte streams)
  • Pipe correctness suite: half-close, EOF, backpressure, large writes, fairness under contention
  • Event waits: select/poll-style waits across channels, pipes, and keyboard input

File System and Storage

  • Ramfs bring-up: inode table, directory entries, root directory
  • Per-process FD table with inheritance; dup/dup2 support and close semantics
  • Path resolution: absolute/relative traversal with ./.. and permissions
  • File I/O syscalls: open, close, read, write, lseek, stat, fstat
  • Directories and links: link, unlink, readdir/getdents (have mkdir, chdir, getcwd)
  • File descriptor utilities: close-on-exec flags, fd table limits, edge cases
  • File system tests: directory traversal, link counts, permission checks

Device Drivers

  • VGA text mode: 80×25 character output mirrored to browser via serial
  • Serial (UART): COM1 logging and VGA frame transport via uart_16550
  • PS/2 keyboard: scancode reading and decoding via pc-keyboard
  • Timer (PIT): 100 Hz tick counter and uptime tracking via pic8259
  • Interrupt hardening: spurious IRQ handling, prioritization, and metrics

Syscalls

  • Console I/O: read(fd=0), write(fd=1) for keyboard/console
  • Process control: fork, exec, exit, wait, waitpid
  • Process info: getpid, getppid
  • Timing: nanosleep
  • IPC: channel_send, channel_recv, pipe, pipe_read, pipe_write, pipe_close
  • File system: open, close, read, write, lseek, stat, fstat, dup, dup2
  • Process control extras: kill, pause, uptime, yield
  • User buffer safety: copyin/copyout with fault recovery for all pointer-using syscalls
  • FS coverage: link, unlink (have mkdir, chdir, getcwd)

User Programs

  • Shell core:
    • REPL with line editing; builtins exit, echo, help
    • Piping (|), redirection (>, <, >>), background &, jobs/fg/bg
  • Utilities:
    • cat, wc, stat, pwd
    • ls, echo, rm, mkdir, ln, grep, time, yes
  • Process/IPC tools:
    • sleep N
    • ps, kill, lightweight top
    • IPC demos: pipe ping-pong; mailbox/channel echo and stress
  • Testing and stress:
    • forktest, stressfs/logstress, simple zombie/orphan demo, basic user test suite
  • Init:
    • Minimal init that execs the shell and reaps children

Frontend

  • Axum backend: serve static assets, manage QEMU bridge state
  • QEMU integration: spawn QEMU, capture serial, decode VGA markers
  • WebSocket VGA stream: push frames/cells to browser, cache for new clients
  • Keyboard bridge: browser keydown → QMP sendkey
  • VM controls: reset, pause/resume via QMP
  • Multi-session: isolated QEMU instances per user with resource caps and lifecycle management
  • Observability: expose scheduler/process telemetry, syscall stats, and backpressure signals to the UI
  • Browser UX: overlays for file system, IPC demo tooling, and keyboard/mouse capture options

Testing

  • Test framework: custom harness with QEMU exit codes
  • Boot tests: println, heap allocation, stack overflow IST, program loader
  • Syscall tests: user-mode FS happy-path + error suite
  • Scheduler tests: preemption, blocking, wake-up ordering, starvation checks
  • Concurrency tests: lock correctness and contention stress
  • FS tests: file/directory operations and durability once storage lands
  • IPC tests: pipe edge cases, mailbox saturation, channel fairness
  • Soak/fuzz: ELF loader fuzzing, QMP parser fuzzing, long-running leak/stability runs

Description

System Architecture Diagram

Some interesting features (beyond that covered in the blog post series):

  • The kernel's vga_buffer mirrors screen state over UART using three compact text formats: [[VGA_FRAME …]] (full 80×25 snapshot), [[VGA_CELL …]] (single-character update), and [[VGA_SCROLL …]] (line shift with new bottom row). The Axum server tails QEMU's serial output, parses these markers, and broadcasts typed VgaUpdate messages over WebSocket. The browser builds the grid once and applies incremental updates—only changed cells touch the DOM.
  • The web server communicates with QEMU via QMP (QEMU Monitor Protocol) over a Unix socket. Browser keyboard events are translated to sendkey commands, and VM controls (reset, pause, resume) use HMP commands tunneled through QMP—allowing full interaction without a local display.
  • The timer interrupt periodically emits [[SYSINFO uptime_ms=… usable_mb=… total_mb=… ticks=…]] and [[PROC pid=… name=… state=… …]] lines. The backend parses these into OsStats and ProcessesSnapshot structs, cached in AppState and exposed via /api/os/stats and /api/os/processes for real-time dashboard panels.
  • The kernel uses syscall/sysret with MSR configuration (STAR/LSTAR/FMASK). A naked syscall_entry stub saves registers and dispatches to Rust handlers implementing console I/O, process control (fork, exec, exit, wait, waitpid), timing (nanosleep), and IPC (channel_send/channel_recv, pipe/pipe_read/pipe_write/pipe_close), plus PID queries.
  • The PCB table tracks each process's PID, parent, kernel stack, entry point, run state, saved registers, and page table root (CR3). The timer IRQ fires at 100 Hz; each tick saves the outgoing context, picks the next Ready process, restores its context (including CR3), and returns via iretq. Context switches only occur from ring 3 to avoid interrupting in-flight syscalls.
  • The ELF loader parses ELF64 headers and PT_LOAD segments, supporting both flat (vaddr=0) and position-dependent layouts. Segments are copied into heap-backed regions, .bss is zero-filled, and the entry point/stack pointer are recorded in the PCB. This allows loading separately-compiled user binaries rather than embedding raw assembly.
  • The message-passing IPC subsystem gives each process a kernel-managed mailbox (bounded queue). channel_send(target_pid, buf, len) copies payloads into the target's queue and wakes any blocked receiver; channel_recv(buf, len) pops from the caller's mailbox, blocking if empty until a message arrives.
  • Anonymous pipes provide unidirectional byte streams between processes. pipe allocates a 512-byte circular buffer; pipe_read and pipe_write transfer data with blocking when empty/full; pipe_close signals EOF to the other end.

The important files are:

crates/
  app/
    src/
      handlers.rs           HTTP + WebSocket handlers for VGA stream, QEMU control, and OS stats/processes APIs
      qmp.rs                QMP client over Unix socket for `sendkey` and HMP commands (reset, pause, resume)
      main.rs               Axum server; serves `/api/*`, `/ws/vga`, and the built SvelteKit `dist/`
    tests/
      handlers.rs           Smoke tests for static asset and API endpoints
  os/
    src/
      boot/
        gdt.rs              GDT/TSS setup (kernel + user segments, IST stack for double-fault)
        interrupts.rs       IDT, PIC remapping, timer (100 Hz) + keyboard IRQ handlers, exception handlers
        memory.rs           Paging setup, frame allocator, per-process tables, copy-on-write fork
      drivers/
        serial.rs           UART (COM1) initialization and `serial_print!` macros
        vga.rs              VGA text console + serial mirroring (`[[VGA_FRAME]]`, `[[VGA_CELL]]`, `[[VGA_SCROLL]]`)
      process/
        mod.rs              PCB table, PID allocation, context save/restore, blocking/wake primitives
        scheduler.rs        Preemptive round-robin scheduler triggered by timer IRQ
      programs/
        binaries.rs         Embeds prebuilt Rust user ELF images via include_bytes!
        loader.rs           ELF64 parser and PT_LOAD segment loader for user programs
        user_mode.rs        Launches user space, spawns builtin programs, enters ring 3 via `sysretq`
      task/
        executor.rs         Cooperative async executor with task queue and Wakers
        keyboard.rs         Scancode queue, async `ScancodeStream`, and blocking `read_char_blocking` for syscalls
      allocator.rs          Kernel heap: fixed-size block allocator with linked-list fallback
      context.rs            `SavedRegisters`, `CpuContext`, `PageTableRoot` (CR3) for context switching
      ipc.rs                Message-passing mailboxes + anonymous pipes with blocking read/write
      syscall.rs            `syscall`/`sysret` entry stub, MSR setup, and Rust syscall handlers
      sysinfo.rs            Emits `[[SYSINFO]]` and `[[PROC]]` telemetry over serial for the frontend
    tests/
      basic_boot.rs         println smoke test
      heap_allocation.rs    Heap allocator stress/regression tests
      stack_overflow.rs     Double-fault IST coverage
      should_panic.rs       Ensures panic path exits QEMU as expected
      program_loader.rs     Validates spawn_program stacks + ELF guards
    x86_64-os.json          Custom target spec: no_std, soft-float, disable red zone, panic=abort
  user/
    src/bin/
      shell.rs              User-mode shell compiled as ELF and embedded in kernel
  src/
    routes/
      +page.svelte          UI (VGA grid, overlays, stats/IPC panels, websocket + keyboard bridge)

About

An OS in the Browser

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 85.5%
  • Svelte 13.5%
  • Other 1.0%