Search Knowledge

© 2026 LIBREUNI PROJECT

Operating Systems Internals / Laboratory: OS Development

The 'Hello World' Kernel: Writing the Code

The ‘Hello World’ Kernel: Writing the Code

Building a custom kernel requires programming directly against physical hardware memory and registers without relying on standard user-space libraries (such as stdio.h or stdlib.h), as no underlying operating system exists yet to support them.

This guide details how to create a minimal, Multiboot-compliant kernel using x86 assembly for system bootstrapping and C for the high-level execution entry point.

1. The Assembly Entry Point (boot.s)

To allow a multiboot-compliant bootloader (such as GRUB) to load and execute the kernel, the final executable must contain a specific, magic header within its first 8 Kilobytes. This header contains configuration flags and a verification checksum.

; Declare constants for the Multiboot header.
MAGIC    equ  0x1BADB002
FLAGS    equ  0x00
CHECKSUM equ -(MAGIC + FLAGS)

; The Multiboot header must be within the first 8KB of the file.
section .multiboot
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

section .text
extern kmain
global _start

_start:
    ; Set up a stack pointer (ESP) pointing to the allocated memory area
    mov esp, stack_top

    ; Call the C kernel entry point
    call kmain

    ; Disable interrupts and halt the CPU if kmain returns
    cli
.hang:
    hlt
    jmp .hang

section .bss
align 16
stack_bottom:
    resb 16384 ; Allocate 16 KB for the system stack
stack_top:

2. The C Kernel (kernel.c)

Without a standard graphics driver or system call interface, the kernel writes text directly to the screen via memory-mapped I/O (MMIO). On x86 architectures, the physical memory address 0xB8000 is mapped to the standard VGA text mode buffer.

In text mode, the screen is formatted as a grid of 80 columns by 25 rows. Each character slot consists of two consecutive bytes:

  1. ASCII Character Byte: The character code to display.
  2. Color Attribute Byte: The foreground and background colors (4 bits each).
// The VGA text buffer starts at physical address 0xB8000.
volatile char* vga_buffer = (volatile char*)0xB8000;

void kmain(void) {
    const char* str = "Hello, OS World!";
    int i = 0;
    int j = 0;

    // Clear the screen (filling with black background and space characters)
    for (i = 0; i < 80 * 25 * 2; i += 2) {
        vga_buffer[i] = ' ';
        vga_buffer[i+1] = 0x07; // Light grey on black
    }

    // Write "Hello, OS World!" to the top-left corner
    i = 0;
    while (str[i] != '\0') {
        vga_buffer[j] = str[i];     // Write the ASCII character byte
        vga_buffer[j+1] = 0x0F;     // Color attribute: Intense White on Black
        i++;
        j += 2;
    }
}

Key Concepts Explained

The minimal kernel relies on specific low-level C programming constructs and physical memory allocations.

Volatile Keyword

In kernel.c, the volatile type qualifier instructs the compiler that the memory at 0xB8000 is subject to change or has significance beyond the local application scope. Without volatile, an optimizing compiler might analyze the write-only assignments and eliminate them, assuming they are redundant because the program never reads from those addresses.

For example, this snippet demonstrates how compiler optimization handles volatile vs. non-volatile pointer writes:

/* Volatile vs Non-Volatile compiler optimizations example */
int *non_volatile_ptr = (int *)0x1000;
volatile int *volatile_ptr = (volatile int *)0x1000;

// Without volatile, the compiler may optimize this loop to only perform the last write
*non_volatile_ptr = 1;
*non_volatile_ptr = 2; // Only this write is kept in the output assembly!

// With volatile, the compiler is forced to output assembly instructions for both writes
*volatile_ptr = 1; // Kept (essential for hardware registers)
*volatile_ptr = 2; // Kept

The Stack

C function execution models rely on a stack frame structure to pass parameters, store local variables, and save instruction return pointers. Because x86 hardware does not establish a stack pointer dynamically upon bootloader handover, the assembly entry code (boot.s) must manually allocate stack memory within the .bss section and load the address into the CPU’s stack pointer register (ESP) before calling the C function kmain.

Exercise: Hardware Address

For example, define the VGA buffer address constant in your C program to map the video memory:

#define VGA_ADDRESS 0xB8000

Identify the starting byte memory address where video text data must be written in the exercise below:

VGA Memory Mapping

/* The base address for VGA text memory */\n0x800

The subsequent sections detail how to link these compiled assembly and C object modules together to form a bootable kernel binary.

References & Further Reading

To practice or explore further, see the official specifications below for an example of bare-metal x86 development reference: