Implementing a Simple Operating System — part 01

Himani Perera
7 min readOct 1, 2022

--

Ever wanted to make an Operating system on your own? Then everything’s here! This article will show you how to implement your own operating system.

Developing an Operating System is no easy task. It is a complicated and difficult process. So, to make it more interesting, let’s go through a step-by-step layout…

Without wasting any other words, let’s get into the project…

STEP 01: Installing a Virtual Machine

WHAT???

A virtual machine is the emulation of a computer system, which is based on computer architecture and provides the functionality of an actual physical computer. Their implementations may involve specialized hardware, software, or a combination of the two.

we will be using Oracle VM VirtualBox as our virtual machine for this project.

WHY???

If we intend to build an operating system, we can achieve this much more easily with a virtual machine than with a physical computer. Because executing our operating system on a virtual machine is much faster than getting our operating system onto a physical medium and then executing it on a physical machine.

Step 02: Installing & Setting Up a Host Operating System

I have used Ubuntu as the host operating system for this OS development process. You can use this link to download Ubuntu.

Ubuntu Vs. Windows

Once Ubuntu is installed, either physical or virtual, and set it up on the virtual machine, the following packages should be installed using apt-get:

sudo apt-get install build-essential nasm genisoimage bochs bochs-sdl
After setting up Ubuntu on VirtualBox

programming languages…

We’ll be using the C Programming Language with the GCC compiler to develop our OS. We selected C as building an operating system requires precise control over generated code as well as direct memory access.

For writing assembly code, we’ll use NASM as the assembler.

Step 03: Getting familiar with creating files on Ubuntu

The touch command in Ubuntu is simply used to create empty files i.e. files without any content.

touch mytextfile.txt

Step 04: Booting Process

Booting an operating system consists of passing control through a series of small programs, including BIOS, Bootloader, and the OS. Each one is more “powerful” than the one before it, with the operating system being the final and the most powerful one.

BIOS

When you turn on your computer, it will start a small software that follows the Basic Input Output System (BIOS) standard. This application is typically kept on a read-only memory chip on the PC’s motherboard. Nowadays, BIOS mainly performs some early diagnostics (power-on-self-test) and then transfers control to the bootloader.

The Bootloader

The BIOS program will hand over control of the PC to a program known as a bootloader. The bootloader’s task is to transfer control to the OS.

However, due to hardware limitations1 and backward compatibility, the bootloader is sometimes divided into two parts: the first part of the bootloader transfers control to the second component, which finally delivers control of the PC to the operating system.

A bootloader requires a lot of low-level code that interacts with the BIOS. As a result, we will be using an existing bootloader: GNU General Unified Bootloader (GRUB).

The operating system can be constructed using GRUB as an ‘.ELF’ executable, which will be loaded into the correct memory location by GRUB.

The Operating System

By jumping to a memory location, GRUB will hand over control to the operating system. GRUB will search for a magic number before the leap to guarantee that it is indeed jumping to an OS and not some arbitrary code. This magic number is a requirement of the multiboot protocol, to which GRUB conforms. Once GRUB has completed the transition, the operating system has complete control of the computer.

Step 05: Compiling the Operating System

In this step, we will be compiling the OS. For that we have to save the code below in a file named ‘loader.s’.

Then use the following command to compile this loader.s file, into an ELF object file.

nasm -f elf32 loader.s

Now you should see a file named loader.o’ in your working directory.

note: This section of the OS must be developed in assembly code. We cannot use the C language since it requires a stack, which isn’t available.

Step 06: Linking the Kernel

After the compilation, the code must now be linked to generate an executable file, which involves a little more attention than when linking typical programs. Because addresses less than 1 MB are used by GRUB, BIOS, and memory mapped I/O, we want GRUB to load the kernel at a memory address greater than or equal to0x00100000 (1 MB).

In order to complete this step, we have to save the following linker script in a file named ‘link.ld’.

Using the following command, you can now link the executable:

ld -T link.ld -melf_i386 loader.o -o kernel.elf

Now you should see a file named ‘kernel.elf’ which is the final executable.

Step 07: Obtaining GRUB

For the implementation, we will use the GRUB Legacy stage2_eltorito bootloader. The binary file for the above-mentioned bootloader can be downloaded from the following link.

http://littleosbook.github.com/files/stage2_eltorito

Copy the file ‘stage2_eltorito’ to the folder that already contains loader.s and link.ld.

Step 08: Building the ISO image

The executable must be saved on a medium that may be read by a virtual or real computer. In our project, we will use ISO image files for this. To make the image, we can use the program genisoimage.

First, a folder containing the files that will be on the ISO image must be created. We can use the following commands to create a folder and copy the files to their correct places.

   mkdir -p iso/boot/grub              # create the folder structure
cp stage2_eltorito iso/boot/grub/ # copy the bootloader
cp kernel.elf iso/boot/ # copy the kernel

Then make a GRUB configuration file named ‘menu.lst’. This file should tell GRUB where to look for the kernel and configure other options.

Then save followings in ‘menu.lst’ file and place that file in the folder ‘iso/boot/grub/’.

The contents of the ‘iso’ folder should now resemble the image below:

    iso
|-- boot
|-- grub
| |-- menu.lst
| |-- stage2_eltorito
|-- kernel.elf

Then the ISO image can then be generated with the following command:

   genisoimage -R                  \
-b boot/grub/stage2_eltorito \
-no-emul-boot \
-boot-load-size 4 \
-A os \
-input-charset utf8 \
-quiet \
-boot-info-table \
-o os.iso \
iso

The file ‘os.iso’ will be created. The kernel executable, the GRUB bootloader, and the configuration file are all included inside that file.

Step 09: Running our OS with Bochs

We can now run the OS in the Bochs emulator using the os.iso ISO image. To get started with Bochs, you’ll need a configuration file. Here’s an example of a basic setup file:

Depending on how you installed Bochs, you may need to modify the path to romimage and vgaromimage.

Save the configuration file as ‘bochsrc.txt’ and run Bochs with following command:

bochs -f bochsrc.txt -q

Then you will get a window like this.

However, if the above window does not appear, then you must change the display_library: sdlto display_library: sdl2 in file bochsrc.txt. Then the error will be fixed.

Bochs should now be running and displaying some information on the console. After quitting Bochs, display the log produced by Boch:

cat bochslog.txt

The contents of the CPU registers replicated by Bochs should now be visible somewhere in the output. If you see RAX=00000000CAFEBABE or EAX=CAFEBABE in the output, your OS has successfully booted.

The log generated by Bochs

Finally, after all these steps, we can get the following folders and files.

Yayyyy!!! We just created our simple Operating System. In the coming weeks, we will look into more details about how we can further develop our OS. Until then, stay safe and have a nice week!

--

--

Himani Perera

Final Year Software Engineering Undergraduate at University of Kelaniya, Sri Lanka