2026-02-26 17:37:57 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00
2026-02-26 21:33:16 +00:00

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 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

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
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:

  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

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 FREEREADYRUNNINGTERMINATEDFREE
Scheduler Round-robin among READY tasks via task_yield()
Context Switch Assembly routine saves/restores callee-saved registers (rbp, rbx, r12r15) 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 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:
    static void cmd_foo(BootInfo *Boot, CHAR16 *Args);
    
  3. Append an entry to the commands[] array (before the NULL sentinel):
    { 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

License

This is a minimal educational example. Feel free to use and modify as needed.

Description
No description provided
Readme 257 KiB
Languages
C 82%
Assembly 10%
Makefile 8%