A bare-metal x86 kernel written from scratch in C and x86 Assembly, running on QEMU. Built to understand how operating systems actually work at the hardware level — no abstractions, no OS beneath you.
- Boots from scratch via a custom two-stage bootloader (Real Mode → Protected Mode)
- Initializes GDT (Global Descriptor Table) with kernel code/data segments
- Remaps the PIC (8259A) and sets up a full 256-entry IDT (Interrupt Descriptor Table)
- Handles hardware interrupts: keyboard (IRQ1), timer (IRQ0), and default fallback
- Catches CPU exceptions: divide by zero (#0), general protection fault (#13), page fault (#14)
- Drives a VGA text mode terminal with scrolling, hardware cursor, and direct memory writes
- Runs an interactive shell with command history, arrow key navigation, insert-mode editing
| Command | Description |
|---|---|
help |
List available commands |
hello |
Print a greeting |
version |
Show OS version string |
clear |
Clear the terminal screen |
Shell features:
- Up/Down arrow keys to scroll through command history (last 10 commands)
- Left/Right arrow keys to move cursor within the current line
- Insert mode — characters insert at cursor position, not just append
- Home/End keys to jump to start/end of line
- Delete key support
- Screen scrolls when output reaches the bottom
myOS/
├── boot.asm # Stage 1: 16-bit bootloader
│ # Loads kernel from disk via BIOS int 0x13
│ # Sets up GDT, switches CPU to 32-bit Protected Mode
│ # Jumps to kernel at 0x1000
│
├── entry.asm # Kernel entry point
│ # Sets up stack, calls kernel_main()
│
├── kernel.c # Kernel main
│ # Clears VGA buffer, remaps PIC, initializes IDT
│ # Flushes keyboard buffer, enables interrupts, starts shell
│
├── idt/
│ ├── idt.h # IDT entry and descriptor structs (packed)
│ ├── idt.c # IDT setup, gate registration, exception handlers
│ │ # TTY state, VGA terminal, shell logic, keyboard handler
│ ├── pic.h # Port I/O inline asm (outb/inb/sti)
│ └── pic.c # 8259A PIC remapping (master 0x20, slave 0x28)
│
├── isr.asm # Low-level ISR/IRQ stubs that call C handlers
├── linker.ld # Linker script — places .text.start at correct address
└── Makefile # Build system
Power on
│
▼
BIOS loads sector 1 → 0x7C00 (boot.asm, Real Mode 16-bit)
│
├─ Zero segment registers
├─ Load kernel sectors 2-21 → 0x1000 via BIOS int 0x13
├─ Load GDT (null + code + data descriptors)
├─ Set CR0 PE bit → enter Protected Mode
└─ Far jump to flush pipeline → 32-bit mode
│
▼
entry.asm (Protected Mode 32-bit)
│
└─ Set up kernel stack at 0x9F000
└─ Call kernel_main()
│
▼
kernel.c → kernel_main()
│
├─ Clear VGA buffer (0xB8000)
├─ pic_remap() — remap IRQs above CPU exceptions
├─ idt_init() — register 256 gates, set exception + IRQ handlers
├─ Flush keyboard buffer
├─ sti() — enable hardware interrupts
└─ shell_prompt() — hand control to interactive shell
| Vector | Source | Handler |
|---|---|---|
| 0x00 | Divide by zero | Print exception, halt |
| 0x0D | General Protection Fault | Print exception, halt |
| 0x0E | Page Fault | Print exception, halt |
| 0x20 | IRQ0 — Timer | irq0_handler() (stub) |
| 0x21 | IRQ1 — Keyboard | irq1_handler() — full keyboard driver |
| 0x22–0xFF | All others | irq_default fallback |
Requirements:
# Arch Linux
sudo pacman -S nasm gcc qemu nasm binutilsBuild and run:
make # builds kernel.bin
make run # launches in QEMUClean:
make clean- How the BIOS boot sequence works and why the bootloader lives at
0x7C00 - Real Mode vs Protected Mode — segment registers, GDT, privilege rings
- How the CPU uses the IDT to dispatch interrupts and exceptions
- Why the PIC needs to be remapped (IRQs 0-7 clash with CPU exception vectors by default)
- Direct VGA memory-mapped I/O at
0xB8000— no OS, no syscalls, just write to memory - How keyboard scancodes work and how to build an in-kernel keyboard driver
- Inline assembly in C (
outb,inb,sti,lidt) - Linker scripts — controlling exactly where code lands in memory
- Physical memory manager (bitmap allocator)
- Paging — virtual memory, page tables
-
kmalloc/kfree - Process management and basic scheduler
- Userspace and privilege level switching (ring 0 → ring 3)
- Simple filesystem (read from disk)
- Port to Rust (rewrite kernel components in Rust, keep ASM boot)
- OSDev Wiki — the bible
- Intel x86 Software Developer Manuals
- Writing an OS in Rust — Philipp Oppermann
Built from scratch. No tutorials copied. Just reading docs and making it work.