🖥️ Build Your Own Operating System
Complete step-by-step guide to creating a simple operating system from scratch. Based on Operating Systems: Three Easy Pieces (OSTEP) and the OSDev Wiki.
🔢 Essential OS Concepts
⚡ CPU Scheduling
💾 Memory Hierarchy
🔌 Virtual Memory
🔄 Context Switch
📚 OS Development Roadmap
⚠️ Development Environment Setup
• Required: Linux or macOS (Windows needs WSL or VM)
• Compiler: Cross-compiler for your target architecture (i686-elf-gcc)
• Emulator: QEMU for testing without real hardware
• Tools: NASM (assembler), GRUB (bootloader), Make
• Backup: Always test in virtual machine first!
1 Bootloader (Assembly)
The bootloader is the first code that runs when your computer starts. It sets up the CPU in protected mode and loads the kernel.
boot.asm - Simple Boot Sector
bits 16 ; 16-bit real mode
org 0x7C00 ; BIOS loads boot sector at 0x7C00
start:
; Disable interrupts
cli
; Set up segments
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; Print boot message
mov si, boot_msg
call print_string
; Load kernel from disk
mov bx, 0x7E00 ; Load kernel after bootloader
mov ah, 0x02 ; Read sectors
mov al, 10 ; Number of sectors to read
mov ch, 0 ; Cylinder 0
mov cl, 2 ; Sector 2 (boot is sector 1)
mov dh, 0 ; Head 0
int 0x13
; Jump to kernel
jmp 0x7E00
; String printing function
print_string:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp print_string
.done:
ret
boot_msg db "Booting MyOS...", 0
; Boot signature
times 510-($-$$) db 0
dw 0xAA55 ; Boot sector magic number
linker.ld - Linker Script
ENTRY(start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 1M; /* Load kernel at 1MB (conventional) */
.text BLOCK(4K) : ALIGN(4K) {
*(.multiboot)
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K) {
*(.rodata)
}
.data BLOCK(4K) : ALIGN(4K) {
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K) {
*(COMMON)
*(.bss)
}
}
Compile with: nasm -f elf32 boot.asm -o boot.o
2 Minimal Kernel (C)
A simple kernel that prints to screen and halts. This is your OS's core.
kernel.c - Basic Kernel
#include <stdint.h>
/* VGA text mode buffer */
volatile uint16_t* vga_buffer = (uint16_t*)0xB8000;
const int VGA_WIDTH = 80;
const int VGA_HEIGHT = 25;
/* Current cursor position */
int cursor_x = 0;
int cursor_y = 0;
/* Clear screen */
void clear_screen(uint8_t color) {
uint16_t blank = (color << 8) | ' ';
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
vga_buffer[i] = blank;
}
cursor_x = 0;
cursor_y = 0;
}
/* Print character */
void putchar(char c, uint8_t color) {
if (c == '\n') {
cursor_x = 0;
cursor_y++;
} else if (c == '\r') {
cursor_x = 0;
} else {
int index = cursor_y * VGA_WIDTH + cursor_x;
vga_buffer[index] = (color << 8) | c;
cursor_x++;
if (cursor_x >= VGA_WIDTH) {
cursor_x = 0;
cursor_y++;
}
}
/* Scroll if needed */
if (cursor_y >= VGA_HEIGHT) {
for (int i = 0; i < VGA_WIDTH * (VGA_HEIGHT - 1); i++) {
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
}
for (int i = VGA_WIDTH * (VGA_HEIGHT - 1); i < VGA_WIDTH * VGA_HEIGHT; i++) {
vga_buffer[i] = (color << 8) | ' ';
}
cursor_y = VGA_HEIGHT - 1;
}
}
/* Print string */
void print(const char* str, uint8_t color) {
for (int i = 0; str[i] != '\0'; i++) {
putchar(str[i], color);
}
}
/* Kernel main function */
void kernel_main(void) {
/* Clear screen with blue background */
clear_screen(0x1F); /* White on blue */
/* Print welcome message */
print("Welcome to MyOS!\n", 0x0F);
print("Kernel loaded successfully.\n\n", 0x0F);
/* System information */
print("System Status:\n", 0x0A);
print("- Running in 32-bit protected mode\n", 0x07);
print("- VGA text mode: 80x25\n", 0x07);
print("- Memory available: TBD\n", 0x07);
/* Hang forever */
while (1) {
__asm__("hlt"); /* Halt CPU */
}
}
kernel_entry.asm - Kernel Entry Point
global start
extern kernel_main
section .text
bits 32
start:
; Set up stack pointer
mov esp, stack_top
; Call kernel main
call kernel_main
; If kernel_main returns, halt
hlt
section .bss
stack_bottom:
resb 16384 ; 16KB stack
stack_top:
Compile with: i686-elf-gcc -c kernel.c -o kernel.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra
3 Build System (Makefile)
Automate compilation and linking with a Makefile.
Makefile - Build Automation
# Cross-compiler prefix CC = i686-elf-gcc AS = nasm LD = i686-elf-ld # Flags CFLAGS = -std=gnu99 -ffreestanding -O2 -Wall -Wextra LDFLAGS = -T linker.ld -ffreestanding -O2 -nostdlib # Output files KERNEL = myos.bin ISO = myos.iso # Object files OBJS = boot.o kernel_entry.o kernel.o all: $(ISO) # Compile bootloader boot.o: boot.asm $(AS) -f elf32 $< -o $@ # Compile kernel entry kernel_entry.o: kernel_entry.asm $(AS) -f elf32 $< -o $@ # Compile kernel C code kernel.o: kernel.c $(CC) $(CFLAGS) -c $< -o $@ # Link kernel myos.bin: $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o $@ # Create bootable ISO $(ISO): myos.bin mkdir -p isodir/boot/grub cp myos.bin isodir/boot/ cp grub.cfg isodir/boot/grub/ grub-mkrescue -o $@ isodir # Run in QEMU run: $(ISO) qemu-system-i386 -cdrom $(ISO) # Clean build files clean: rm -f *.o *.bin *.iso rm -rf isodir .PHONY: all run clean
4 Interrupts & System Calls
Handle hardware interrupts and implement system call interface.
interrupts.c - Interrupt Descriptor Table
#include <stdint.h>
/* IDT Entry */
struct idt_entry {
uint16_t base_low;
uint16_t selector;
uint8_t zero;
uint8_t flags;
uint16_t base_high;
} __attribute__((packed));
/* IDT Pointer */
struct idt_ptr {
uint16_t limit;
uint32_t base;
} __attribute__((packed));
/* IDT */
struct idt_entry idt[256];
struct idt_ptr idtp;
/* External assembly function to load IDT */
extern void idt_load();
/* Set IDT gate */
void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
idt[num].base_low = (base & 0xFFFF);
idt[num].base_high = (base >> 16) & 0xFFFF;
idt[num].selector = sel;
idt[num].zero = 0;
idt[num].flags = flags;
}
/* Initialize IDT */
void init_idt() {
idtp.limit = (sizeof(struct idt_entry) * 256) - 1;
idtp.base = (uint32_t)&idt;
/* Clear IDT */
for (int i = 0; i < 256; i++) {
idt_set_gate(i, 0, 0, 0);
}
/* Load IDT */
idt_load();
}
/* Basic system call handler */
void syscall_handler() {
__asm__ volatile (
"pusha\n"
"call handle_syscall\n"
"popa\n"
"iret\n"
);
}
/* Example system calls */
#define SYS_WRITE 1
#define SYS_EXIT 2
void handle_syscall() {
uint32_t syscall_num, arg1, arg2, arg3;
__asm__ volatile (
"mov %%eax, %0\n"
"mov %%ebx, %1\n"
"mov %%ecx, %2\n"
"mov %%edx, %3\n"
: "=r"(syscall_num), "=r"(arg1), "=r"(arg2), "=r"(arg3)
);
switch(syscall_num) {
case SYS_WRITE:
/* Write to screen */
print((char*)arg1, arg2);
break;
case SYS_EXIT:
/* Exit process */
while(1) __asm__("hlt");
break;
}
}
5 Memory Management
Implement physical and virtual memory management with paging.
memory.c - Physical Memory Manager
#include <stdint.h>
#include <stddef.h>
#define PAGE_SIZE 4096
#define MEMORY_SIZE 0x1000000 /* 16MB for example */
/* Bitmap for tracking free pages */
uint32_t* memory_bitmap;
uint32_t bitmap_size;
/* Initialize memory manager */
void init_memory() {
/* Place bitmap at 1MB (after kernel) */
memory_bitmap = (uint32_t*)0x100000;
/* Calculate bitmap size */
bitmap_size = MEMORY_SIZE / PAGE_SIZE / 32;
if ((MEMORY_SIZE / PAGE_SIZE) % 32) bitmap_size++;
/* Mark all memory as free (0 = free, 1 = used) */
for (uint32_t i = 0; i < bitmap_size; i++) {
memory_bitmap[i] = 0;
}
/* Mark kernel memory as used (first 2MB) */
uint32_t kernel_pages = 0x200000 / PAGE_SIZE;
for (uint32_t i = 0; i < kernel_pages / 32; i++) {
memory_bitmap[i] = 0xFFFFFFFF;
}
}
/* Allocate a page */
void* alloc_page() {
for (uint32_t i = 0; i < bitmap_size; i++) {
if (memory_bitmap[i] != 0xFFFFFFFF) {
/* Find free bit */
for (int j = 0; j < 32; j++) {
if (!(memory_bitmap[i] & (1 << j))) {
/* Mark as used */
memory_bitmap[i] |= (1 << j);
/* Calculate physical address */
uint32_t page_num = i * 32 + j;
return (void*)(page_num * PAGE_SIZE);
}
}
}
}
return NULL; /* Out of memory */
}
/* Free a page */
void free_page(void* addr) {
uint32_t page_num = (uint32_t)addr / PAGE_SIZE;
uint32_t i = page_num / 32;
uint32_t j = page_num % 32;
/* Mark as free */
memory_bitmap[i] &= ~(1 << j);
}
/* Page Directory Entry */
typedef struct {
uint32_t present : 1;
uint32_t rw : 1;
uint32_t user : 1;
uint32_t writethrough : 1;
uint32_t cache : 1;
uint32_t accessed : 1;
uint32_t dirty : 1;
uint32_t page_size : 1;
uint32_t global : 1;
uint32_t available : 3;
uint32_t frame : 20;
} page_dir_entry;
/* Enable paging */
void enable_paging() {
/* Create page directory at 0x200000 */
page_dir_entry* page_dir = (page_dir_entry*)0x200000;
/* Clear page directory */
for (int i = 0; i < 1024; i++) {
page_dir[i].present = 0;
}
/* Map first 4MB identity */
for (uint32_t i = 0; i < 1024; i++) {
page_dir[i].present = 1;
page_dir[i].rw = 1;
page_dir[i].frame = i;
}
/* Load page directory and enable paging */
__asm__ volatile (
"mov %0, %%cr3\n"
"mov %%cr0, %%eax\n"
"or $0x80000000, %%eax\n"
"mov %%eax, %%cr0\n"
:: "r"(page_dir)
);
}
6 Process Scheduling
Implement a simple Round Robin scheduler.
scheduler.c - Simple Scheduler
#include <stdint.h>
#define MAX_PROCESSES 64
#define STACK_SIZE 4096
/* Process Control Block */
typedef struct {
uint32_t pid;
uint32_t esp; /* Stack pointer */
uint32_t eip; /* Instruction pointer */
uint32_t state; /* 0=ready, 1=running, 2=blocked */
uint32_t priority;
uint32_t stack[STACK_SIZE];
} pcb_t;
pcb_t processes[MAX_PROCESSES];
pcb_t* current_process = NULL;
uint32_t next_pid = 1;
/* Initialize scheduler */
void init_scheduler() {
for (int i = 0; i < MAX_PROCESSES; i++) {
processes[i].pid = 0; /* 0 = unused */
}
/* Create idle process */
create_process(NULL); /* Kernel idle */
}
/* Create a new process */
uint32_t create_process(void (*entry_point)()) {
for (int i = 0; i < MAX_PROCESSES; i++) {
if (processes[i].pid == 0) {
processes[i].pid = next_pid++;
processes[i].state = 0; /* Ready */
processes[i].priority = 1;
/* Set up stack */
processes[i].esp = (uint32_t)&processes[i].stack[STACK_SIZE - 1];
processes[i].eip = (uint32_t)entry_point;
/* Push fake context for iret */
uint32_t* stack_top = (uint32_t*)processes[i].esp;
*(--stack_top) = 0x202; /* EFLAGS */
*(--stack_top) = 0x08; /* CS */
*(--stack_top) = processes[i].eip; /* EIP */
*(--stack_top) = 0; /* EAX */
*(--stack_top) = 0; /* ECX */
*(--stack_top) = 0; /* EDX */
*(--stack_top) = 0; /* EBX */
*(--stack_top) = (uint32_t)stack_top; /* ESP */
*(--stack_top) = 0; /* EBP */
*(--stack_top) = 0; /* ESI */
*(--stack_top) = 0; /* EDI */
processes[i].esp = (uint32_t)stack_top;
return processes[i].pid;
}
}
return 0; /* No free slots */
}
/* Round Robin Scheduler */
void schedule() {
static uint32_t current_pid = 0;
/* Save current process context */
if (current_process) {
__asm__ volatile (
"mov %%esp, %0\n"
"mov %%eip, %1\n"
: "=r"(current_process->esp), "=r"(current_process->eip)
);
}
/* Find next ready process */
for (int i = 0; i < MAX_PROCESSES; i++) {
uint32_t idx = (current_pid + i + 1) % MAX_PROCESSES;
if (processes[idx].pid != 0 && processes[idx].state == 0) {
current_pid = idx;
current_process = &processes[idx];
current_process->state = 1; /* Running */
/* Switch to process */
__asm__ volatile (
"mov %0, %%esp\n"
"ret\n"
:: "r"(current_process->esp)
);
break;
}
}
/* If no process found, idle */
__asm__("hlt");
}
/* Example process */
void process1() {
while(1) {
print("Process 1 running\n", 0x07);
for (int i = 0; i < 1000000; i++); /* Busy wait */
schedule(); /* Yield CPU */
}
}
void process2() {
while(1) {
print("Process 2 running\n", 0x07);
for (int i = 0; i < 1000000; i++);
schedule();
}
}
7 Simple File System
Implement a basic FAT-like file system.
filesystem.c - Basic FAT Implementation
#include <stdint.h>
#include <stddef.h>
#define BLOCK_SIZE 512
#define FAT_ENTRIES 128
#define ROOT_DIR_ENTRIES 16
/* File Allocation Table Entry */
typedef struct {
uint8_t used : 1;
uint8_t next_block : 7; /* 0-127 */
} fat_entry_t;
/* Directory Entry */
typedef struct {
char name[11]; /* 8.3 format */
uint8_t attributes; /* Unused in this simple version */
uint32_t size; /* File size in bytes */
uint8_t first_block;/* First FAT block */
} dir_entry_t;
/* File System */
fat_entry_t fat[FAT_ENTRIES];
dir_entry_t root_dir[ROOT_DIR_ENTRIES];
uint8_t* disk_blocks; /* Simulated disk */
/* Initialize file system */
void init_filesystem() {
/* Clear FAT */
for (int i = 0; i < FAT_ENTRIES; i++) {
fat[i].used = 0;
fat[i].next_block = 0;
}
/* Clear root directory */
for (int i = 0; i < ROOT_DIR_ENTRIES; i++) {
root_dir[i].name[0] = '\0';
root_dir[i].first_block = 0;
root_dir[i].size = 0;
}
/* "Allocate" disk blocks */
disk_blocks = (uint8_t*)0x300000; /* 3MB mark */
}
/* Find free FAT entry */
int find_free_fat() {
for (int i = 1; i < FAT_ENTRIES; i++) { /* Skip 0 */
if (!fat[i].used) {
return i;
}
}
return -1; /* No space */
}
/* Create file */
int create_file(const char* name, uint32_t size) {
/* Find free directory entry */
int dir_index = -1;
for (int i = 0; i < ROOT_DIR_ENTRIES; i++) {
if (root_dir[i].name[0] == '\0') {
dir_index = i;
break;
}
}
if (dir_index == -1) return -1;
/* Find FAT blocks */
int blocks_needed = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
if (blocks_needed == 0) blocks_needed = 1;
int first_block = find_free_fat();
if (first_block == -1) return -1;
int current_block = first_block;
for (int i = 0; i < blocks_needed; i++) {
fat[current_block].used = 1;
if (i == blocks_needed - 1) {
fat[current_block].next_block = 0; /* End of chain */
} else {
int next_block = find_free_fat();
if (next_block == -1) {
/* Clean up allocated blocks */
while (current_block != 0) {
int temp = fat[current_block].next_block;
fat[current_block].used = 0;
current_block = temp;
}
return -1;
}
fat[current_block].next_block = next_block;
current_block = next_block;
}
}
/* Fill directory entry */
int name_len = 0;
while (name[name_len] && name_len < 10) {
root_dir[dir_index].name[name_len] = name[name_len];
name_len++;
}
root_dir[dir_index].name[name_len] = '\0';
root_dir[dir_index].size = size;
root_dir[dir_index].first_block = first_block;
return dir_index;
}
/* Read file */
int read_file(const char* name, void* buffer, uint32_t size) {
/* Find file */
int dir_index = -1;
for (int i = 0; i < ROOT_DIR_ENTRIES; i++) {
int match = 1;
for (int j = 0; j < 11; j++) {
if (root_dir[i].name[j] != name[j]) {
match = 0;
break;
}
if (name[j] == '\0') break;
}
if (match) {
dir_index = i;
break;
}
}
if (dir_index == -1) return -1;
/* Read file */
uint32_t bytes_read = 0;
int current_block = root_dir[dir_index].first_block;
uint8_t* dest = (uint8_t*)buffer;
while (current_block != 0 && bytes_read < size) {
uint8_t* src = disk_blocks + (current_block * BLOCK_SIZE);
uint32_t to_copy = BLOCK_SIZE;
if (to_copy > size - bytes_read) {
to_copy = size - bytes_read;
}
/* Copy block */
for (uint32_t i = 0; i < to_copy; i++) {
dest[bytes_read + i] = src[i];
}
bytes_read += to_copy;
current_block = fat[current_block].next_block;
}
return bytes_read;
}
/* List files */
void list_files() {
print("Files:\n", 0x0F);
for (int i = 0; i < ROOT_DIR_ENTRIES; i++) {
if (root_dir[i].name[0] != '\0') {
print("- ", 0x07);
print(root_dir[i].name, 0x07);
print("\n", 0x07);
}
}
}
8 Complete OS Integration
Final kernel with all components integrated.
main.c - Complete Kernel
#include <stdint.h>
/* Function prototypes */
void clear_screen(uint8_t color);
void print(const char* str, uint8_t color);
void init_idt();
void init_memory();
void init_scheduler();
void init_filesystem();
void enable_paging();
void schedule();
/* Example processes */
void process1();
void process2();
/* Kernel main - everything starts here */
void kernel_main(void) {
/* Basic setup */
clear_screen(0x1F);
print("MyOS Booting...\n\n", 0x0F);
/* Initialize subsystems */
print("Initializing IDT... ", 0x07);
init_idt();
print("[OK]\n", 0x0A);
print("Initializing Memory... ", 0x07);
init_memory();
print("[OK]\n", 0x0A);
print("Enabling Paging... ", 0x07);
enable_paging();
print("[OK]\n", 0x0A);
print("Initializing Scheduler... ", 0x07);
init_scheduler();
print("[OK]\n", 0x0A);
print("Initializing Filesystem... ", 0x07);
init_filesystem();
print("[OK]\n", 0x0A);
/* Create some files */
create_file("README.TXT", 1024);
create_file("CONFIG.CFG", 512);
/* Create processes */
print("\nCreating processes...\n", 0x0F);
create_process(process1);
create_process(process2);
/* Show system info */
print("\n=== MyOS Ready ===\n", 0x0E);
list_files();
print("\nType 'help' for commands\n", 0x07);
/* Start scheduler */
schedule();
}
/* Simple shell */
void shell() {
print("\nmyos> ", 0x0F);
/* Very simple command parser */
char cmd[32];
/* ... command parsing logic ... */
if (/* cmd == "help" */) {
print("Commands: ls, ps, mem, help, exit\n", 0x07);
} else if (/* cmd == "ls" */) {
list_files();
} else if (/* cmd == "ps" */) {
print("PID 1: Process 1\n", 0x07);
print("PID 2: Process 2\n", 0x07);
} else if (/* cmd == "mem" */) {
print("Memory: 16MB total\n", 0x07);
} else if (/* cmd == "exit" */) {
print("Goodbye!\n", 0x07);
while(1) __asm__("hlt");
} else {
print("Unknown command\n", 0x0C);
}
}
🚀 Building and Testing Your OS
Complete Build Commands
# 1. Install cross-compiler (Ubuntu/Debian) sudo apt-get install build-essential nasm grub-pc-bin xorriso wget https://github.com/lordmilko/i686-elf-tools/releases/download/7.1.0/i686-elf-tools-linux.zip unzip i686-elf-tools-linux.zip export PATH=$PATH:~/i686-elf-tools/bin # 2. Build OS make # 3. Run in QEMU make run # 4. Or test in VirtualBox # Convert ISO to VDI: VBoxManage convertdd myos.iso myos.vdi --format VDI # Create new VM with this VDI as boot disk
📚 Resources & Next Steps
- Operating Systems: Three Easy Pieces - Free online textbook
- OSDev Wiki - Comprehensive OS development wiki
- Intel Software Developer Manuals - CPU architecture reference
- Linux Kernel Source - Study real-world implementation
- xv6 - Simple Unix-like teaching OS (MIT)
- Bare Bones OS Tutorial - First kernel tutorial on OSDev
💡 Project Ideas to Extend Your OS
1. Simple Shell
Implement a command-line interface with basic commands (ls, cat, echo)
2. GUI Framework
Add VESA graphics support and basic windowing system
3. Network Stack
Implement TCP/IP networking with Ethernet driver
4. Multi-core Support
Add SMP support for multiple CPUs
⚠️ Important Notes
• Start Simple: Get basic boot → kernel → print working first
• Use QEMU: Never test new OS code on real hardware initially
• Debugging: Use QEMU's -s -S flags for GDB debugging
• Backup Often: OS development can corrupt disks if bugs escape VM
• Reference Code: Study xv6, Minix, or Linux 0.01 for inspiration
• Community: Join OSDev.org forums for help