Better docs and structure

This commit is contained in:
2026-02-26 21:33:16 +00:00
parent d449150169
commit 13a281fa4f
18 changed files with 892 additions and 387 deletions

325
README.md
View File

@@ -4,24 +4,30 @@ A minimal bootable UEFI operating system written in C that demonstrates UEFI boo
## 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 031) 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
- **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 031) 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
## Requirements
---
- GCC compiler
- GNU-EFI library
## Getting Started
### Requirements
- GCC cross-compiler (or native x86-64 GCC)
- GNU-EFI library and headers
- QEMU with OVMF firmware
- Make
- GNU Make
- `xorriso` and `mtools` (for ISO builds only)
## Installation
### Installation
### On Debian/Ubuntu:
<details>
<summary><strong>Debian / Ubuntu</strong></summary>
```bash
make install-deps
@@ -33,57 +39,78 @@ Or manually:
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
```
### On Arch Linux:
</details>
<details>
<summary><strong>Arch Linux</strong></summary>
```bash
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
```
### On Fedora/RHEL:
</details>
<details>
<summary><strong>Fedora / RHEL</strong></summary>
```bash
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
```
## Building
</details>
Build the UEFI loader and kernel:
### Building
```bash
make
make # build both UEFI loader and kernel
```
This creates `build/EFI/BOOT/BOOTX64.EFI` (UEFI loader) and `build/kernel.elf` (kernel binary).
This produces two binaries:
## Running
| Output | Description |
|--------|-------------|
| `build/EFI/BOOT/BOOTX64.EFI` | UEFI loader (PE32+) |
| `build/kernel.elf` | Kernel binary (ELF64) |
Test with QEMU (no graphics mode):
### Running
```bash
make run
```
To build and run from an ISO image:
```bash
make runiso
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).
### QEMU Controls:
- **Exit QEMU**: Press `Ctrl+A`, then `X`
- **Shutdown OS**: Type `shutdown` in the OS
| Key / Command | Action |
|---------------|--------|
| `Ctrl+A`, then `X` | Exit QEMU |
| `shutdown` (in shell) | Power off the OS |
## Commands
### Make Targets
Once the OS boots, you can use these commands:
| 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` | Display all available commands |
| `man <command>` | Display detailed help for a command |
| `shutdown` | Shutdown the system |
| `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) |
@@ -108,10 +135,10 @@ Once the OS boots, you can use these commands:
### Testing Memory Management
The `memtest` command runs through four test phases:
The `memtest` command runs four phases:
1. **Heap allocation** — allocates 8 blocks of increasing size (16 to 4096 bytes) via `kmalloc()`
2. **Heap free** — frees all allocated blocks via `kfree()` and verifies coalescing
1. **Heap allocation** — allocates 8 blocks of increasing size (164096 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
@@ -126,7 +153,7 @@ The `tasktest` command:
3. The shell yields to let workers run in round-robin order
4. Displays the task list after completion
You can also manually test with `spawn` and `ps`:
You can also test manually with `spawn` and `ps`:
```
-> spawn myTask
@@ -142,140 +169,154 @@ Created task 'myTask' (PID 1)
Background tasks run whenever the shell yields (which happens automatically while waiting for keyboard input).
## Project Structure
```
.
├── main.c # UEFI loader (reads and loads kernel ELF)
├── kernel.c # Kernel entry point and interactive shell loop
├── kernel.ld # Kernel linker script (base address 0x100000)
├── boot_info.h # Shared boot interface (BootInfo struct)
├── commands.c # Command registry and handler implementations
├── commands.h # Command interface definitions
├── string_utils.c # String utility functions (trim, compare, etc.)
├── string_utils.h # String utility declarations
├── idt.c # IDT setup, exception handlers, PIC EOI
├── idt.h # IDT types (ISRFrame) and init declaration
├── isr.S # ISR stub table (vectors 0255) with common handler
├── memory.c # PMM (bitmap), paging (4-level), heap (first-fit)
├── memory.h # Memory subsystem declarations and constants
├── task.c # Task manager: PCB pool, scheduler, yield/exit
├── task.h # Task types (Task, TaskState) and API
├── context_switch.S # Assembly cooperative context switch (x86-64)
├── Makefile # Build configuration
└── README.md # This file
```
---
## Architecture
### Boot Flow
1. **UEFI Entry**`efi_main()` in `main.c` is called by firmware
2. **GNU-EFI Init** — library initialized with the system table
3. **Kernel Load**`kernel.elf` is read from the EFI partition, ELF64 PT_LOAD segments are mapped into memory
4. **BootInfo Setup** — function pointers for console I/O, shutdown, and page allocation are packaged into a `BootInfo` struct
5. **Kernel Entry**`kmain()` is called with the `BootInfo` pointer
### Kernel Initialization
1. **IDT** — CPU exception vectors (031) are installed; firmware IRQ handlers are preserved
2. **Memory** — PMM allocates a 16 MB page pool via UEFI, bitmap tracks free/used pages; paging reads CR3; heap carves 256 KB from the PMM
3. **Tasks** — scheduler initializes; the running kernel becomes task 0
4. **Shell** — interactive loop with non-blocking input and cooperative yielding
```
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
| Component | Description |
|-----------|-------------|
| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages); supports single and contiguous multi-page allocation |
| **Paging** | Walks/creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT); supports map, unmap, and virtual-to-physical translation |
| **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing; 16-byte alignment; magic number corruption detection |
| 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
| Component | Description |
|-----------|-------------|
| **PCB** | `Task` struct with PID, state, name, saved RSP, stack, entry function, and scheduling metadata |
| 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 selection among `READY` tasks via `task_yield()` |
| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12``r15`) and `RFLAGS`, swaps RSP |
| **Stack** | Each task gets 32 KB (8 pages) allocated from PMM; freed on `task_exit()` |
| **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 with metadata (name, description, usage, handler function)
- `execute_command()` parses input, splits command and arguments, and dispatches to the matching handler
- `man` provides detailed help by looking up command metadata
- New commands are added by writing a handler and appending to the `commands[]` array
Commands are registered in a static array in `commands.c`:
## Building for Real Hardware
```c
static Command commands[] = {
{ L"name", L"description", L"usage", handler_fn },
...
{ NULL, NULL, NULL, NULL } /* sentinel */
};
```
To create a bootable USB drive:
`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 0255) 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 create an EFI partition (FAT32)
3. Mount the EFI partition
4. Copy `build/EFI/BOOT/BOOTX64.EFI` to `/EFI/BOOT/` on the USB drive
5. Copy `build/kernel.elf` to the root of the EFI partition
6. Boot from the USB drive in UEFI mode
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 backup your data before creating bootable media!
> **Warning:** Always back up your data before creating bootable media.
## Cleaning
Remove build artifacts:
```bash
make clean
```
---
## Troubleshooting
### GNU-EFI headers not found
| 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`. |
Make sure GNU-EFI is installed. The headers are typically in `/usr/include/efi`.
If installed in a different location, update the `EFI_INC` variable in the Makefile.
### OVMF firmware not found
OVMF files might be in different locations depending on your distribution:
- `/usr/share/OVMF/`
- `/usr/share/qemu/`
- `/usr/share/edk2-ovmf/`
Update the paths in the Makefile if needed.
### QEMU crashes or fails to start
Ensure you have QEMU with x86_64 support and OVMF firmware installed.
---
## Technical Details
- **Architecture**: x86_64
- **Firmware Interface**: UEFI 2.x
- **Development Library**: GNU-EFI
- **Loader Format**: PE32+ (Portable Executable for EFI)
- **Kernel Format**: ELF64
- **Boot Protocol**: UEFI Boot Services
- **Memory Model**: Identity-mapped (UEFI), 4-level paging (kernel)
- **Scheduling**: Cooperative round-robin multitasking
## License
This is a minimal educational example. Feel free to use and modify as needed.
## Adding New Commands
1. Open `commands.c`
2. Add a forward declaration: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args);`
3. Add an entry to the `commands[]` array (before the sentinel)
4. Implement the handler function
5. Rebuild with `make`
| 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.