Build your own Operating System #2_implementing_C
In the previous article, we guided you to setup the booting part of our operating system. In this article, we are going to implement C language to our project instead of Assembly language. Assembly is a very good programming for interacting with CPU and other hardware resources. But, C is much more human-friendly language when compared with Assembly language. So, we decided to use C as much as possible to make the development process easier and assembly language will be used only where it make sense.
So, let’s get started!
Setting up a stack
Since all non-trivial(not lightweight) C programs use a stack, and setting up a stack is not harder than to make the esp
register point to the end of an area of free memory. So far, in this development process, the only things in memory are GRUB, BIOS, the OS kernel, and some memory mapped I/Os. This is not a good thing to do; because, we don’t know how much memory is available or if the esp
pointed memory area is used by something else.
Reserving a piece of uninitialized memory in the bss
section in the ELF file of the kernel will be a solution. And also, this will reduce the OS executable size.
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
Add this section to loader.s
file.
And then, we need to setup the stack pointer by pointing esp
to the end of the kernel_stack
memory. In order to do that, you need to add the following statement inside the loader:
block you your loader.s
file.
mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
; stack (end of memory area)
After all, loader.s file will look like this:
Calling C code from the Assembly
Since we are using C language, we need to call the C code from the Assembly code. There are many different ways to do that. But in here, we will use cdecl
calling convention. According to this convention, 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. And, the return value of the function is placed in the eax
register.
For example, to call the following function in Assembly:
/* The C function */
int sum_of_three(int arg1, int arg2, int arg3)
{
return arg1 + arg2 + arg3;
}
You have to call it like this.
; The assembly code
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, result will be in eax
In your project directory, create an empty file called kmain.c
. You can do it with touch kmain.c
command. You can keep this file empty for now.
Compiling the C code
The next step will be this. For normal compilations, we can use gcc fileName.c -o objectName
. But, in this case, we are compiling them for an operating system. So, we have to use a lot of flags as below.
-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs
And, we recommend you to turn on all warnings and treat warnings as errors by adding these flaags:
-Wall -Wextra -Werror
Build tools
This is the last step for this article. In this step, we are going to build the OS. Previously, we used a lot of commands to compile each and every file separately, build the ISO image, and run the OS in bochs
emulator. But, we can do it in an easier way. In order to do that, you have to create a separate file to execute those commands. Execute touch Makefile
to create the file.
Save this file in Makefile
. Note that you have to do all the indentations with tabs, not with spaces.
After all of these steps, your file structure should look like this.
.
|-- bochsrc.txt
|-- iso
| |-- boot
| |-- grub
| |-- menu.lst
| |-- stage2_eltorito
|-- kmain.c
|-- loader.s
|-- Makefile
Now, you should be able to run the OS in the bochs
emulator by executing the simple command make run
. This will compile the kernel and boot it up. Then check the bochslog.txt
to find RAX=00000000CAFEBABE
or EAX=CAFEBABE
to make sure that your OS has successfully booted.
You can download a completed code that I have created for booting my OS from: here
Hope you have successfully implemented C to OS and hope to catch you in the next article.
Thank you!