Building Your Own a Simple Operating System from Scratch

Learn how to create a basic operating system from the ground up. This step-by-step guide covers bootloader creation, kernel development, and implement

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:

  1. Bootloader: A small program that initializes the system and loads the kernel.
  2. Kernel: The central component that manages system resources and provides essential services.
  3. 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

  1. Create a new directory for your OS project:

    mkdir my_os
    cd my_os
    
  2. Create subdirectories for different components:

    mkdir boot kernel shell
  3. 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 file shell/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:

  1. In the root directory of your project, run:

    make

    This will compile all components and create the os.img file.

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

  1. Implementing more shell commands
  2. Adding a simple file system
  3. Implementing basic memory management
  4. 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!

About the author

🚀 | Exploring the realms of creativity and curiosity in 280 characters or less. Turning ideas into reality, one keystroke at a time. =» Ctrl + Alt + Believe

Post a Comment

-
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.