In this section, we will delve into the fascinating world of operating system (OS) development by creating a basic OS kernel. This module will cover the fundamental concepts and provide practical steps to build a simple kernel from scratch.

Objectives

  • Understand the role and structure of an OS kernel.
  • Learn how to set up a development environment for kernel programming.
  • Write and compile a basic kernel.
  • Implement essential kernel functions.

Prerequisites

Before starting this module, ensure you have:

  • Basic knowledge of Assembly language.
  • Familiarity with C programming (as we will use C for some parts of the kernel).
  • A development environment set up for Assembly and C programming.

  1. Understanding the OS Kernel

What is a Kernel?

The kernel is the core component of an operating system. It manages system resources and allows software to interact with hardware. Key responsibilities include:

  • Process Management: Handling the execution of processes.
  • Memory Management: Managing system memory and allocation.
  • Device Management: Interfacing with hardware devices.
  • File System Management: Managing files and directories.

Types of Kernels

  • Monolithic Kernel: All OS services run in kernel space.
  • Microkernel: Only essential services run in kernel space; others run in user space.
  • Hybrid Kernel: Combines elements of monolithic and microkernel designs.

  1. Setting Up the Development Environment

Tools Required

  • Assembler: NASM (Netwide Assembler)
  • Compiler: GCC (GNU Compiler Collection)
  • Linker: LD (GNU Linker)
  • Emulator: QEMU (for testing the kernel)

Installing the Tools

# Install NASM
sudo apt-get install nasm

# Install GCC
sudo apt-get install gcc

# Install LD
sudo apt-get install binutils

# Install QEMU
sudo apt-get install qemu

  1. Writing the Basic Kernel

Bootloader

The bootloader is the first code that runs when a computer starts. It loads the kernel into memory and transfers control to it.

Example Bootloader (boot.asm)

[BITS 16]
[ORG 0x7C00]

start:
    ; Set up the stack
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00

    ; Load the kernel
    mov si, msg
    call print_string

    ; Infinite loop
    jmp $

print_string:
    mov ah, 0x0E
.next_char:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp .next_char
.done:
    ret

msg db 'Loading kernel...', 0

times 510-($-$$) db 0
dw 0xAA55

Compiling the Bootloader

nasm -f bin boot.asm -o boot.bin

Writing the Kernel

The kernel will be written in C and Assembly. The entry point will be in Assembly, and it will call a C function.

Example Kernel (kernel.c)

void kernel_main() {
    const char *str = "Hello, Kernel World!";
    char *vidptr = (char*)0xb8000;  // Video memory starts here
    unsigned int i = 0;
    unsigned int j = 0;

    // Clear the screen
    while (j < 80 * 25 * 2) {
        vidptr[j] = ' ';
        vidptr[j+1] = 0x07;  // Attribute-byte: light grey on black screen
        j = j + 2;
    }

    j = 0;

    // Write the string to video memory
    while (str[j] != '\0') {
        vidptr[i] = str[j];
        vidptr[i+1] = 0x07;
        ++j;
        i = i + 2;
    }

    return;
}

Kernel Entry Point (kernel_entry.asm)

[BITS 32]
[GLOBAL kernel_entry]

kernel_entry:
    ; Set up the stack
    mov esp, 0x90000

    ; Call the kernel main function
    call kernel_main

    ; Infinite loop
    hlt

Compiling the Kernel

# Compile the C kernel
gcc -m32 -ffreestanding -c kernel.c -o kernel.o

# Compile the Assembly entry point
nasm -f elf32 kernel_entry.asm -o kernel_entry.o

# Link the kernel
ld -m elf_i386 -T linker.ld -o kernel.bin kernel_entry.o kernel.o

Linker Script (linker.ld)

ENTRY(kernel_entry)
SECTIONS
{
    . = 0x100000;
    .text : { *(.text) }
    .data : { *(.data) }
    .bss : { *(.bss) }
}

Creating the OS Image

cat boot.bin kernel.bin > os-image.bin

Running the Kernel in QEMU

qemu-system-i386 -fda os-image.bin

  1. Implementing Essential Kernel Functions

Basic Functions

  • Screen Output: Displaying text on the screen.
  • Memory Management: Allocating and deallocating memory.
  • Interrupt Handling: Managing hardware and software interrupts.

Example: Screen Output Function

void print_string(const char *str) {
    char *vidptr = (char*)0xb8000;
    unsigned int i = 0;
    while (str[i] != '\0') {
        vidptr[i*2] = str[i];
        vidptr[i*2+1] = 0x07;
        ++i;
    }
}

Practical Exercise

Task

  1. Modify the kernel to display a custom message.
  2. Implement a function to clear the screen.
  3. Add a simple memory allocation function.

Solution

  1. Modify kernel_main to call print_string with a custom message.
  2. Implement clear_screen function.
  3. Implement a basic memory allocation function using a simple memory pool.

Example Solution

void clear_screen() {
    char *vidptr = (char*)0xb8000;
    unsigned int j = 0;
    while (j < 80 * 25 * 2) {
        vidptr[j] = ' ';
        vidptr[j+1] = 0x07;
        j = j + 2;
    }
}

void kernel_main() {
    clear_screen();
    print_string("Custom Kernel Message");
}

#define MEM_POOL_SIZE 1024
char memory_pool[MEM_POOL_SIZE];
unsigned int mem_index = 0;

void* malloc(unsigned int size) {
    void* mem = &memory_pool[mem_index];
    mem_index += size;
    return mem;
}

Conclusion

In this section, we have covered the basics of creating a simple OS kernel. We started with understanding the role of a kernel, setting up the development environment, writing a basic kernel, and implementing essential functions. This foundational knowledge prepares you for more advanced kernel development topics, such as process management, advanced memory management, and hardware interfacing.

In the next module, we will explore more advanced assembly concepts, including interrupts, system calls, and optimizing assembly code.

© Copyright 2024. All rights reserved