Implementing Your Own Operating System

Asitha Nuwan
5 min readJul 23, 2021

--

Hello everyone,

From the previous article,Explained the steps to set up the booting part of the operating system.

You can read it here:

Now I’m going to explain the implementation of the operating system. We implement C language instead of Assembly language because C language is much more user friendly than Assembly language. So we can make the development process easier.

Let’s get started!

Step 1:Setting Up a Stack

One prerequisite for using C is a stack since 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 (Remember that the stack grows towards lower addresses on the x86)

Reversing 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 that we made earlier.after that we need to setup the stack pointer by pointing esp to the end of the kernel_stack memory. To do that you need to add the following statement inside the loader:

mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
; stack (end of memory area)

After that your file look like this:

global loader ; the entry symbol for ELF

MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant
FLAGS equ 0x0 ; multiboot flags
CHECKSUM equ -MAGIC_NUMBER ; calculate the checksum
; (magic number + checksum + flags should equal 0)
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

section .text: ; start of the text (code) section
align 4 ; the code must be 4 byte aligned
dd MAGIC_NUMBER ; write the magic number to the machine code,
dd FLAGS ; the flags,
dd CHECKSUM ; and the checksum

loader: ; the loader label (defined as entry point in linker script)
mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
;stack (end of memory area)
; The assembly code
extern 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

.loop:
jmp .loop ; loop forever

Step 2:Calling C Code From Assembly

Since we are using C language,we need to call the C code from the assembly code.there are so many ways to do that but we will use calling convention,since that is the one used by GCC.This indicate that the parameters of the function should be passed thorough the stack (on x86).

The parameters of this function should be pushed in the stack in the order form rigth to left,that is,you push the rightmost argument first.The return value of the function is placed in the eax register.

This is a simple C function:

/* The C function */
int sum_of_three(int arg1, int arg2, int arg3)
{
return arg1 + arg2 + arg3;
}

You can call the function like this:

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

In your project directory,create an empty file called kmain.c .you can do it with with touch kmain.c command.you can keep this file empty for now.

Step 3:Compiling The C Code

Many flags to GCC must be used while compiling the C code for the OS.This is because the C code should not assume the presence of a standard library because our OS does not have one.

Following are the flags used for compiling the C code:

-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs-Wall -Wextra -Werror

And we recommend you to turn on all warnings and treat warnings as errors by adding these flags.

-Wall -Wextra -Werror

I provide some information for you about flags below:

-Wall -Wextra –Werror ; turning on all warnings and treat warnings as errors-nostdlib ; Do not use the standard system startup files or libraries when linking.-nostartfiles ; Do not use the standard system startup files when linking. The standard system libraries are used normally, unless -nostdlib, -nolibc, or -nodefaultlibs is used.-fno-stack-protector ; there will be a little more space allocated on the stack and a little more overhead on entry to and return from a function while the code sets up the checks and then actually checks whether you’ve overwritten the stack while in the function.-nolibc ; Do not use the C library or system libraries tightly coupled with it when linking.

Step 4:Build Tools

Now it is also a good time to setupsome build tools to make it easier to compile and test-run the OS.Execute touch Makefile to create the fil.

A Makefile is as follows:

OBJECTS = loader.o kmain.o

CC = gcc

CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \

-nostartfiles -nodefaultlibs -Wall -Wextra -Werror -c

LDFLAGS = -T link.ld -melf_i386

AS = nasm

ASFLAGS = -f elf

all: kernel.elf

kernel.elf: $(OBJECTS)

ld $(LDFLAGS) $(OBJECTS) -o kernel.elf

os.iso: kernel.elf

cp kernel.elf iso/boot/kernel.elf

genisoimage -R \

-b boot/grub/stage2_eltorito \

-no-emul-boot \

-boot-load-size 4 \

-A os \

-input-charset utf8 \

-quiet \

-boot-info-table \

-o os.iso \

iso

run: os.iso

bochs -f bochsrc.txt -q

%.o: %.c

$(CC) $(CFLAGS) $< -o $@

%.o: %.s

$(AS) $(ASFLAGS) $< -o $@

clean:

rm -rf *.o kernel.elf os.iso

Save the file in Makefile .note that you have to do all the indentations with tabs,not with spaces.

After these steps,your file structure should look like this :

.
| — bochsrc.txt
| — iso
| | — boot
| | — grub
| | — menu.lst
| | — stage2_eltorito
| — kmain.c
| — loader.s
| — Makefile5

Now, you should be able to run the operating system 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 operating system has successfully booted.

Now the second part of developing your own OS is successfully finished. Hope to see you form another article.

Thank You!

--

--

No responses yet