BoringOS & OS Development

Build your own operating system: bootloaders and BoringKernel

Begin kernel experiments in QEMU

Kernel builds and the emulator run locally; the repository and bootloader dependencies need downloads. QEMU network options determine possible connections. This exercise writes no image to a physical drive.

In this article
  1. Firmware, bootloader and kernel have distinct roles
  2. Why a kernel differs from a normal C program
  3. Read the real BoringKernel entry point
  4. Separate physical and virtual memory
  5. Understand a small halting example
  6. Explore the project in an emulator
  7. Follow the path to a native desktop
  8. References and next steps

An operating system does not begin with a taskbar. First, code must reliably take control from a bootloader, understand information about the machine and manage memory. BoringOS demonstrates this path through its experimental independent x86_64 system and its own BoringKernel.

The BoringOS project page presents the larger current state. This article explains the foundations for readers who have never written a kernel. BoringOS uses mainly C with small assembly components and is not a Linux distribution. Our starting point is an emulator, without modifying a physical boot drive.

Firmware, bootloader and kernel have distinct roles

Firmware starts the machine and selects a boot path. A bootloader loads the kernel and provides defined information. BoringOS uses Limine for this step. The kernel then takes responsibility for its own system work.

Layer Role in the simplified introduction
Firmware Hardware startup and boot selection
Limine Load the kernel and deliver boot information
BoringKernel Memory, exceptions, processes and system functions
Userspace Native services and applications

Limine is an external project rather than a self-developed part of BoringKernel. Keeping this boundary clear shows what the kernel owns and which assumptions still come from the bootloader.

Why a kernel differs from a normal C program

An ordinary application starts inside an existing operating system and can use its processes, files and libraries. An early kernel must supply that environment first.

Freestanding describes this difference in the C build. A compiled C program is not automatically a bootable kernel: entry point, linker layout, architecture and boot protocol must agree. A normal printf is not simply available during early startup.

Serial output is therefore a useful first observation channel. It can report progress before a full graphics stack or filesystem exists. An emulator makes that output conveniently visible on the host.

Read the real BoringKernel entry point

kernel/core/entry.c contains boring_kernel_entry. A short real excerpt is:

serial_init();
serial_write_string("BoringOS booting...\n");
serial_write_string("BoringKernel 0.0.62-dev\n");

These lines belong inside the existing entry function; they are not a complete bootable file. Queries and checks for several system areas follow. The current implementation is substantially beyond a “Hello World” kernel.

Order matters when reading it: observation needs an output channel before memory-initialization failures can be reported clearly. The code checks results and halts deliberately after fundamental failures rather than continue with invalid prerequisites.

Separate physical and virtual memory

The physical memory manager, or PMM, tracks usable RAM regions and frames. It must distinguish available memory from areas reserved for the kernel or hardware.

The virtual memory manager, VMM, handles address mappings through page tables. A program address is consequently not necessarily the same as a physical RAM address. Bootloader information includes the memory map and details of existing mappings.

BoringOS tests these foundations deliberately: allocated frames should be aligned, unique and correctly accounted for when released. Passing one test establishes a particular condition, rather than the correctness of an entire desktop.

Older repository documents describe earlier milestones. For current work, the precise revision, present entry code and documented acceptance checkpoints matter more than an isolated old bootstrap description.

Understand a small halting example

This original C example illustrates only a controlled waiting loop in an x86_64 kernel context:

void tutorial_halt(void) {
    for (;;) {
        __asm__ volatile ("cli; hlt");
    }
}

cli disables maskable interrupts; hlt halts the CPU until an appropriate event. The loop does not return into an unknown calling environment. These privileged instructions belong in kernel context. Do not execute the example as an ordinary Linux application.

It intentionally omits the boot protocol, linker script, output and device initialization. For an actual boot, use the complete project rather than infer bootability from this small excerpt.

Explore the project in an emulator

Prerequisites include a suitable x86_64 GCC/binutils toolchain, Make, QEMU, xorriso and the project's documented download tools. Check the current build instructions and use a new checkout:

git clone https://github.com/dennishilk/BoringOS.git

Enter it:

cd BoringOS

Build the standard configuration:

make

Start the standard emulator test:

make run

The current Makefile uses QEMU with serial output and no graphical window. This is a bootstrap/test route, rather than automatically the full graphical BoringWM demo. The observed state also depends on test mode. End this foreground QEMU test with Ctrl + C; its stdio connection enables terminal signals by default.

Follow the path to a native desktop

A desktop additionally needs a scheduler, Ring 3 processes, native system calls, a VFS and BoringFS, plus display and input services. BoringWM, terminal, editor and file management build on those foundations.

The project documents physical tests on Cthulhu, including USB/HID and a 1920×1080 desktop. Such claims belong to particular accepted revisions. A later optimization passing automated tests is not automatically the same physically verified version.

QEMU supports reproducible development but does not test every USB topology or physical graphics device. It remains the appropriate first setting for beginners: observe startup, read one function and follow a single checked prerequisite.

References and next steps

Sources checked on 8 October 2026. For a closer look at display addressing, continue with the framebuffer article.