IMPLEMENTING A LITTLE OS #2
A beginners guide to Operating System — Part 2 (Start implementing with C)
Welcome to the second episode of my operating system development series. You may read how to set up the development environment and boot the simplest OS we can construct in my first article, which was published last week.
I’ll demonstrate in this article how to create a C program for our kernel and call it from Assembly code. Therefore, in the future, we can design our OS using C and only use Assembly as necessary.
Setting up a stack
One prerequisite for using C is a stack since all non-trivial C programs use a stack. To form a stack we need to point the esp register to the end of a properly aligned free memory area.
We could point esp to a random area in memory since, so far, the only thing in the memory is GRUB, BIOS, the OS kernel, and some memory-mapped I/O. This is not a good idea — we don’t know how much memory is available. A better idea is to reserve a piece of uninitialized memory in the bss section of the kernel.
The NASM pseudo-instruction resb can be used to declare uninitialized data:
Copy the above code into the file loader.s, right after the last definition (CHECKSUM).
Then set the stack pointer by pointing esp register to the end of the kernel_stack memory. To do this, replace the instructions in the loader label in the loader.s file with the following code.
Calling C Code From Assembly
Below, the C function will take three integer arguments and return the sum. Copy the following code into a new file named kmain.c. This will be the source file of our first C code that we will call from our assembly code.
Using assembly to call a C function
Just writing a C function is not enough. We need to call it using assembly.
We will use the cdecl calling convention because it is the one used by GCC. The arguments to a C function should be pushed to the stack in right-to-left order, with the rightmost argument being pushed first. The function’s return value will be stored in the eax register.
To accomplish the task, place the following code after the esp instruction in the loader.s file.
Compiling C code
When compiling the C code for the OS, GCC needs to use a lot of flags. This is because C code should not assume there is a standard library as there is none for our operating system. In addition, we should enable all warnings and treat them as errors. When compiling the C code, we use the following flags to achieve the above goals.
(We will use these in the Makefile which we are going to create in the next step):
-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector
-nostartfiles -nodefaultlibs -Wall -Wextra -Werror
Set up Build Tools
Now we’ll set up some build tools to make it easier to compile and test our operating system. We will use Make as our build system.
Here is a simple Makefile for our OS. Create a file named Makefile and save the above text in that file.
Following is the loader.s file after all the modifications have been done:
The contents of your working directory should now look like the following figure:
Now you should be able to start the OS with the simple command run, which will compile the kernel and start it in Bochs.
Now we can use C language to develop our operating system. This is a huge achievement considering how easy it is compared to developing an OS with only Assembly Language.
Let’s come up with the next part of this os developing series.
References ….
The Little OS Book: https://littleosbook.github.io/book.pdf