Build your own Operating System #1

Gayan Malinda
10 min readJul 16, 2021

--

Setting up the development environment and booting a primitive operating system

An operating system is software that communicates with the hardware and allows other programs to run. It is comprised of system software, or the fundamental files your computer needs to boot up and function. Every desktop computer, tablet, and smartphone includes an operating system that provides basic functionality for the device.

Common desktop operating systems include Windows, OS X, and Linux. While each OS is different, most provide a graphical user interface, or GUI, that includes a desktop and the ability to manage files and folders. They also allow you to install and run programs written for the operating system.

However, the purpose of this article is not to explain about operating systems. Instead, I’ll be to the point to show you how to implement your own operating system. I look forward to presenting series of articles related to making your own OS in the coming weeks. After following all of these, you will be the owner of your own OS.

In this article, I’m going to explain the steps of setting up the development environment and booting a primitive operating system.

Tools

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

Host Operating System

A host operating system is the primary operating system (OS) installed on a computer system’s hard drive. In most cases, there is only one host OS. Other OSs, known as virtual OSs, may operate within the host OS.

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.

Click here to download oracle VirtualBox.

Packages

Once Ubuntu is installed in your computer, either physical or virtual, the following packages should be installed using the terminal. (use the command below)

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

Now I briefly explain what are the packages you installed.

build-essential

This package is necessary for compiling software. It includes the GNU debugger, g++/GNU compiler collection, and some more tools and libraries that are required to compile a program. With the installation of build-essential packages, some other packages such as G++, dpkg-dev, GCC and make, etc. also get installed on your system.

NASM

The Netwide Assembler (NASM) is an assembler and disassembler for the Intel x86 architecture. It can be used to write 16-bit, 32-bit (IA-32) and 64-bit (x86–64) programs. Since assembly language is used during the process, nasm is installed to compile assembly programs.

genisoimage

This pre-mastering program to generate ISO9660/Joliet/HFS hybrid filesystems. This is used to further describe the files in the ISO9660 filesystem to a Unix host, and provides information such as long filenames, UID/GID, POSIX permissions, symbolic links, and block and character device files. Genisoimage package is required to generate an ISO image file for the file system.

Bochs

Bochs is a highly portable open source IA-32 (x86) PC emulator written in C++. It includes emulation of the Intel x86 CPU, common I/O devices, and a custom BIOS. This is well suited for OS development due to its debugging features.

bochs-sdl

This package contains an SDL GUI plugin for Bochs.

Programming Languages

The operating system will be developed using the C programming language, using GCC. We use C because developing an OS requires very precise control of the generated code and direct access to memory. Other languages that provide the same features can also be used, but this article series will only cover C.

We’ll be using NASM as the assembler for writing assembly code. Bash will be used as the scripting language.

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

Booting is a startup sequence that starts the operating system of a computer when it is turned on. A boot sequence is the initial set of operations that the computer performs when it is switched on. Every computer has a boot sequence. The average computer doesn’t understand the boot sequence but is important to know for customizing and troubleshooting your computer.

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.

Boot Sequence

There is a standard boot sequence that all personal computers use. First, the CPU runs an instruction in memory for the BIOS. That instruction contains a jump instruction that transfers to the BIOS start-up program. This program runs a power-on self test (POST) to check that devices the computer will rely on are functioning properly. Then, the BIOS goes through the configured boot sequence until it finds a device that is bootable. Once BIOS has found a bootable device, BIOS loads the bootsector and transfers execution to the boot sector. If the boot device is a hard drive, it will be a master boot record (MBR). The MBR code checks the partition table for an active partition. If one is found, the MBR code loads that partition’s boot sector and executes it. The boot sector is often operating system specific, however in most operating systems its main function is to load and execute the operating system kernel, which continues startup. If there is no active partition or the active parition’s boot sector is invalid, the MBR may load a secondary boot loader which will select a partition and load its boot secotr, which usually loads the corresponding operating system kernel.

Boot Devices

The boot device is the device from which the operating system is loaded. A modern PC BIOS (Basic Input/Output System) supports booting from various devices. These include the local hard disk drive, optical drive, floppy drive, a network interface card, and a USB device. Typically, the BIOS will allow the user to configure a boot order. If the boot order is set to:

  1. CD Drive
  2. Hard Disk Drive
  3. Network

then the BIOS will try to boot from the CD drive first, and if that fails then it will try to boot from the hard disk drive, and if that fails then it will try to boot from the network, and if that fails then it won’t boot at all.

BIOS

BIOS (basic input/output system) is the program a computer’s microprocessor uses to start the computer system after it is powered on. It also manages data flow between the computer’s operating system (OS) and attached devices, such as the hard disk, video adapter, keyboard, mouse and printer. But, modern operating systems do not use the BIOS’ functions instead they use drivers that interact directly with the hardware, skipping the BIOS. Today, BIOS mainly runs some early diagnostics, a test referred to as a POST (Power-On Self-Test) that helps the computer meets requirements to boot up properly and then transfers control to the bootloader.

BootLoader

Computers powered by the central processing unit can only execute code found in the systems memory. Modern operating systems and application program code and data are stored on nonvolatile memories or mass storage devices. When a computer is first powered on, it must initially rely only on the code and data stored in nonvolatile portions of the systems memory. At boot time, the operating system is not really loaded and the computer’s hardware cannot peform many complex systems actions.

The program that starts the “chain reaction” which ends with the entire operating system being loaded is known as the boot loader (or bootstrap loader). The term creatively came from early designiners imagining that before a computer “runs” it must have it’s “boots strapped”. The boot loader’s only job is to load other software for the operating system to start. Often, multiple-stage boot loaders are used, in which several small programs of increasing complexity sequentially summon one after the other, until the last of them loads the operating system.

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.

Let’s start Development Process

Now let’s implement the smallest possible OS that can be used together with GRUB. The only thing the OS will do is write 0xCAFEBABE to the eax register.

Compiling the Operating System

This part of the OS has to be written in assembly code. Create a file named as loader.s and save the following code;

The only thing this OS will do is write the very specific number 0xCAFEBABE to the EAX register. It is very unlikely that this number would be in the register if the OS did not put it there.

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

nasm -f elf32 loader.s

Now you can see a file named “loader.o” in your working directory.

Linking the Kernel

The code must now be linked to produce an executable file. You want GRUB to load the kernel at a memory address larger than or equal to 0x00100000(1 megabyte (MB)), because addresses lower than 1 MB are used by GRUB, BIOS and memory-mapped I/O. Hence, the following linker script should be saved into a file called link.ld

The executable file can now be linked with loader.o with the following command;

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

Then the final executable file that is created is called as “kernel.elf”.

Obtaining GRUB

During the development process, the GRUB Legacy stage2_eltorito.bin bootloader will be used. You can download the file from this link.

Copy the file “stage2_eltorito.bin” to your working directory.

Building an ISO Image

The executable must be placed on a media that can be loaded by a virtual or physical machine. Here, ISO image files will be used as the media, but you can select the media depending on what the virtual or physical machine supports. You 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:

A configuration file menu.lst for GRUB must be created and then the following details must be saved.

This file tells GRUB where the kernel is located and configures some options.

Now, place the file “menu.lst” in the folder iso/boot/grub/.

Now the ‘iso’ folder should contain the following figure.

As the final step, ISO image “os.iso” can then be generated using the terminal 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 ISO image os.iso now contains the kernel executable, the GRUB bootloader, and the configuration file.

Running Bochs

After the completion of above steps, now we can run the OS in the Bochs emulator using the os.iso ISO image. Bochs needs a configuration file to start. So, a file named bochsrc.txt should be created and the following simple configuration is saved on it.

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.

Now you can run Bochs with the following command through the terminal;

bochs -f bochsrc.txt -q

Here the flag -f tells Bochs to use the given configuration file and the flag -q tells Bochs to skip the interactive start menu. Then run continue command in the interactive start menu.

You should now see Bochs starting and displaying a console with some information from GRUB 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.

Congratulations! Now you have successfully booted your own OS. Let’s meet with the next article of Develop Your Own OS series. Thank you so much for reading!

References:

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

--

--

Gayan Malinda

Software Engineering Undergraduate - University of Kelaniya, Sri Lanka