← Portfolio Home
Builds / Operating Systems
Hermes OS
A 32-bit x86 operating system kernel written from scratch — no libc, no host runtime,
no framework. Boots under GRUB, sets up its own descriptor tables, services its own
hardware interrupts, manages its own memory, and drops you into an interactive shell.
● Working — boots in QEMU
Interactive shell verified
NASM + freestanding C
i386 protected mode
2,500+lines of code
15source files
10subsystems
9shell commands
0external deps
Live console — real captured output
qemu-system-i386 — oskernel.elf — serial0
=== Hermes OS Shell ===
Type 'help' for available commands.
$ help
Available commands:
help Show this help message
hello Print hello world
echo Echo text to screen
uptime Show system uptime
ps List running tasks
ls List files in filesystem
cat Display file contents
mem Show memory info
reboot Reboot the system
$ hello
Hello, World! Welcome to Hermes OS.
$ echo hermes os
hermes os
$ uptime
Uptime: 15.7 seconds (1575 ticks)
$ ps
PID STATE NAME
--- ----- ----
0 READY demo1
1 READY demo2
2 READY demo3
$ ls
NAME TYPE SIZE
---- ---- ----
README FILE 665
VERSION FILE 60
HELLO FILE 37
docs DIR 0
$ cat README
====================================
Hermes OS - x86 Kernel from Scratch
====================================
This is a minimal x86 operating system kernel
built from scratch featuring:
[BOOT] Multiboot-compliant bootloader
[GDT] Protected mode with segmentation
[IDT] Interrupt handling (32 IRQs + 16 exceptions)
[PIC] Programmable Interrupt Controller
[TIM] PIT timer with 100Hz scheduling tick
[KBD] PS/2 keyboard driver
[MMU] Physical memory manager + paging
[SCH] Preemptive round-robin scheduler
[FS] Simple in-memory filesystem
[SH] Interactive command shell
Commands: help, hello, echo, uptime, ps, ls, cat, mem, reboot
$ mem
Free memory blocks: 15784 (63136 KB)
$
Captured from the serial port of a real qemu-system-i386 boot, with keystrokes
injected as genuine PS/2 scancodes through the QEMU monitor — the same path a physical
keyboard would take.
Boot flow
GRUB → multiboot header →
_start (32-bit) → GDT →
IDT → PIC remap → PIT 100 Hz
→ PS/2 keyboard → PMM + paging
→ filesystem → shell
Subsystems
Bootboot/boot.asm
Multiboot header, GRUB-loadable, enters 32-bit protected mode at 1 MB
GDTkernel/gdt.c
5 descriptors — null, ring-0 code/data, ring-3 code/data
IDTkernel/idt.c
256 gates: 32 CPU exceptions + 16 hardware IRQs
PICkernel/irq.c
8259A remapped to vectors 32–47, per-IRQ handler registry
Timerkernel/timer.c
PIT channel 0 at 100 Hz, tick counter driving uptime
Keyboardkernel/keyboard.c
8042 PS/2 init, scancode→ASCII, shift/caps, ring buffer
Memorykernel/pmm.c
Bitmap physical allocator + x86 paging, 16 MB identity-mapped
Schedulerkernel/scheduler.c
Round-robin task table, TCBs, assembly context switch
Filesystemkernel/fs.c
In-memory file table with read/list/stat
Shellkernel/shell.c
9 built-in commands, line editing, VGA + serial output
Three bugs that actually had to be debugged
The interrupt handler read garbage
Every IRQ is dispatched as irq_handler(registers_t*), but the assembly stub called irq_handler without pushing the frame pointer. The C code then read “int_no” out of whatever happened to be on the stack and indexed the handler table with it — a page fault at 0xc014d3d0, recursing until the stack hit an unmapped page and triple-faulted. Fix: push esp before the call in both the ISR and IRQ stubs.
Two bits of the PIC mask
0xFB looks like “enable IRQ0 and IRQ1” but the 8259A mask is inverted — 1 means masked. That value actually disabled both the timer and the keyboard. Correct mask: 0xFC.
The CPU slept through every keystroke
The idle loop used hlt, which only wakes on an interrupt. With IRQ0 and IRQ1 masked and the UART’s receive interrupt off, nothing ever woke it — so polled serial input was never read. Keeping the 100 Hz timer unmasked fixes it, and additionally required enabling the 8042 controller’s keyboard-interrupt bit (config byte bit 0) during init.
Build & run
# toolchain: nasm, clang (freestanding), x86_64-elf-ld, qemu
make # → oskernel.elf (~37 KB)
qemu-system-i386 -kernel oskernel.elf -m 64
# headless, with the shell on your terminal:
qemu-system-i386 -kernel oskernel.elf -m 64 -serial stdio -display none
Shell commands
| Command | What it does | Proves |
help | List all commands | Shell command table |
hello | Print a greeting | VGA + serial output |
echo … | Echo arguments back | Line parsing |
uptime | Ticks and seconds since boot | PIT + IRQ0 firing |
ps | Task table with state | Scheduler structures |
ls | Files with type and size | Filesystem directory scan |
cat <file> | Print file contents | Filesystem read path |
mem | Free blocks and KB | Bitmap allocator |
reboot | Triple-fault reset | CPU control |