MarsOS

A desktop operating system I built from scratch - UEFI boot, cooperative multitasking kernel, a real windowed GUI, and an event-driven application framework, all in freestanding C.

I wanted to understand what an operating system actually is below the abstraction layers. Not Linux internals, not a POSIX wrapper - actually building one. MarsOS boots from UEFI on x86_64, brings up its own kernel, renders a windowed desktop to the framebuffer, and runs a set of built-in applications. No libc. No third-party OS libraries. Just freestanding C and UEFI firmware protocols.

It runs in QEMU today. Real hardware is the goal.

Boot sequence

The firmware loads BOOTX64.EFI. From there the boot entry locates the GOP framebuffer, keyboard, and pointer protocols via UEFI Boot Services, packs everything into a boot_info_t struct, and hands control to kernel_main. The kernel then brings up each subsystem in order: platform, diagnostics, interrupts, event bus, timer, scheduler, process manager, memory, virtual memory, heap, syscall interface, input drivers, window manager, VFS, and finally the application framework.

After that the scheduler loop runs forever. No OS call ever returns to firmware.

The kernel

The scheduler is cooperative round-robin - tasks yield by returning from their step function. There’s an optional timer-preemptive mode but the main model is cooperative, which keeps the design simple and eliminates a whole class of concurrency bugs at this stage. Each process gets a capability bitmask (input, graphics, storage, system) that controls what it can access.

Memory is managed with a heap for small allocations and a page allocator for large ones. There’s also a virtual memory subsystem and a simple FAT VFS for disk access.

The GUI

The window manager renders directly to the UEFI GOP framebuffer in software - no GPU. It supports overlapping windows with z-ordering, a taskbar, a start menu, focus management, keyboard and mouse routing, and a resize handle on every window. Text is rendered with an 8x16 monospace bitmap font.

Apps don’t touch the framebuffer directly. Instead each app maintains a UTF-16 text content buffer and calls wm_set_window_content(). The WM renders it. This keeps apps simple and the rendering logic centralised.

The event bus

Everything in the OS communicates through an event bus. There are four broadcast channels (input, keyboard, system, app) plus per-process targeted queues. Events carry a 64-byte payload, a source PID, an optional target PID, and a backpressure policy (drop-oldest or drop-newest when a queue is full). The WM consumes raw input events, figures out which window has focus, and re-publishes them as targeted APP_INPUT events to the right process queue.

Applications

Apps are kernel tasks with a manifest that declares an ID, title, initial window geometry, and capability flags. The task step function processes up to 12 events per cycle, updates the content buffer, and returns. Built-in apps:

More are in progress: text editor, calculator, paint, audio player, system info, network tools.

Drivers

The input stack supports UEFI keyboard and three mouse protocols (UEFI, PS/2, absolute pointer) with a priority ordering so it degrades gracefully. There’s also a PCI driver, block device driver, audio driver skeleton, and a network driver stub.

Testing

Because you can’t unit test a kernel running inside QEMU very easily, I built a host-test harness that compiles the same kernel source files against the host libc and runs contract tests on the event bus, scheduler, process model, memory allocator, heap, and VM builder. If make -C os test-host is green and make -C os run reaches the desktop, everything is consistent.

What I learnt

TODO

Built with