Writing your own Operating System with C

Hasini Samarathunga
5 min readJul 22, 2021

This is the second article in a series of articles explaining the development of an x86 Operating System step-by-step. In the previous article, I explained how to implement the smallest possible OS that can be used together with GRUB. The only thing possible with that OS was to write 0xCAFEBABE to the eax register. If you seem lost I recommend going back to this article below.

This article will explain how to use C instead of assembly code as the programming language for the OS. Although using assembly gives us the maximum control over every aspect of the code and is the best for interacting with the CPU, C is a much more convenient language to use for programmers.

Now let’s get to the actual coding!

Setting Up a Stack

The first step is setting up a kernel stack. Setting up a stack is quite simple. We only need to point the esp register to the end of an area of free memory that is correctly aligned. Here alignment on 4 bytes is recommended for better performance. Now the question is, where to point the esp ?

At the start, the only things in the memory are GRUB, BIOS, the OS kernel, and some memory-mapped I/O. So we can’t point the esp to a random area in memory because we don’t know the availability of that memory space.

Therefore, we reserve a piece of uninitialized memory in the bss section in the ELF file of the kernel for our stack. Here the bss section is used instead of the data section to reduce the size of the OS executable.

The NASM pseudo-instruction RESB, RESW, RESD, RESQ, REST, RESO, RESY and RESZ are designed to be used in the BSS section of a module: they declare uninitialized storage space.

Here we use resb to declare the Kernel Stack size.

Add the below code to your loader.s file to declare the stack.

And add the below code to set up the stack pointer. This is done by pointing esp to the end of the kernel_stack memory.

Now your loader.s file should look like below.

Calling C Code From Assembly

Next step we need to call a C function from assembly code.

First, create a C file called kmain.c. And create a function kmain. Keep the with no return value and arguments for now.

To call the C function use pseudo instruction extern to tell the assembler function is defined elsewhere. Add the below code to the loader.s file.

extern kmain               ; the function kmain is defined elsewhere

Then add the below code to the loader label to call the kmain function.

call kmain                 ; call the function

Compiling C Code

When compiling the C code for the OS, a lot of flags to GCC need to be used because otherwise, the C code might assume the presence of a standard library.

The flags used for compiling the C code are -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs

Keep in mind that it is highly recommended to turn on all warnings and treat warnings as errors when writing C programs.

Build Tools

In order to make compiling and test running the OS easier, we use the make build system.

Create the Makefile given below.

The contents of your working directory should now look like,

    .
|-- bochsrc.txt
|-- iso
| |-- boot
| |-- grub
| |-- menu.lst
| |-- stage2_eltorito
|-- kmain.c
|-- loader.s
|-- link.ld
|-- Makefile

Now you should be able to start the OS with the simple command make run . This will compile the kernel and boot it up in Bochs.

Writing to EAX from C function

Now let’s make our C function return a value that will be written in the EAX.

First, let’s modify our kmain function to return the summation of three arguments given to it.

Now let’s pass some arguments into our kmain function. Here we use the cdecl calling convention as it is the convention used by GCC. And it states that the arguments to a function should be passed through the stack (on x86) and should be pushed on the stack in a right-to-left order.

Your loader.s file should look like below.

Now when you boot up the Bochs and then display the log produced by Boch (using cat bochslog.txt), you will find the summation EAX=6 written in it.

You can change the arguments pushed to the stack with any number and the summation will be written on EAX in hexadecimal. Below is the result of pushing 2,3, and 5.

Hope you found it easier to follow these steps and learned about using C instead of assembly code in developing an OS. You can obtain the complete code from my GitHub below.

Hope to see you in the next article as well!

Thank you!

Reference : Helin, E., & Renberg, A. (2015). The little book about OS development

--

--