Build Your Own OS #2
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!