Developing your own OS - Part#1

Thakshila Bandara
5 min readOct 1, 2022

--

This is an article series about how to implement our own operating system and this is the very first article of this series. Through these article i will explain my experience about making my own OS.

Here i’m following the guidelines of the “The little book about OS development” .

Step 1: Select a Linux based OS and a virtual machine.

In here i’m used Ubuntu as the Linux based OS and the oracle virtual-box as the virtual machine.

In here we use a virtual machine to run Ubuntu because it is easy. it allows us to run Ubuntu as guest OS while windows run as host OS.

Step 2: Launch the Ubuntu and installing necessary packages.

sudo apt-get install build-essential nasm genisoimage qemu qemu-system-x86 

In here i used qemu as the virtual machine to run our little OS.

Programming Languages

Here I used C language as main developing language because when developing OS it requires a very precise control of the generated code and direct access to memory.

Also, for writing assembly code, I have chosen NASM.

Step 3: Booting.

When an operating system is booted, control is passed along a series of smaller programs, each one more “powerful” than the one before it, with the operating system acting as the final “program.”

boot process

BIOS

The BIOS program’s original purpose was to export some library features for screen printing, keyboard input reading, etc. Modern operating systems don’t use the BIOS’s features; instead, they use drivers to communicate with hardware directly, avoiding the BIOS. Currently, BIOS mostly performs some initial diagnostics (power-on-self-test) before handing off command to the boot loader.

The Bootloader

The bootloader’s responsibility is to hand over command to the creators of the operating system. Typically, it is divided into two sections. The first component of the bootloader will hand control over to the second component, who will then hand control over to the operating system.

We are not developing a bootloader in this case since doing so would require writing a lot of low-level code that communicates with the BIOS. So, we’ll employ the GNU GRand Unified Bootloader, an already-existing bootloader (GRUB).

The operating system can be created using GRUB as a regular ELF executable, and GRUB will load it into the appropriate memory region.

The Operating system

GRUB will leap to a location in memory and hand off control to the operating system. In order to confirm that it is genuinely leaping to an OS and not just some random code, GRUB will search for a magic number before the jump. The multiboot specification, to which GRUB complies, includes this magic number. The OS will have complete control over the computer once GRUB has accomplished the transition.

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. Use this folder for further works. Make sure to create all the sub-folders and files inside of this folder.

Step 4: Compiling the OS

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

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

nasm -f elf32 loader.s

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

Step 5: 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.

The executable file can now be linked with the following command:

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

After executing, kernel.elf file created inside the folder.

Since the OS ISO image may be created on systems utilizing both GRUB 2 and GRUB Legacy, GRUB Legacy is the version of GRUB we’ll be using. 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.

Step 6: Building an ISO image.

I used ISO image files as the media. We will create the kernel ISO image with the program genisoimage. A folder must first be created that contains the files that will be on the ISO image. The following commands create the 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 cp kernel.elf iso/boot/           # copy the kernel

A configuration file menu.lst for GRUB must be created. This file tells GRUB where the kernel is located and configures some options. Place the file menu.lst in the folder iso/boot/grub/.

The contents of the iso folder should now look like the following:

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

To generate 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.

Step 7: Running the OS

Run the following command in Ubuntu terminal:

qemu-system-i386 -boot d -cdrom os.iso -m 32 -d cpu -D logQ.txt

If our little OS work, you will see a screen like below,

Now close the emulator window and open up the newly created file logQ.txt.

When you scroll down to bottom of that you can see ‘cafebabe’.

That implies our noobOS has successfully booted.

You can check my github repository for this OS from here.

Thank you very much for reading!

Hope to get back to you with the chapter #2.

--

--

Thakshila Bandara

Software Engineering undergraduate. | University of Kelaniya.