Build your own Operating System(1)

Nimesha kavindi
7 min readOct 1, 2022

--

The operating system is the most essential computer software that manages hardware and software resources. An operating system reduces the barrier of managing tasks and their resources, providing the interfaces for various hardware and software components.

Firstly, want to say developing an operating system is probably one of the most challenging things you can do on a computer. But if you start this development step by step then you can systematically do this. Since this is a complex task, we can divide the whole into several parts.

As a first step, we want to set up your development environment and boot the operating system. To do that firstly, want to decide what tools and technologies we going to use in our implementation.

Tools that need to development

Host operating system

We will be using Ubuntu as our host operating system for OS development. If we install your host OS on a virtual machine like virtualbox to test your OS it will be good practice.

Package

Once you have installed ubuntu physical or virtual, the following packages should be installed using the terminal;

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

Programming language

We use C because developing an OS requires very precise control of the generated code and direct access to memory. Therefore, we will use the C programming language using the GCC compiler. Other programming languages that provide the same feature can also be provided.

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

Bash will be used as the scripting language.

Virtual Machine

When developing an OS, it is very convenient to be able to run your code in a virtual environment instead of on a physical computer. If we consider, starting OS in a virtual machine and getting your OS onto a physical medium and then running into a physical machine starting OS in a virtual machine is much faster. For debugging features Bochs emulator is well suited for OS development.

Booting process

Booting an operating system involves transferring control along a chain of small programs, each one more powerful than the previous one. In this example, each box is a program.

BIOS (Basic Input Output System)

When your computer is turned on, a small program that follows BIOS standards is launched. Usually, this program is kept on the computer’s motherboard on a read-only memory chip. BIOS mainly runs some early diagnostics and then transfers control to the bootloader.

Bootloader

A program known as a bootloader will take over operating the computer from the BIOS program. Transferring control to us, the programmers of the operating system and our code is what the bootloader’s job is. However, the bootloader is frequently divided into two parts: the first part of the bootloader will transfer control to the second section, which ultimately hands control of the PC to the operating system.

An existing bootloader will be used the GNU: GRand Unified Bootloader because writing a bootloader involves writing a lot of low-level code that interacts with the BIOS. The operating system can be created using GRUB, and GRUB will load it into the appropriate memory region. The code must be organized in memory in a specified way before the kernel can be built.

The operating system

GRUB will leap to a location in memory and hand off control to the operating system. In order to make sure that it is indeed jumping to an OS and not some random code, GRUB will check 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.

Hello Cafebabe

The implementation of the smallest OS that may be used with GRUB will be covered in this part. The OS will only write 0xCAFEBABE to the eax register as its single action.

Compiling the Operating System

We can’t use C language since it requires a stack, which isn’t available because this part of the operating system has to be written in assembly language.

Firstly, we want to get a text file and save it as ‘loader.s’ and fill it with the following code:

The only thing this OS will do is write the very specific number 0xCAFEBABE to the eax register.

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

nasm -f elf32 loader.s

After doing that now you should see a file named ‘loader. o’.

Linking the Kernel

After compiling the above command, the code must now be linked to produce an executable file. We want GRUB to load the kernel at a memory address more than or equal to 0x00100000 since GRUB, BIOS, and memory-mapped I/O use addresses less than 1 megabyte (MB) (1 MB). The linker can be used by running the script below.

Firstly, we want to create a text file and save it as a link.ld and put the following code:

Then, The executable can now be linked with the following command:

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

After doing that the final executable will be called the kernel.elf.

Obtaining GRUB

Since both GRUB 2 and GRUB Legacy can be used to create the OS ISO image, GRUB Legacy is the version of GRUB we will employ. More specifically, the stage2 eltorito bootloader for GRUB Legacy will be used.

Then copy the file stage2_eltorito to the folder that already contains loader.s and link.ld.

Building an ISO Image

The executable needs to be stored on a medium that a physical or virtual machine can load. The media used in this book will be ISO image files, however, floppy images can also be used if the virtual or physical system supports them.

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 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:

Place the file menu.lst in the folder as follow:

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

Running Bochs

The os.iso ISO image can now be used to run the OS in the Bochs emulator. Bochs needs a configuration file to run, and one is provided below as an example of a straightforward configuration file:

Depending on how you installed Bochs, you may need to adjust the path to romimage and vgarominage. You can found at Boch’s official website.

bochs -f bochsrc.txt -q

If you saved the configuration in a file named bochsrc.txt then you can run Bochs with the 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 following command:

cat bochslog.txt

You should now see the contents of the registers of the CPU simulated by Bochs somewhere in the output. If you find RAX=00000000CAFEBABE or EAX=CAFEBABE but it is depending on if you are running Bochs with or without 64-bit support in the output then your OS has successfully booted!.

So after doing these steps one by one now you created a simple operating system. In the next blog article, you will be able to see how to use the C programming language to further develop your OS.

REFERENCES:

· The little OS book: https://littleosbook.github.io/book.pdf

FURTHER READINGS:

· http://duartes.org/gustavo/blog/post/how-computers-boot-up

· http://duartes.org/gustavo/blog/post/kernel-boot-process

· http://wiki.osdev.org/Boot_Sequence

--

--