Create Your Own Operating System

Malshani Dahanayaka
5 min readJul 23, 2021

PART 02 — How to use C instead of assembly code as the programming language for the OS

“The C language was actually created to move the UNIX kernel code from assembly to a higher-level language, which would do the same tasks with fewer lines of code.”

It’s certainly possible to write the entire OS in assembly language, but it would require a lot more time, effort, and knowledge, and the OS would have to be completely rewritten if you ever wanted to move it to another CPU architecture.

Using a high-level language, such as C, can make OS development much easier. The most common languages that are used in OS development are C, C, and Perl. C and C++ are the most common, with C being the most used. C, as being a middle-level language, provides high-level constructs while still providing low-level details that are closer to assembly language, and hence, the system. Because of this, using C is fairly easy in OS development. also, the C programming language was originally designed for system-level and embedded software development.

C is a structured programming language which allows a complex program to be broken into simpler programs called functions. It likewise permits free movement of data across these functions. And also, C is highly portable and is used for scripting system applications which form a major part of Windows, UNIX, and Linux operating systems.

In this article, I’m going to explain you how to use C instead of assembly code as the programming language for the OS. This is my second article of the “Create your own OS” article series. If you did not read my first article, you can read it from here to get an idea about setting up the development environment and booting a primitive operating system.

Let’s see how to implement our OS with C language.

Setting Up a Stack

What is the stack?

Stacks in computing architectures are regions of memory where data is added or removed in a last-in-first-out (LIFO) manner. In most modern computer systems, each thread has a reserved region of memory referred to as its stack. When a function executes, it may add some of its local state data to the top of the stack; when the function exits it is responsible for removing that data from the stack. At a minimum, a thread’s stack is used to store the location of a return address provided by the caller in order to allow return statements to return to the correct location. The stack is often used to store variables of fixed length local to the currently active functions.

Why we need a stack to create OS?

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

The NASM pseudo-instruction resb can be used to declare uninitialized data:

The stack pointer is then set up by pointing esp to the end of the kernel_stack memory:

Calling C code from assembly

In this section, we called the C function from the assembly. There are many different conventions for how to call C code from assembly code but in this article, we use cdecl calling convention since that is the one used by GCC. The arguments of the C function should be pushed on the stack in right-to-left order, that is, you push the rightmost argument first. The return value of the function is placed in the eax register.

The below code shows an example.

Updating loader.s file

After making codes for stack and arguments are pushed to C function we can update our loader.s file in OS.

Packing Structs

Padding aligns structure members to “natural” address boundaries — say, int members would have offsets, which are mod(4) == 0 on the 32-bit platform. Padding is on by default. It inserts the following “gaps” into your first structure:

Packing, on the other hand, prevents the compiler from doing padding — this has to be explicitly requested — under GCC it’s __attribute__((__packed__)), so the following:

would produce a structure of size 6 on 32-bit architecture.

A note: unaligned memory access is slower on architectures that allow it (like x86 and amd64), and is explicitly prohibited on strict alignment architectures like SPARC.

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:

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

Create kmain.c file

You can now create a function sum_of_three() in a file called kmain.c that you call from loader.s.

Build Tools

Make is a Unix utility that is designed to start the execution of a makefile. A makefile is a special file, containing shell commands, that you create and name makefile (or Makefile depending upon the system). While in the directory containing this makefile, you will type make run and the commands in the makefile will be executed.

The contents of your working directory should now look like the following file architecture:

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.

Then quit the bochs. Now use the command cat bochslog.txt to display the log produced by Boch. In displayed details, you will find EAX =00000006 in it. It is the hexadecimal representation of the sum of arguments provided for the C function in the kmain.c file.

Now you have successfully implemented your OS with C language. Hope you get a clear idea on how to use C instead of assembly code as the programming language for your own OS.

Reference: Helin, E., & Renberg, A. (2015). The little book about OS development

Thank You for reading…

— Malshani dahanayaka —

--

--

Malshani Dahanayaka

Software Engineering Undergraduate of University of Kelaniya