Building Your Own a Simple Operating System from Scratch
Have you ever wondered how operating systems work under the hood? In this comprehensive guide, we'll walk you through the process of creating a simple operating system from scratch. By the end of this tutorial, you'll have a basic OS that can boot, load a kernel, display "Hello, World!", and run a simple shell.
Introduction to Operating Systems
An operating system (OS) is a crucial piece of software that manages computer hardware and provides services for computer programs. It acts as an intermediary between computer hardware and the user, handling tasks such as:
- Process management
- Memory management
- File system management
- Device management
- User interface provision
The core components of a basic operating system include:
- Bootloader: A small program that initializes the system and loads the kernel.
- Kernel: The central component that manages system resources and provides essential services.
- Shell: A user interface for interacting with the system, typically through commands.
In this tutorial, we'll create simplified versions of these components to build our own basic OS.
Prerequisites and Tools
Before we begin, ensure you have the following tools installed on your development machine:
- QEMU: An open-source machine emulator and virtualizer
- GCC: The GNU Compiler Collection
- NASM: The Netwide Assembler
- Make: A build automation tool
You can install these on most Linux distributions using the package manager. For example, on Ubuntu:
sudo apt-get update sudo apt-get install qemu gcc nasm make
For Windows users, consider using Windows Subsystem for Linux (WSL) or a virtual machine running Linux.
Setting Up the Development Environment
Create a new directory for your OS project:
mkdir my_os cd my_os
Create subdirectories for different components:
mkdir boot kernel shell
Create a Makefile in the root directory to automate the build process:
# Makefile ASM=nasm CC=gcc LD=ld ASMFLAGS=-f elf32 CFLAGS=-m32 -c -ffreestanding LDFLAGS=-m elf_i386 -Ttext 0x1000 --oformat binary all: os.img os.img: boot/boot.bin kernel/kernel.bin shell/shell.bin cat $^ > os.img boot/boot.bin: boot/boot.asm $(ASM) -f bin $< -o $@ kernel/kernel.bin: kernel/kernel.o $(LD) $(LDFLAGS) $< -o $@ kernel/kernel.o: kernel/kernel.c $(CC) $(CFLAGS) $< -o $@ shell/shell.bin: shell/shell.o $(LD) $(LDFLAGS) $< -o $@ shell/shell.o: shell/shell.c $(CC) $(CFLAGS) $< -o $@ clean: rm -f boot/*.bin kernel/*.bin kernel/*.o shell/*.bin shell/*.o os.img run: os.img qemu-system-i386 -fda os.img
This Makefile will help us compile and link our OS components and create the final disk image.
Creating the Bootloader
The bootloader is the first code that runs when a computer starts. It's responsible for loading the kernel into memory and transferring control to it.
Create a file boot/boot.asm
:
; boot.asm [bits 16] [org 0x7c00] KERNEL_OFFSET equ 0x1000 start: mov [BOOT_DRIVE], dl mov bp, 0x9000 mov sp, bp mov bx, MSG_REAL_MODE call print_string call load_kernel call switch_to_pm jmp $ %include "boot/print_string.asm" %include "boot/disk_load.asm" %include "boot/gdt.asm" %include "boot/switch_to_pm.asm" [bits 16] load_kernel: mov bx, MSG_LOAD_KERNEL call print_string mov bx, KERNEL_OFFSET mov dh, 15 mov dl, [BOOT_DRIVE] call disk_load ret [bits 32] BEGIN_PM: mov ebx, MSG_PROT_MODE call print_string_pm call KERNEL_OFFSET jmp $ BOOT_DRIVE db 0 MSG_REAL_MODE db "Started in 16-bit Real Mode", 0 MSG_PROT_MODE db "Successfully landed in 32-bit Protected Mode", 0 MSG_LOAD_KERNEL db "Loading kernel into memory", 0 times 510-($-$$) db 0 dw 0xaa55
This bootloader switches from 16-bit real mode to 32-bit protected mode and loads our kernel into memory.
Developing the Kernel
The kernel is the core of our operating system. For this simple example, we'll create a kernel that prints "Hello from kernel!" to the screen.
Create a file kernel/kernel.c
:
void print_string(char* str) { unsigned short* video_memory = (unsigned short*) 0xb8000; while(*str != 0) { *video_memory = (*str | (15 << 8)); str++; video_memory++; } } void kernel_main() { print_string("Hello from kernel!"); }
Implementing a Simple Shell
Our shell will be a basic command-line interface that can recognize a few simple commands.
Create a fileshell/shell.c
:
#include <stdint.h> #define VIDEO_MEMORY 0xb8000 #define MAX_COLS 80 #define MAX_ROWS 25 uint16_t* video_memory = (uint16_t*)VIDEO_MEMORY; int current_row = 0; int current_col = 0; void print_char(char c) { if (c == '\n') { current_row++; current_col = 0;return; } int index = current_row * MAX_COLS + current_col; video_memory[index] = (uint16_t)c | (uint16_t)(15 << 8); current_col++; if (current_col>= MAX_COLS) { current_col = 0; current_row++; } } void print_string(const char* str) { while (*str) { print_char(*str); str++; } } void clear_screen() { for (int i = 0; i < MAX_COLS * MAX_ROWS; i++) { video_memory[i]=(uint16_t)' ' | (uint16_t)(15 << 8); } current_row = 0; current_col = 0; } void shell_main() { clear_screen(); print_string("Welcome to MyOS Shell!\n"); print_string("Type ' help' for a list of commands.\n"); while (1) { print_string("> "); // Here you would typically implement input handling // // and command processing } }
Testing Your Operating System
Now that we have all the components, let's build and run our OS:
-
In the root directory of your project, run:
make
This will compile all components and create the
os.img
file. -
To run the OS in QEMU, use:
make run
You should see QEMU start up, displaying the output from your bootloader, kernel, and shell.
Conclusion and Next Steps
Congratulations! You've just created a simple operating system from scratch. While this OS is very basic, it demonstrates the fundamental concepts of OS development.
To expand on this project, you might consider:
- Implementing more shell commands
- Adding a simple file system
- Implementing basic memory management
- Adding support for user-space programs
Remember, OS development is a complex field with many areas to explore. This tutorial is just the beginning of what could be an exciting journey into the world of operating systems.
Happy coding, and enjoy exploring the depths of OS development!