Built during Lock-In week 1 of FlavorTown. No proper name yet so the URL might change at some point.
- Boots via the Limine bootloader and splashes a big OS in the middle of your screen
- Shows the current time and timezone (UTC by default, change the
timer_set_timezone()call inkernel.cif you want) - Has a working shell with basic commands
- Has a full windowed desktop with draggable windows, a taskbar, a Start menu, and an analog clock widget
- Dynamically loads and runs userspace programs — the kernel literally scans an ELF section for registered programs and just runs them, which honestly took way too long to figure out but is extremely cool
- Comes with a few programs out of the box:
desktop— a full GUI with mouse support, draggable windows, a terminal, and an app launcher that auto-discovers every registered programdrawing— move a cursor around and draw pixels on screensnake— fully playable snake with a rainbow-colored snake because why notmusic— plays songs through the PC speaker with a lot of songs included + a playable piano/keyboard (Songs thanks to the people who helped write songs for the API)info— system info screen (memory, screen size, GPU info, etc.)memtest— heap stress teststorage— ATA disk driver with a basic filesystem on top
- Detects your GPU via a PCI bus scan (Intel, AMD, NVIDIA, VMware, VirtIO) and knows the vendor, device ID, and VRAM size — but doesn't actually use it in most situations currently. All rendering goes straight through the Limine framebuffer except some, which indeed go through the GPU.
| Boot splash | Shell | Desktop |
|---|---|---|
| GPU acceleration | Snake | Music Player |
|---|---|---|
cls clear the screen
clock show current time
help list commands
reboot reboot the machine
shutdown shut it down
exit halt the CPU (rip)
run any userspace program by name
name
name
┌──────────────────────────────────────────────┐
│ Userspace Programs │
│ shell desktop snake music drawing ... │
├──────────────────────────────────────────────┤
│ init system │
│ (discovers programs, runs test, launches) │
├──────────────────────────────────────────────┤
│ Libraries │
│ draw · font · keyboard · mouse · timer │
│ memory · speaker · power · libc subset │
├──────────────────────────────────────────────┤
│ Kernel │
│ framebuffer init · heap setup · RTC │
├──────────────────────────────────────────────┤
│ Limine Bootloader │
└──────────────────────────────────────────────┘
VM x86-64 hardware
x86-64 hardware
The userspace loader works by putting every program struct into a custom .userspace_programs ELF section. At runtime init scans from __start_userspace_programs to __stop_userspace_programs, matches the name, runs the test function, and launches it. Biggest breakthrough of the whole project tbh.
Clone the repo and cd into it. You need x86_64-linux-gnu-gcc, xorriso, and qemu-system-x86_64.
make clean &∓& make &∓& make runThis compiles everything, creates OS.iso, and launches it in QEMU with VirtIO GPU and PC speaker audio. That's it.
Full platform-specific instructions are further down if you're on Windows or macOS.
kmain() is the entry point. It immediately switches to a dedicated stack to avoid triple-faults from deep call chains, then:
- Calls
draw_init()to configure the Limine framebuffer. - Fills the screen and shows an "OS Booting…" splash.
- Calls
timer_init()to start the RTC, sets the UTC offset. - Draws the centred "OS" logo and the current time.
- Waits 2 seconds so you can actually read the splash.
- Calls
memory_init()to bring up the heap (16 MB at0x1000000). - Calls
init_main("shell")to hand off to userspace. The kernel never returns.
kernel/linker.ld defines the .userspace_programs section that makes the whole program discovery system work.
draw — Full GPU-abstracted graphics layer. Detects the GPU via PCI, selects a driver, and exposes put_pixel, fill_rect, draw_line, draw_circle, draw_string, and more. Also has basic VRAM allocation stubs ready for when the hardware-accelerated path gets wired up.
font — Bitmapped font renderer. Supports arbitrary integer scale factors, which is why everything in the OS can be drawn at 1x, 2x, or bigger.
keyboard — PS/2 keyboard driver. Polls port 0x60, translates scan codes to ASCII. Exposes keyboard_init(), keyboard_read() (blocking), and keyboard_has_key() (non-blocking).
mouse — PS/2 mouse driver. Reads 3-byte packets (buttons + delta-X/Y), clamps to screen bounds. Exposes mouse_init(), mouse_poll(), and mouse_get_state().
timer — RTC/CMOS driver. Reads the hardware clock, applies a UTC offset, provides timer_get_time() and timer_delay_s().
memory — Simple free-list heap allocator. Initialised by the kernel. kmalloc() and kfree().
speaker — PC speaker driver with a note/frequency API. Used by the music player.
power — power_reboot() via keyboard controller reset. power_shutdown() via ACPI S5 port write.
libc/ — Freestanding libc subset: printf, snprintf, puts, strlen, strcmp, memcpy, memset, atoi, ctype helpers. No host libc anywhere.
Every program is one struct placed in the .userspace_programs linker section:
struct userspace_program {
const char *name; // command name
userspace_entry main; // void fn(void)
userspace_test test; // int fn(void) — 1 = OK, 0 = broken
};;init_main(name) finds the matching program, calls its .test(), shows [OK] or [FAILED] on screen, flushes the PS/2 buffer so leftover mouse/keyboard bytes don't leak into the new program, clears the screen, and calls .main(). On return it flushes PS/2 again so the caller gets a clean input state.
shell — Text-mode CLI. Draws shell>gt;, reads keystrokes, handles backspace and enter, dispatches to built-in commands or init_main() for anything else.
desktop — Windowed GUI. Dot-grid background, draggable windows with title bars and close buttons, taskbar with a live clock, Start menu, app launcher that auto-discovers all registered programs, windowed terminal, and an analog clock window with hands rendered via a sin/cos lookup table (no FPU needed).
snake — Classic Snake on a 42×30 grid. Rainbow body because why not. Timer-paced, keyboard-controlled.
music — PC speaker player. Songs are Note[] arrays (frequency + duration). Ships with Megalovania and others. Press a key to skip tracks.
drawing — Freehand pixel canvas.
info — System info: screen resolution, GPU vendor/device ID, VRAM size, free heap.
memtest — Allocates and frees heap blocks, verifies contents, reports pass/fail.
storage — ATA PIO driver, a simple flat filesystem (SFS), a RAM filesystem (RAMFS), and a VFS layer on top. Reads/writes the QEMU IDE disk image.
You dont have to build the OS.iso yourself. You can also download it from the github releases page and skip the building section..
Ubuntu / Debian:
sudo apt update
sudo apt install gcc-x86-64-linux-gnu binutils-x86-64-linux-gnu xorriso qemu-system-x866Arch Linux:
sudo pacman -S x86_64-linux-gnu-gcc x86_64-linux-gnu-binutils xorriso qemumacOS (Homebrew):
brew install x86_64-elf-gcc x86_64-elf-binutils xorriso qemu
# Then edit the Makefile: change the CC and LD prefixes to x86_64-elf-Then:
git clone --recurse-submodules https://github.com/QKing-Official/os.git
cd os
make # produces OS.iso
make disk.img # first time only — 64 MB blank IDE disk for storageIf you miss a Limine file, clone the limine repo to the limine dir and make all files in the root of that subdirectory.
make disk.img # first time only
make runnOr manually (replace pa with pipewire or alsa if needed):
qemu-system-x86_64 \
-cdrom OS.iso -boot d \
-m 256M \
-audiodev pa,id=snd0 -machine pcspk-audiodev=snd0 \
-vga virtio -global virtio-gpu-pci.vgamem_mb=256 \
-drive file=disk.img,format=raw,if=ide,index=0,media=disk=disk- Install QEMU for Windows.
- Open PowerShell in the project folder.
- Create the disk image (first time only):
qemu-img create -f raw disk.img 64M - Run:
qemu-system-x86_64 `
-cdrom OS.iso -boot d `
-m 256M `
-audiodev dsound,id=snd0 -machine pcspk-audiodev=snd0 `
-vga virtio -global virtio-gpu-pci.vgamem_mb=256 `
-drive file=disk.img,format=raw,if=ide,index=0,media=diskDrop the -audiodev / -machine pcspk-audiodev lines if you don't need music.
Homebrew QEMU doesn't have PulseAudio, use coreaudio instead:
qemu-system-x86_64 \
-cdrom OS.iso -boot d \
-m 256M \
-audiodev coreaudio,id=snd0 -machine pcspk-audiodev=snd0 \
-vga virtio -global virtio-gpu-pci.vgamem_mb=256 \
-drive file=disk.img,format=raw,if=ide,index=0,media=disk=diskOn Apple Silicon (M1/M2/M3) x86-64 is fully emulated so it's a bit slower. Add -accel tcg,thread=multi for a speed bump:
qemu-system-x86_64 -accel tcg,thread=multi \
-cdrom OS.iso -boot d -m 256M \
-audiodev coreaudio,id=snd0 -machine pcspk-audiodev=snd0 \
-vga virtio -global virtio-gpu-pci.vgamem_mb=256 \
-drive file=disk.img,format=raw,if=ide,index=0,media=diskdiskThe linker auto-discovers programs — there's no registration table to touch.
1. Create userspace/myprog/myprog.c:
#include "../../libraries/draw.h"
#include "../../libraries/keyboard.h"
#include "../../libraries/timer.h"
#include "../userspace.h"
static void myprog_main(void) {
fill_rect(0, 0, screen_width(), screen_height(), 0xFF0D1117);
set_fg(0xFF00FF88);
set_bg(0xFF0D1117);
draw_string(20, 20, "Hello from myprog!", 2);
keyboard_init();
timer_delay_ms(5000); // Wait for 5 seconds
keyboard_read(); // wait for a key before returning
}
static int myprog_test(void) { return 1; }
// This one line is all the wiring you need
__attribute__(((used, section(".userspace_programs"), aligned(1)))
struct userspace_program myprog_prog = {
..name = "myprog",
..main = myprog_main,
..test = myprog_test,
};;2. Build — the Makefile already globs all .c files, nothing else to change:
make &∓& make run3. Run it from the shell (shell> myprogrog) or it'll appear automatically in START → Apps on the desktop.
// Graphics (colors are 0xAARRGGBB, use 0xFF prefix)
void fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color);
void put_pixel(uint32_t x, uint32_t y, uint32_t color);
void draw_line(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t color);
void draw_circle(uint32_t cx, uint32_t cy, uint32_t r, uint32_t color);
void fill_circle(uint32_t cx, uint32_t cy, uint32_t r, uint32_t color);
void draw_rect_outline(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t thickness, uint32_t color);
void draw_string(uint32_t x, uint32_t y, const char *str, uint32_t scale);
void set_fg(uint32_t color);
void set_bg(uint32_t color);
uint32_t screen_width(void);
uint32_t screen_height(void);
// Keyboard
void keyboard_init(void);
char keyboard_read(void); // blocks
int keyboard_has_key(void); // non-blocking
// Mouse
void mouse_init(uint32_t sw, uint32_t sh);
void mouse_deinit(void);
int mouse_poll(void);
const mouse_state_t *mouse_get_state(void); // .x .y .left .right
// Timer
void timer_init(void);
void timer_get_time(uint8_t *h, uint8_t *m, uint8_t *s);
void timer_delay_s(uint32_t seconds);
// Memory
void *kmalloc(size_t size);
void kfree(void *ptr);
size_t memory_free_space(void);
// Speaker
void speaker_play(uint32_t freq);
void speaker_stop(void);
// Power
void power_reboot(void);
void power_shutdown(void);Locking back in now. More stuff coming soon probably.
Cyaa
— QKing on devlog 6 (ship 1)