Welcome To OS CREATOR Website Subscribe Now!

How to Create a Simple Operating System from Scratch

Have you ever wondered how an operating system works? How does it manage the hardware and software resources of a computer? How does it provide a user

In this blog post, we will guide you through the process of creating a simple operating system from scratch, using some basic tools and languages. By the end of this post, you will have a working system that can boot, load a kernel, print "Hello, World!" to the screen, and run a shell.

What is an Operating System?

An operating system is a software program that acts as an intermediary between the computer hardware and the user of the computer. The main purpose of an operating system is to provide an environment for effective execution of an application program. An operating system manages the resources and services such as devices, processors, memory, files, and networks.

An operating system consists of several components, such as:
  • Kernel: The kernel is the core of the operating system that controls the basic functions of the system, such as process management, memory management, device management, and system calls.
  • Bootloader: The bootloader is a small program that runs when the computer is turned on and loads the kernel into memory and executes it.
  • Shell: The shell is a program that provides a user interface to interact with the operating system, such as typing commands and running programs.

What Do You Need to Create an Operating System?

To create an operating system from scratch, you will need some tools and libraries, such as:
  • QEMU: QEMU is a software that emulates a computer and allows you to run your operating system without installing it on a physical machine.
  • GCC: GCC is a compiler that converts your C code into executable code that can run on your operating system.
  • NASM: NASM is an assembler that converts your assembly code into executable code that can run on your operating system.
  • GRUB: GRUB is a bootloader that loads your operating system from a disk image and transfers control to it.
You can install these tools and libraries on your Linux or Windows system using the following commands:

Linux:

bash
  sudo apt-get update
  sudo apt-get install qemu gcc nasm grub
  

Windows:

bash
  choco install qemu gcc nasm grub
  

How to Create an Operating System from Scratch?

Now that you have the tools and libraries ready, you can start creating your operating system from scratch. The steps are:
  1. Setting up the development environment
  2. Writing the bootloader
  3. Writing the kernel
  4. Writing the shell
Let us discuss each step in detail.

Setting up the Development Environment

The first step is to set up the development environment where you will write and test your code. You will need a code editor, a terminal, and a disk image.

  • Code editor: You can use any code editor of your choice, such as Visual Studio Code, Atom, or Sublime Text. You will need to write code in C and assembly languages, so make sure your code editor supports syntax highlighting and indentation for these languages.
  • Terminal: You will need a terminal to compile and run your code, as well as to interact with your operating system. You can use any terminal of your choice, such as Bash, PowerShell, or CMD.
  • Disk image: You will need a disk image to store your operating system files and boot it using QEMU. You can create a disk image using the following command:
    bash
        qemu-img create -f raw os.img 64M
        
    This command creates a disk image named os.img with a size of 64 MB and a format of raw.

Writing the Bootloader

The second step is to write the bootloader that will load your kernel into memory and execute it. The bootloader is written in assembly language and stored in the first sector of the disk image, which is called the boot sector. The boot sector has a size of 512 bytes and ends with a magic number of 0xAA55, which tells the BIOS that this is a bootable disk.

To write the bootloader, create a file named boot.asm and paste the following code:
asm
; Define some constants
BITS 16 ; Use 16-bit mode
org 0x7c00 ; Set the origin to 0x7c00, the address where the BIOS loads the boot sector
SECTOR_SIZE equ 512 ; Set the sector size to 512 bytes
KERNEL_OFFSET equ 0x1000 ; Set the kernel offset to 0x1000, the address where the bootloader loads the kernel

; Define a macro to print a string
%macro print 2
mov ah, 0x0e ; Set the BIOS function to print a character
mov bh, 0x00 ; Set the page number to 0
mov bl, %2 ; Set the foreground color to %2
mov si, %1 ; Set the source index to %1, the address of the string
.print_char:
lodsb ; Load the next character from si to al and increment si
cmp al, 0 ; Compare al with 0, the null terminator
je .done ; If al is 0, jump to .done
int 0x10 ; Call the BIOS interrupt 0x10 to print the character in al
jmp .print_char ; Jump to .print_char to print the next character
.done:
%endmacro

; Define a macro to load a sector
%macro load_sector 3
push dx ; Save dx to the stack
mov ah, 0x02 ; Set the BIOS function to read a sector
mov al, 1 ; Set the number of sectors to read to 1
mov ch, 0 ; Set the cylinder number to 0
mov dh, 0 ; Set the head number to 0
mov cl, %1 ; Set the sector number to %1
mov bx, %2 ; Set the destination address to %2
mov dl, %3 ; Set the drive number to %3
int 0x13 ; Call the

; Load the kernel from the second sector to the kernel offset
load_sector 2, KERNEL_OFFSET, 0x80

; Jump to the kernel offset to execute the kernel
jmp KERNEL_OFFSET

; Fill the remaining bytes of the boot sector with zeros
times SECTOR_SIZE - ($ - $$) db 0

; Append the magic number to the end of the boot sector
dw 0xAA55

This code loads the kernel from the second sector of the disk image to the memory address 0x1000, which is the kernel offset. Then, it jumps to that address to execute the kernel. The times directive fills the remaining bytes of the boot sector with zeros, so that the total size of the boot sector is 512 bytes. The dw directive appends the magic number 0xAA55 to the end of the boot sector, which tells the BIOS that this is a bootable disk.

To compile the bootloader, use the following command:
bash
nasm -f bin boot.asm -o boot.bin

This command uses NASM to compile the boot.asm file into a binary file named boot.bin.

To write the bootloader to the disk image, use the following command:
bash
dd if=boot.bin of=os.img conv=notrunc

This command uses DD to copy the boot.bin file to the os.img file without truncating it.

Writing the Kernel

The third step is to write the kernel that will print "Hello, World!" to the screen and halt the CPU. The kernel is written in C language and stored in the second sector of the disk image. The kernel uses some functions from the standard library, such as printf and exit, which are provided by the GCC compiler.

To write the kernel, create a file named kernel.c and paste the following code:
c
#include <stdio.h>
#include <stdlib.h>

// Define a function to print a string to the screen
void print(char *str)
{
// Loop through each character of the string
while (*str)
{
// Write the character to the video memory at address 0xb8000
*(unsigned short *)0xb8000 = (*str++ | 0x0f00);
// Increment the video memory address by 2 bytes
0xb8000 += 2;
}
}

// Define the main function of the kernel
int main()
{
// Print "Hello, World!" to the screen
print("Hello, World!");

// Exit the kernel
exit(0);

// Return 0
return 0;
}

This code defines a function named print that takes a string as an argument and prints it to the screen. The function writes each character of the string to the video memory at address 0xb8000, which is the start of the text mode display. The function also adds an attribute byte of 0x0f to each character, which sets the foreground color to white and the background color to black. The main function of the kernel calls the print function with the argument "Hello, World!", and then exits the kernel using the exit function from the standard library.

To compile the kernel, use the following command:
bash
gcc -ffreestanding -c kernel.c -o kernel.o

This command uses GCC to compile the kernel.c file into an object file named kernel.o. The -ffreestanding option tells the compiler that the program does not depend on the standard library or the operating system.

To link the kernel, use the following command:
bash
ld -o kernel.bin -Ttext 0x1000 kernel.o --oformat binary

This command uses LD to link the kernel.o file into a binary file named kernel.bin. The -Ttext 0x1000 option tells the linker that the code section of the kernel should start at address 0x1000, which is the kernel offset. The --oformat binary option tells the linker that the output format should be binary.

To write the kernel to the disk image, use the following command:
bash
dd if=kernel.bin of=os.img seek=1 conv=notrunc

This command uses DD to copy the kernel.bin file to the os.img file, starting from the second sector. The seek=1 option tells DD to skip the first sector, which is occupied by the bootloader. The conv=notrunc option tells DD not to truncate the disk image.

Writing the Shell

The fourth and final step is to write the shell that will read user input and execute commands. The shell is written in C language and stored in the third sector of the disk image. The shell uses some functions from the standard library, such as gets, puts, and strcmp, which are provided by the GCC compiler.

To write the shell, create a file named shell.c and paste the following code:
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Define a function to read a line from the keyboard
char *read_line()
{
// Allocate a buffer of 256 bytes
char *buffer = malloc(256);
// Read a line from the standard input and store it in the buffer
gets(buffer);
// Return the buffer
return buffer;
}

// Define a function to execute a command
void execute_command(char *command)
{
// Check if the command is "help"
if (strcmp(command, "help") == 0)
{
// Print the help message
puts("Welcome to my shell!");
puts("The available commands are:");
puts("help - show this help message");
puts("hello - print hello to the screen");
puts("exit - exit the shell");
}
// Check if the command is "hello"
else if (strcmp(command, "hello") == 0)
{
// Print hello to the screen
puts("Hello!");
}
// Check if the command is "exit"
else if (strcmp(command, "exit") == 0)
{
// Exit the shell
exit(0);
}
// Otherwise
else
{
// Print an error message
puts("Invalid command!");
}
}

// Define the main function of the shell
int main()
{
// Declare a variable to store the command
char *command;
// Loop forever
while (1)
{
// Print the prompt
printf("> ");
// Read a line from the keyboard and store it in the command variable
command = read_line();
// Execute the command
execute_command(command);
// Free the command variable
free(command);
}
// Return 0
return 0;
}

This code defines a function named read_line that reads a line from the keyboard and returns it as a string. The function allocates a buffer of 256 bytes using the malloc function from the standard library, and then reads a line from the standard input using the gets function from the standard library. The function returns the buffer as the result.

The code also defines a function named execute_command that takes a command as an argument and executes it. The function checks if the command is one of the predefined commands, such as "help", "hello", or "exit", and performs the corresponding action. If the command is "help", the function prints a help message that shows the available commands. If the command is "hello", the function prints "Hello!" to the screen. If the command is "exit", the function exits the shell using the exit function from the standard library. If the command is not one of the predefined commands, the function prints an error message that says "Invalid command!".

The main function of the shell loops forever and performs the following steps:
  • Prints the prompt "> " to the screen using the printf function from the standard library.
  • Reads a line from the keyboard and stores it in the command variable using the read_line function.
  • Executes the command using the execute_command function.
  • Frees the command variable using the free function from the standard library.
To compile the shell, use the following command:
bash
gcc -ffreestanding -c shell.c -o shell.o

This command uses GCC to compile the shell.c file into an object file named shell.o. The -ffreestanding option tells the compiler that the program does not depend on the standard library or the operating system.

To link the shell, use the following command:
bash
ld -o shell.bin -Ttext 0x2000 shell.o --oformat binary

This command uses LD to link the shell.o file into a binary file named shell.bin. The -Ttext 0x2000 option tells the linker that the code section of the shell should start at address 0x2000, which is the shell offset. The --oformat binary option tells the linker that the output format should be binary.

To write the shell to the disk image, use the following command:
bash
dd if=shell.bin of=os.img seek=2 conv=notrunc
This command uses DD to copy the shell.bin file to the os.img file, starting from the third sector. The seek=2 option tells...

How to Test Your Operating System?

The last step is to test your operating system and see it in action. You can use QEMU to emulate a computer and boot your operating system from the disk image. To do that, use the following command:
bash
qemu-system-i386 -hda os.img
This command uses QEMU to emulate a 32-bit x86 system and load the os.img file as the hard disk drive. You should see a window that shows the output of your operating system, such as: ![QEMU window]

You can interact with your operating system using the keyboard and the mouse. You can type commands in the shell and see the results. You can also exit QEMU by pressing Ctrl+C in the terminal.

Congratulations! You have successfully created a simple operating system from scratch. You have learned how to write a bootloader, a kernel, and a shell, and how to compile, link, and test them. You have also learned some basic concepts and skills of operating system development.

Conclusion

In this blog post, we have guided you through the process of creating a simple operating system from scratch, using some basic tools and languages. We hope you have enjoyed this project and learned something new and useful. If you have any questions, comments, or feedback, please feel free to share them with us.

Thank you for reading and happy coding! 😊

Post a Comment

  • Copy
  • Paste
  • Share
  • More
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.