Creating a Kernel from Scratch

Theo
4 min readJan 25, 2021

--

A short tale of creating a barebone Linux-like Kernel

Find the repo here 👉 https://github.com/RockoonTechnologies/Kernel

The Kernel is the pillar of the Operating System and is the lowest of the low-level layers. It's what actually communicates directly with the hardware. Therefore the construction and development of one is essential for virtually any process, but we take this amazing layer of software for granted.

Therefore not only did I set out in attempt to learn about this, I also really wanted to try to make one (a great idea for someone with minimal C/C++ and assembly experience).

The Beginning

I started, naturally with research. I looked around the tons of websites (all look like they were written before the 2000’s) and quickly got lost.

Luckily I found a lifeline with this article from Linux Journal, written by Petros Koutoupis.

Majority of the code he demonstrated, and the methods he used we will be exploring today.

This article is more a code overview, actual concepts can be found in the article

Assembly (yay…)

This will be fun…

I won't get too terribly far into this, because I would not consider myself one to really teach it.

In the tree of code, assembly is the root and directly translates to actual binary. However, this “readable” code is terrible to code. Nevertheless, here is the Assembly that we are going to use:

Boot.asm

bits 32

section .multiboot ;according to multiboot spec
dd 0x1BADB002 ;set magic number for
;bootloader
dd 0x0 ;set flags


section .text
global start
extern main ;defined in the C file

start:
cli ;block interrupts
mov esp, stack_space ;set stack pointer
call main
hlt ;halt the CPU

section .bss
resb 16000 ;16KB for stack
stack_space:

The most simplistic way to see this is simply: we start the computer, setup our environment, get a C function (main) then on start we allocate memory for our stack then call the C function.

The Actual Kernel

So now that our boot is handled, we can begin actually doing stuff.

For ease, I created multiple other files that handle some of the harder stuff for us.

The file that's called first is kernel.c, and looks like so:

#include "source.h"
#include "keyboard.h"
void main(void)
{
terminal_buffer = (unsigned short*)VGA_ADDRESS;
vga_index = 0;
clear_screen();
print_string("Hello world!", YELLOW);
vga_index = 80;
print_string("Version 1", RED);
vga_index = 160;
print_char('b', RED);
vga_index = 240;
while (1) {
keyboard_handler(); }return;
}

Let’s review this.

So first we call we create a terminal buffer. This is a memory buffer that we will throw all of our display data in. As you can see we will be using VGA.

Then we set our index at 0. Think of this as our cursor, the location of our text.

Lastly, we clear our screen before we print the “Hello world!” to the terminal, in yellow. You can see us incrementing our “cursor” each time we print.

Lastly, we enter an infinite loop where we ask for keyboard input (this will be explored later).

Now, all these functions, print_string; clear_screen are sadly not stock and are functions in the source.c.

Lets see what that looks like:

#include "source.h"#define VGA_ADDRESS 0xB8000#define BLACK 0
#define GREEN 2
#define RED 4
#define YELLOW 14
#define WHITE_COLOR 15
unsigned short* terminal_buffer;
unsigned int vga_index;
void clear_screen(void)
{
int index = 0;
/* there are 25 lines each of 80 columns;
each element takes 2 bytes */
while (index < 80 * 25 * 2) {
terminal_buffer[index] = ' ';
index += 2;
}
}
void print_string(char* str, unsigned char color)
{
int index = 0;
while (str[index]) {
terminal_buffer[vga_index] = (unsigned short)str[index] | (unsigned short)color << 8;
index++;
vga_index++;
}
}
void print_char(char str, unsigned char color)
{
int index = 0;

terminal_buffer[vga_index] = str | (unsigned short)color << 8;
index++;
vga_index++;

}

As you can see, these functions are actually what do the majority of the magic and are actually quite simple. In the most simple of terms, we are writing characters to the video memory, causing them to show on our screen. This also includes our definition for colors that are used in the process above.

Lastly, we have a keyboard.c. As the name suggests this handles basic keyboard input.

It's long alright, but that's only because we need to decipher each key input separately and print it to the console. What we do is poll the keyboard to see if new input is available, then resolve its hexadecimal to a char.

More information can be found here: http://kernelx.weebly.com/getting-keyboard-input.html

All this combined creates a visually responsive kernel that's is surprisingly simple.

Building

So we have a all these files, but how do I turn this into a image I can use on a VM.

To build this as a ISO follow the following steps
(ill make a cmake file soon, ik ik)

First lets Build the assembly file into a object file
```
$ nasm -f elf32 boot.asm -o boot.o
```

Build Kernel.C into a object

```
$ gcc -m32 -c kernel.c -o kernel.o
```

Build Keyboard.C and Source.C
```
$ gcc -m32 -c Keyboard.c -o Keyboard.o
$ gcc -m32 -c Source.c -o Source.o
```
Link everything into one executable
```
ld -m elf_i386 -T linker.ld -o kernel boot.o kernel.o Keyboard.o Source.o
```

Build the .iso


mkdir -p iso/boot/grub
cp kernel iso/boot/
cp grub.cfg iso/boot/grub/
grub-mkrescue -o my-kernel.iso iso/

--

--