Writing your own Operating System

Hasini Samarathunga
6 min readJul 15, 2021

This is the very first article in a series of articles. Altogether I will explain my experience in making my very own x86 Operating System. It is optimal to develop the OS a small portion at a time because that way it is usually easier to find bugs in a system.

Here I’m following the “LittleOSBook” with the implementation. Before we start making the OS we should start with setting up the development environment.

Setting up a Linux Virtual Machine

The very first step when experimenting with developing an OS is to set up a Virtual Machine. Some of the options for VM are Virtual Box, Parallels Desktop, Qemu, etc. I recommend starting with Virtual Box as it is free to use and quite simple.

For this project, I used Ubuntu 20.04 as the Host Operating System. Setting up both the Virtual Box and Ubuntu 20.04 in a Windows 10 can be found here.

Next, to get all the essential packages, use this command on the terminal.

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

Programming Languages used

Here, C programming language is used along with GCC to develop the OS, because developing an OS requires very precise control of the generated code as well as direct access to memory.

The code will make use of one type attribute that is specific for GCC: __attribute__((packed)) . This attribute is mostly compatible with GCC only and it allows us to ensure that the compiler uses a memory layout for struct exactly as we define it in the code.

Here, NASM is used as the assembler for writing assembly code.

Booting an OS

This consists of transferring control along a chain of small programs, each one more “powerful” than the previous one, ending with the OS.

BIOS

Basic Input Output System (BIOS) is a small program, stored on a read-only memory chip on the motherboard, that starts when the PC is turned on.BIOS program was originally used to export some library functions for printing to the screen, reading keyboard input, etc. But Modern BIOS mainly runs some early diagnostics and then transfers control to the bootloader.

The Bootloader

The bootloader’s task is to transfer control to the operating system developers. It is usually split into two parts. The first part of the bootloader will transfer control to the second part, which finally gives control of the PC to the operating system.

Here we are not writing a bootloader as it involves writing a lot of low-level code that interacts with the BIOS. Therefore, we are going to use an existing bootloader called the GNU GRand Unified Bootloader (GRUB).

Using GRUB, the operating system can be built as an ordinary ELF executable, which will be loaded by GRUB into the correct memory location.

The Operating System

GRUB will transfer control to the operating system by jumping to a position in memory. Before the jump, GRUB will look for a magic number to ensure that it is actually jumping to an OS and not some random code. This magic number is part of the multiboot specification which GRUB adheres to. Once GRUB has made the jump, the OS has full control of the computer.

This article will describe how to implement the smallest possible OS that can be used together with GRUB. The only thing this OS will do is write 0xCAFEBABE to the eax register.

First, make a folder with your name of choice. Make sure to create all the files and sub-folders in this folder.

So now, let me walk you through how I made my very own simple OS, pocketOS.

Compiling the Operating System

Save the following assembly code in a file called loader.s

loader.s

The file loader.s can be compiled into a 32 bits ELF object file with the below command.

    nasm -f elf32 loader.s

The generated loader.o file will now be in the folder.

Linking the Kernel

Now the code needs to be linked to produce an executable file. GRUB will load the kernel at a memory address larger than or equal to 0x00100000 (1MB). Because GRUB itself, BIOS, and memory-mapped I/O all use addresses lower than 1 MB. Save the linker script below into a file called link.ld.

link.ld

The executable can now be linked with the below command.

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

You will now see the final executable kernel.elf in the folder.

Obtaining GRUB

The GRUB version we will use is GRUB Legacy since the OS ISO image can then be generated on systems using both GRUB Legacy and GRUB 2. More specifically, the GRUB Legacy stage2_eltorito bootloader will be used.

The stage2_eltorito file can be downloaded from here.

Then copy the file stage2_eltorito to our folder.

Building an ISO Image

The executable needs to be placed on a media that can be loaded by a virtual or physical machine. Here, ISO image files are used as the media. Floppy images are also used depending on what the virtual or physical machine supports.

   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

Go to iso/boot/grub/ and create a configuration file menu.lst for GRUB. This file tells GRUB where the kernel is located and configures some options.

menu.lst

The contents of the iso folder should now look like this.

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

To generate the ISO image use 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 ISO image os.iso now contains the kernel executable, the GRUB bootloader, and the configuration file.

Running Bochs

Now we can run the OS in the Bochs emulator using the os.iso ISO image.

First, save the below Bochs configuration in a file named bochsrc.txt .

bochsrc.txt

Now you can run Bochs using the below command,

bochs -f bochsrc.txt -q

The flag -f tells Bochs to use the given configuration file and the flag -q tells Bochs to skip the interactive start menu. You should now see Bochs starting and displaying a console with some information from GRUB on it.

After quitting Bochs, display the log produced by Boch,

cat bochslog.txt

You should now see the contents of the registers of the CPU simulated by Bochs somewhere in the output. Now you will find RAX=00000000CAFEBABE or EAX=CAFEBABE (depending on if you are running Bochs with or without 64-bit support) in the output.

If you see this then your OS has successfully booted!! Congratulations!

Hope you found it easier to follow these steps and create your very own OS. You can obtain the completed OS from my Github here. Hope to see you in the next article as well!

Thank you!

--

--