Build your own OS (Part 1)

Pasan Devin Jayawardene
7 min readJul 12, 2021

--

Setting up the development environment and booting a primitive operating system

Software act a major role in the modern world. The majority of individuals in the twenty-first century rely on software to meet their demands on a daily basis. We use many types of application software to achieve various things, such as meeting someone online, watching a movie, navigating to a specific location, or purchasing a movie ticket. However, there is one type of software that acts as the heart of all kinds of application software. It is the facilitator for application software as well as it is the communicator between software and hardware. It is none other than the Operating System on which the other application software runs.

However, the purpose of this article is not to teach you about operating systems. Instead, I’m going to show you how to make one of your own(Cool right 😉).

Developing an OS is no easy task. It is a complicated and troublesome process. Therefore I will be writing and posting part by part of this Operating System implementation process in the coming weeks. If you follow along with me, you’ll be the proud owner of your own Operating System by the end of this series. So without further ado, let’s dive into this interesting project.

Fig. 2

The tools we going to use

First of all let’s decide what tools and technologies we going to use in our implementation so that we can get started.

  1. Host Operating System -We will be using Ubuntu as our host operating system. It’s a good practice to install your host OS on a virtual machine like VirtualBox to test your OS.
Fig. 3: Running Ubuntu 20.04 on VirtualBox

2. Packages - Once you have Ubuntu OS up and running, the following packages should be installed using the terminal. (use the command below)

sudo apt-get install build-essential nasm genisoimage bochs bochs-sdl

3. Programming Languages - We will use the C Programming language with GCC compiler to develop our OS. We chose C because constructing an operating system necessitates precise control over the generated code as well as direct memory access.

We’ll be using NASM as the assembler for writing assembly code.

Bash will be used as the scripting language.

4. Virtual Machine - It is very convenient to be able to run your code in a virtual environment rather than on a physical computer when creating an operating system because starting your OS in a virtual machine is considerably faster than getting your OS onto a physical disk and then running it on a physical machine. We will use Bochs Emulator as our virtual machine for this project.

Booting Process

In order to create an operating system, it is essential to understand how it boots up. This chapter will guide you through the OS booting process.

The process of booting an operating system involves transferring control through a series of small programs, each one more powerful than the one before it. The main programs in this process include BIOS, Bootloader, and the OS. The operating system is the final and the most powerful one.

Fig. 4: Programs in booting process

BIOS

When the computer is turned on, a little program that follows the Basic Input Output System (BIOS) standard is launched. This program is normally kept on the PC’s motherboard on a read-only memory chip. The BIOS primarily performs some early diagnostics before handing control over to the bootloader.

Bootloader

The BIOS program will hand over control of the computer to a bootloader program. The bootloader’s job is to hand control over to the OS. However, due to hardware restrictions and other limitations, the bootloader is typically split into two pieces. In that case, the first part of the bootloader will hand the control over to the second part, which finally will give control to the OS.

However, we are not going to write our own bootloader in our project since it involves writing a lot of low-level code which interacts with BIOS. Therefore we will use an existing bootloader: the GNU GRAND 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 seek for a magic number before jumping to guarantee that it is jumping to an OS and not some arbitrary code. The multiboot specification, to which GRUB complies, includes this magic number. Once GRUB has completed the transition, the OS has complete control over the computer.

Hello world to OS development

Now we’ll work on developing the simplest operating system feasible. The only thing this OS will do is writing OxCAFEBABE to the EAX register.

Writing and Compiling

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

Save the code below in a file named ‘loader.s’.

Fig. 5: Assembly code to write CAFEBABE to EAX register

The only thing this OS will do is write the very specific number 0xCAFEBABE to the EAX register. We can check if the number has successfully been written to the register once we boot the OS up.

Next, use the following command to compile loader.s into a 32-bit ELF object file.

nasm -f elf32 loader.s

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

Linking

After the code has been compiled, it must be linked to create an executable file. Because addresses less than 1 megabyte (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 to 0x00100000 (1 MB). We can use the following script as the linker.

Fig 6: Script for Linker

Make a file named ‘link.ld’ with the linker script. Using the following command, you can now link the executable:

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

You should now see a file named ‘kernel.elf’ which is the final executable.

Getting GRUB

We will be using the GRUB Legacy stage2_eltorito as our bootloader for the implementation. The binary file for the above mentioned bootloader can be downloaded from the following link.

https://github.com/pasandevin/efficientOS/blob/setup_booting_os/stage2_eltorito

Copy the file ‘stage2_eltorito’ to your working directory.

Building the ISO image

The executable must be stored on a medium that a virtual or physical machine can read. For that, we will be using ISO image files in our project. We can use the program genisoimage to create the image.

A folder that contains the files that will be on the ISO image should be created. Use the following commands to create the folder and copy the files
to their proper locations:

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 instruct GRUB where to find the kernel and set various options. Use the following configuration for the file:

Fig. 7: Configuration file for GRUB

Place the ‘menu.lst’ file in the folder ‘iso/boot/grub/’. Now the ‘iso’ folder should contain the following structure:

Fig. 8: Folder structure for the ‘iso’ folder

Then use the following command to create the iso file.

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

A file named ‘os.iso’ will be generated. This contains the kernel executable, the GRUB bootloader and the configuration file.

Running our OS with Bochs

Using the os.iso ISO image, we can now boot our OS in the Bochs emulator. To get started with Bochs, you’ll need a configuration file. Following is a simple configuration file.

Fig. 9: Configuration file for Bochs

Depending on how you installed Bochs, you may need to adjust the path to romimage and vgaromimage. You can refer to Boch’s official website to find out more about Bochs configuration. Save the configuration file as ‘bochsrc.txt’ and run Bochs with following command:

bochs -f bochsrc.txt -q

Bochs should now be running and presenting a console with some information on it. Quit Bochs and display the log generated by Bochs with the command below:

cat bochslog.txt

The contents of the registers of the CPU replicated by Bochs should now appear somewhere in the output. Your OS has successfully booted if you see RAX=00000000CAFEBABE or EAX=CAFEBABE in the output.

Fig. 10: The log generated by Bochs

Congratulations! We just created a simple Operating System. In next week we’ll see how we can get the help of C Programming Language to further develop our OS. Until then, Goodbye and stay safe!

Fig. 11

Reference:

The Little OS Book: https://littleosbook.github.io/book.pdf

Further Readings:

  1. http://duartes.org/gustavo/blog/post/how-computers-boot-up
  2. http://duartes.org/gustavo/blog/post/kernel-boot-process
  3. http://wiki.osdev.org/Boot_Sequence

-Pasan Devin Jayawardene-

--

--