10 KiB
Simple UEFI Operating System
A minimal bootable UEFI operating system written in C that demonstrates UEFI boot loading, memory management, interrupt handling, and cooperative multitasking.
Features
- UEFI Boot — boots directly on UEFI firmware via a PE32+ loader
- ELF64 Kernel Loader — reads and maps a standalone kernel from the EFI partition
- Console I/O — interactive keyboard input and screen output
- Interrupt Handling — IDT setup with CPU exception handlers (vectors 0–31) and hardware IRQ dispatch
- Memory Management — bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing
- Cooperative Multitasking — process control blocks, assembly context switching, and a round-robin task scheduler
- Interactive Shell — command registry with
help,man, built-in commands, and test utilities
Getting Started
Requirements
- GCC cross-compiler (or native x86-64 GCC)
- GNU-EFI library and headers
- QEMU with OVMF firmware
- GNU Make
xorrisoandmtools(for ISO builds only)
Installation
Debian / Ubuntu
make install-deps
Or manually:
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
Arch Linux
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
Fedora / RHEL
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
Building
make # build both UEFI loader and kernel
This produces two binaries:
| Output | Description |
|---|---|
build/EFI/BOOT/BOOTX64.EFI |
UEFI loader (PE32+) |
build/kernel.elf |
Kernel binary (ELF64) |
Running
make run # build and run with QEMU (FAT directory, nographic)
make runiso # build, create ISO, and run with QEMU
The loader expects kernel.elf at the root of the EFI partition (next to the EFI/ directory).
| Key / Command | Action |
|---|---|
Ctrl+A, then X |
Exit QEMU |
shutdown (in shell) |
Power off the OS |
Make Targets
| Target | Description |
|---|---|
all |
Build the UEFI loader and kernel (default) |
run |
Build and run with QEMU |
iso |
Create a bootable ISO image |
runiso |
Build, create ISO, and run in QEMU |
clean |
Remove all build artifacts |
install-deps |
Install required packages (Debian/Ubuntu) |
help |
Print target summary |
Usage
Shell Commands
Once the OS boots, an interactive prompt (->) is displayed. The following commands are available:
| Command | Description |
|---|---|
help |
List all available commands |
man <command> |
Show detailed help for a command |
shutdown |
Shut down the system |
clear |
Clear the screen |
about |
Display system information |
mem |
Display memory statistics (PMM, heap, paging) |
ps |
List all active tasks with PID, state, and name |
spawn [name] |
Create a demo background task |
memtest |
Run memory allocation/deallocation tests |
tasktest |
Spawn multiple concurrent tasks to test the scheduler |
Example Session
-> help
-> about
-> mem
-> memtest
-> spawn worker1
-> spawn worker2
-> ps
-> tasktest
-> shutdown
Testing Memory Management
The memtest command runs four phases:
- Heap allocation — allocates 8 blocks of increasing size (16–4096 bytes) via
kmalloc() - Heap free — frees all blocks via
kfree()and verifies coalescing - PMM single page — allocates and frees a single 4 KB page
- PMM multi-page — allocates and frees 4 contiguous pages
Each phase reports addresses, success/failure, and usage statistics.
Testing Task Scheduling
The tasktest command:
- Spawns 3 worker tasks (
worker-A,worker-B,worker-C) - Each worker prints 3 progress steps, yielding between each
- The shell yields to let workers run in round-robin order
- Displays the task list after completion
You can also test manually with spawn and ps:
-> spawn myTask
Created task 'myTask' (PID 1)
-> ps
PID STATE SWITCHES NAME
--- ---------- -------- ----
0 RUNNING 5 kernel
1 READY 0 myTask
Active tasks: 2 / 32
Background tasks run whenever the shell yields (which happens automatically while waiting for keyboard input).
Architecture
Boot Flow
UEFI Firmware
└─ efi_main() [main.c] PE32+ UEFI application
├─ InitializeLib() GNU-EFI setup
├─ read_file_to_buffer() read kernel.elf from ESP
├─ load_elf_kernel() parse ELF64, map PT_LOAD segments
├─ populate BootInfo wrap UEFI services as fn pointers
└─ kmain(&Boot) [kernel.c] jump to kernel
├─ idt_init() install exception handlers
├─ memory_init() PMM → paging → heap
├─ task_init() scheduler + task 0
└─ shell loop read-eval-print with cooperative yield
Memory Management
| Layer | Description |
|---|---|
| PMM | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages). Supports single and contiguous multi-page allocation. |
| Paging | Walks and creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT). Supports map, unmap, and virtual-to-physical translation for 4 KB, 2 MB, and 1 GB page sizes. |
| Heap | First-fit free-list allocator with block splitting and bidirectional coalescing. 16-byte alignment. Magic-number corruption detection. |
Task System
| Aspect | Detail |
|---|---|
| PCB | Task struct: PID, state, name, saved RSP, stack base, entry function, scheduling metadata |
| States | FREE → READY → RUNNING → TERMINATED → FREE |
| Scheduler | Round-robin among READY tasks via task_yield() |
| Context Switch | Assembly routine saves/restores callee-saved registers (rbp, rbx, r12–r15) and RFLAGS, then swaps RSP |
| Stack | 32 KB (8 pages) per task, allocated from PMM, freed on task_exit() |
| Limits | Up to 32 concurrent tasks; cooperative (voluntary yield) only |
Command System
Commands are registered in a static array in commands.c:
static Command commands[] = {
{ L"name", L"description", L"usage", handler_fn },
...
{ NULL, NULL, NULL, NULL } /* sentinel */
};
execute_command() splits user input into command + arguments and dispatches to the matching handler. man provides detailed help by looking up command metadata.
Project Structure
.
├── main.c # UEFI loader — reads kernel.elf, sets up BootInfo
├── kernel.c # Kernel entry point (kmain) and interactive shell
├── kernel.ld # Linker script — kernel loaded at 0x100000
├── boot_info.h # BootInfo struct shared between loader and kernel
│
├── commands.c # Command registry and all handler implementations
├── commands.h # Command type and dispatch API
├── string_utils.c # CHAR16 string helpers (trim, compare)
├── string_utils.h # String utility declarations
│
├── idt.c # IDT setup, exception handlers, PIC EOI
├── idt.h # ISRFrame layout and idt_init() declaration
├── isr.S # ISR stub table (vectors 0–255) and common handler
│
├── memory.c # PMM (bitmap), paging (4-level), heap (first-fit)
├── memory.h # Memory subsystem constants, types, and API
│
├── task.c # PCB pool, round-robin scheduler, yield/exit
├── task.h # Task types (Task, TaskState) and task API
├── context_switch.S # Cooperative context switch (x86-64 assembly)
│
├── Makefile # Build system
├── README.md # This file
└── build/ # Build output (generated)
├── EFI/BOOT/BOOTX64.EFI
├── kernel.elf
└── image/ # ISO staging area
Development
Adding a New Command
- Open
commands.c - Add a forward declaration:
static void cmd_foo(BootInfo *Boot, CHAR16 *Args); - Append an entry to the
commands[]array (before theNULLsentinel):{ L"foo", L"Short description", L"Usage: foo [args]\n\r Details.", cmd_foo }, - Implement
cmd_foo - Rebuild with
make
Building for Real Hardware
- Build the project:
make - Format a USB drive with GPT and a FAT32 EFI System Partition
- Mount the ESP
- Copy
build/EFI/BOOT/BOOTX64.EFI→<mount>/EFI/BOOT/BOOTX64.EFI - Copy
build/kernel.elf→<mount>/kernel.elf - Boot from the USB in UEFI mode
Warning: Always back up your data before creating bootable media.
Troubleshooting
| Problem | Solution |
|---|---|
| GNU-EFI headers not found | Install GNU-EFI. Headers are typically at /usr/include/efi. Update EFI_INC in the Makefile if installed elsewhere. |
| OVMF firmware not found | Check /usr/share/OVMF/, /usr/share/qemu/, or /usr/share/edk2-ovmf/. The Makefile auto-detects common paths. |
| QEMU crashes or won't start | Ensure qemu-system-x86_64 and OVMF are installed. Verify with qemu-system-x86_64 --version. |
Technical Details
| Property | Value |
|---|---|
| Architecture | x86-64 (long mode) |
| Firmware Interface | UEFI 2.x |
| Development Library | GNU-EFI |
| Loader Format | PE32+ (EFI application) |
| Kernel Format | ELF64 (static, loaded at 0x100000) |
| Boot Protocol | UEFI Boot Services |
| Memory Model | Identity-mapped (UEFI), 4-level paging (kernel) |
| Scheduling | Cooperative round-robin multitasking |
Resources
License
This is a minimal educational example. Feel free to use and modify as needed.