The XamOS — Let’s Build an OS!!!

Nipuni Perera
5 min readJul 21, 2021

The 2nd step of OS development — Implement with C

No doubt !!!, building our own operating system is a big challenge. As a result, this article will assist you in using C programming language instead of assembly code as the OS programming language in the step-by-step process of building your own Operating system.

If you haven’t already, go here to read the first article in the series on setting up the development environment and booting a rudimentary operating system.

So, let’s see how we can use C instead of assembly code as the programming language for the OS.

Ubuntu Guest Virtual Machine Vs Windows Host Machine

2.1)Setting up a Stack-

Setting up a stack is as simple as pointing the esp register to the end of a perfectly aligned region of free memory.
Because the only things in memory right now are GRUB, BIOS, the OS kernel, and some memory-mapped I/O, we could point esp to any arbitrary location in memory. This isn’t a smart idea since we don’t know how much memory is accessible or whether the place esp would refer to is already occupied.
A better solution is to set aside some uninitialized memory in the kernel’s ELF file bsssection. To decrease the size of the OS executable, the bsssection should be used instead of the data section. GRUB will assign any memory allocated in the bsssection when launching the OS since it recognizes ELF.
To declare uninitialized data, use the NASM pseudo-instruction resb:

Insertion to the loader.s should be done as shown above as it’s a declaration followed by the resb pseudo-instruction;

This stack pointer is then initialized by referring espto the kernel_stack memory’s end:

2.2) Continuing to call C code From Assembly-

The next step is to call a C function from the assembly code. Because GCC uses the cdecl calling convention, I’m using it here. Arguments to a function should be provided through the stack, according to the cdecl calling convention (on x86).
The function’s arguments should be placed on the stack in a right-to-left sequence, with the rightmost parameter being pushed first. The function’s return value is stored in the eax register.
An example may be found in the code below:

saved the codes in loader.s file
How the loader.s file will look like

Structures for Packing-
We frequently encounter “configuration bytes,” which are a set of bits in a specified sequence. The following is a 32-bit example:

Bit:     | 31     24 | 23          8 | 7     0 |
Content: | index | address | config |

It is considerably more convenient to utilize “packed structures” instead of an unsigned integer, unsigned int, for managing such setups.

When utilizing thestruct in the preceding example, the size of the struct is not guaranteed to be exactly 32 bits; the compiler could add padding between elements for a variety of reasons, such as to speed up element access or to meet hardware and/or compiler requirements. Because the struct will eventually be interpreted as a 32-bit unsigned integer by the hardware, it is critical that the compiler does not add any padding when using it to represent configuration bytes. The packed property can be used to prevent GCC from adding any padding:

It’s worth noting that __attribute__((packed)) isn’t part of the C standard, thus it might not be compatible with all C compilers.

2.3)Compiling C code-

Many flags to GCC must be used while building the C code for the OS. This is because the C code should not presume the availability of a standard library because our operating system does not have one.

The following flags are used to compile C code:

We usually advocate turning on all warnings and treating warnings as errors when creating C programs:

You may now call thekmain function fromloader.s by putting it in a file called kmain.c. kmain is unlikely to require any arguments at this time.

2.4)Tools for Construction-

Now we can put up some build tools to make compiling and testing the OS easier. We suggest make, but there are a variety of different build systems to choose from.
The following is an example of a basic Makefile for the OS:

The simple Makefile you created for the OS should look like this.

The contents of your working directory should now appear as shown in the diagram:


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

You should now be able to start the OS by typing, make run which will compile the kernel and start it in Bochs.

So it leads to the end of today's article which was based on the second step of Operating system development —Implement with C.

Thank you very much for reading!

I’ll hope to get back to you with the chapter three, “integrate_outputs” as soon as possible.Till then,

Stay Safe!!!

-Nipuni Perera-

--

--

Nipuni Perera

As a Software Engineering undergrad at the University of Kelaniya SL , I share insights on coding, dev methodologies & emerging tech. Join me on my journey!