323 lines
10 KiB
Markdown
323 lines
10 KiB
Markdown
# 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
|
||
|
||
<details>
|
||
<summary><strong>Debian / Ubuntu</strong></summary>
|
||
|
||
```bash
|
||
make install-deps
|
||
```
|
||
|
||
Or manually:
|
||
|
||
```bash
|
||
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Arch Linux</strong></summary>
|
||
|
||
```bash
|
||
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
|
||
```
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Fedora / RHEL</strong></summary>
|
||
|
||
```bash
|
||
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
|
||
```
|
||
|
||
</details>
|
||
|
||
### 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 <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:
|
||
|
||
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` → `<mount>/EFI/BOOT/BOOTX64.EFI`
|
||
5. Copy `build/kernel.elf` → `<mount>/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.
|