Build Your Own OS #2

Niroshan Pushparaj
5 min readJul 23, 2021

Implement_with_C

In the previous article we guided you to create a boot loader section of our operating system. In this article we are going to implement our OS with C language and assembly code. We can create OS with assembly only but why we introduce C language in this work. The main reason is assembly is machine-friendly language. It can control hardware easily, but we can’t handle this language easily. Therefore we use C language to implement process. This is easy for us to handle.

Setting up a stack
All non-trivial C programs use a stack. Setting up a stack is not harder than to make the esp register point to the end of an area of free memory that is correctly aligned . 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 or if the area esp would point to is used by something else. A better idea is to reserve a piece of uninitialized memory in the bss section in the ELF file of the kernel. It is better to use the bss section instead of the data section to reduce the size of the OS executable. Since GRUB understands ELF, GRUB will allocate any memory reserved in the bss section when loading the OS.

KERNEL_STACK_SIZE equ 4096            ; size of stack in bytes

section .bss
align 4 ; align at 4 bytes
kernel_stack: ; label points to beginning of memory
resb KERNEL_STACK_SIZE ; reserve stack for the kernel

A program can not pop an element from the stack without having pushed an element onto the stack first. Therefore, the memory locations of the stack will always be written to before they are being read. The stack pointer is then set up by pointing esp to the end of the kernel_stack memory:

mov esp, kernel_stack + KERNEL_STACK_SIZE   ; point esp to the start of the

After edit your loader.s file you can save it.

Creating a C function

Let's create simple addition C functions to use on our OS. The C file called kmain.c

int sum_of_three(int arg1, int arg2, int arg3)
{
return arg1 + arg2 + arg3;
}

Well now we are creating a C function, but this is a separate file. The main problem is how to use this function in our kernel. We can call C function from assembly code. There are many different conventions for how to call C code from assembly code. This article uses the cdecl calling convention, since that is the one used by GCC. The cdecl calling convention states that arguments to a function should be passed via the stack . The arguments of the function should be pushed on the stack in a right-to-left order, that is, you push the rightmost argument first. The return value of the function is placed in the eax register.

Again we open loader.s file and insert these codes:

external sum_of_three   ; the function sum_of_three is defined elsewhere

push dword 3 ; arg3
push dword 2 ; arg2
push dword 1 ; arg1
call sum_of_three ; call the function, the result will be in eax

After this process your file will look like this:

Compiling C Code

When compiling the C code for the OS, a lot of flags to GCC need to be used. This is because the C code should not assume the presence of a standard library, since there is no standard library available for our OS. For more information about the flags, see the GCC manual.

The flags used for compiling the C code are:

-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs

As always when writing C programs we recommend turning on all warnings and treat warnings as errors:

-Wall -Wextra -Werror

You already created a function kmain in a file called kmain.c that you call from loader.s . At this point, object file kmain probably won’t need any arguments.

Build Tools

Now we can setup some build tools to reduce our work load. It is use to compile and test run our OS. Create a simple Make-file for the OS called as Makefile :

The contents of your working directory should now look like the following figure (you can use tree command):

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

You should now be able to start the OS with the simple command make run, which will compile the kernel and boot it up in Bochs. If you have any trouble with this make-file you can simply delete the normal space (mentioned in error) and hit the TAB then it will work correctly.

OK now we have a question. Is our OS working properly??. We have a simple idea to check this. In our beginning process we assigned return value of the function is place to the eax register. So if we open bochslog.txt file we can see the return value 6 in the EAX part, Look like this:

if you saw this you can assume your OS is working properly. Hope you have successfully implemented your OS with C language. You can fine my code here. The OS can be developed continuously in the coming weeks. Keep In Touch!!!

Thank You!

--

--