IMPLEMENTING A LITTLE OS #1
A beginners guide to Operating System — Part 1 (Booting the OS)
An operating system (OS) is software that manages computer hardware and system resources and provides tools for applications to run. The birth of operating systems meant that programs no longer had to be written to control the entire scope of computer operations. The purpose of this article is to describe step by step how to build our own operating system. Developing an operating system (OS) is not an easy task. Hence, this write-up will be posted section by section.
Let’s see what tools and technologies we use in our implementation.
Tools and Technologies…
- We are using Ubuntu as our Host Operating System. It is a good practice to install the host operating system in a virtual machine like VirtualBox to test OS.
2. Once Ubuntu is installed, either physical or virtual, the following packages should be installed using the terminal.
sudo apt-get install build-essential nasm genisoimage bochs bochs-sdl
3. The C programming language will be used along with GCC to create the operating system. We chose C because creating an OS involves direct access to memory. We have chosen NASM as the assembler for writing assembly code and Bash as the scripting language.
4. Virtual Machine — When creating an operating system it is much easier to be able to run code in a virtual environment than on a physical computer. That’s because booting from a virtual machine is significantly faster than running your OS on a physical disk. We will use Bochs Emulator as our virtual machine.
Booting Process…
Booting an operating system consists of transferring control through a chain of small programs, each one being more powerful than the previous one, where the operating system is the final “program”. The main programs in this process include BIOS, Bootloader, and OS. The operating system is the last and most powerful one.
BIOS…
The PC will launch a program that follows the Basic Input Output System (BIOS) standard as soon as it is turned on. BIOS mostly performs some initial diagnostics (power-on-self-test) before handing off the command to the bootloader. The program’s original purpose was to export some library features for screen printing, keyboard input reading, etc.
Bootloader…
The BIOS program transfers control of the computer to a program called the bootloader. The bootloader’s job is to hand over control to us, the operating system developers, and our code.
Due to some hardware limitations and backward compatibility, the bootloader is often split into two parts: the first part of the bootloader will transfer control to the second part, and finally give control of the computer to the operating system.
The operating system can be executed using GRUB as a regular ELF, which GRUB loads into the appropriate memory region. A certain approach to memorizing the code is required in order to compile the kernel. The current bootloader for the operating system is GRand Unified Bootloader for GNU (GRUB).
Now we’ll work on developing the simplest operating system feasible. The only thing this OS will do is write OxCAFEBABE to the EAX register.
Writing and Compiling
This part of the OS should be developed in assembly language. We cannot use C language as it requires a stack, which is not available.
Save the following code in a file called ‘loader.s’.
The only thing this operating system does is write the specified number 0xCAFEBABE to the EAX register. When we boot the OS, we can check whether the number has been successfully written in the register.
Next, use the following command to compile loader.s into a 32-bit ELF object file.
nasm -f elf32 loader.s
Now the file is 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.
Create a file called ‘link.ld’ with the link 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.
Obtaining GRUB
More specifically, the GRUB Legacy stage2_eltorito bootloader will be used. After downloading the binary file copy the file stage2_eltorito to the folder that already contains loader.s and link.ld.
Building an ISO Image
An executable must be stored on a virtual or physical machine-readable medium. For that, we will use ISO image files of our project. We can use genisoimage program to create the image.
Create a folder containing the files from the ISO image. 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 create a GRUB configuration file called ‘menu.lst’. This file should instruct GRUB to find the kernel and set various options. Use the following configuration for the file:
Place the ‘menu.lst’ file in the folder ‘iso/boot/grub/’. 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 called ‘os.iso’ will be generated. This contains the kernel executable, the GRUB bootloader, and the configuration file.
Running 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 need a configuration file. Below is a simple configuration file.
Depending on how install Bochs, you may need to adjust the path to roimage and vgaromimage. Save the configuration file as ‘bochsrc.txt’ and run Bochs with the following command:
bochs -f bochsrc.txt -q
Then we will get a window like this.
However, if the above window does not appear, then you must change the display_library: sdl
to display_library: sdl2
in file bochsrc.txt. Then the error will be fixed.
Bochs should now run and display some information on the console. After leaving Bochs, display the log produced by Boch:
cat bochslog.txt
The contents of the CPU’s registers simulated by Bochs should now appear somewhere in the output. If you see RAX=00000000CAFEBABE or EAX=CAFEBABE in the output then your OS has booted successfully.
Congratulations! We just created a simple Operating System. Let’s come up with the next part of this os developing series.
References…
The Little OS Book: https://littleosbook.github.io/book.pdf