# 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 - `xorriso` and `mtools` (for ISO builds only) ### Installation
Debian / Ubuntu ```bash make install-deps ``` Or manually: ```bash sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools ```
Arch Linux ```bash sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools ```
Fedora / RHEL ```bash sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools ```
### Building ```bash 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 ```bash 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 ` | 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: 1. **Heap allocation** — allocates 8 blocks of increasing size (16–4096 bytes) via `kmalloc()` 2. **Heap free** — frees all blocks via `kfree()` and verifies coalescing 3. **PMM single page** — allocates and frees a single 4 KB page 4. **PMM multi-page** — allocates and frees 4 contiguous pages Each phase reports addresses, success/failure, and usage statistics. ### Testing Task Scheduling The `tasktest` command: 1. Spawns 3 worker tasks (`worker-A`, `worker-B`, `worker-C`) 2. Each worker prints 3 progress steps, yielding between each 3. The shell yields to let workers run in round-robin order 4. 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`: ```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 1. Open `commands.c` 2. Add a forward declaration: ```c static void cmd_foo(BootInfo *Boot, CHAR16 *Args); ``` 3. Append an entry to the `commands[]` array (before the `NULL` sentinel): ```c { L"foo", L"Short description", L"Usage: foo [args]\n\r Details.", cmd_foo }, ``` 4. Implement `cmd_foo` 5. Rebuild with `make` ### Building for Real Hardware 1. Build the project: `make` 2. Format a USB drive with GPT and a FAT32 EFI System Partition 3. Mount the ESP 4. Copy `build/EFI/BOOT/BOOTX64.EFI` → `/EFI/BOOT/BOOTX64.EFI` 5. Copy `build/kernel.elf` → `/kernel.elf` 6. 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 - [UEFI Specification](https://uefi.org/specifications) - [GNU-EFI Documentation](https://sourceforge.net/projects/gnu-efi/) - [OSDev Wiki](https://wiki.osdev.org/) ## License This is a minimal educational example. Feel free to use and modify as needed.