Simple Kernel in C

Sometimes it’s better to take one step back in order to take two step forwards. In the long journey until a get a simple Kernel in C++, I decided to start with a simple Kernel in C in order to understand how things works. Getting a C++ Kernel, even the simplest one, can be difficult as you will have no STL in the kernel, no new or delete operators, no virtual methods, no exceptions!

All the kernels (at least the one I know) have a few files in common:

  • a boot loader written in assembly
  • a kernel (may be written in C,C++,Basic, Pascal)
  • a linker file

The boot loader it is the entry point for the kernel. It prepares things in order to call a function defined in a C file. It glue things between your kernel and the boot loader.

kernel_loader.s

.global loader # making entry point visible to linker

# setting up the Multiboot header - see GRUB docs for details
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum required

.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

# reserve initial kernel stack space
.set STACKSIZE, 0x4000 # that is, 16k.
.comm stack, STACKSIZE, 32 # reserve 16k stack on a quadword boundary

loader:
mov $(stack + STACKSIZE), %esp # set up the stack
push %eax # Multiboot magic number
push %ebx # Multiboot data structure

mov $start_ctors, %ebx # call the constructors
jmp 2f
1:
call *(%ebx)
add $4, %ebx
2:
cmp $end_ctors, %ebx
jb 1b

call kmain # call kernel proper

mov $end_dtors, %ebx # call the destructors
jmp 4f
3:
sub $4, %ebx
call *(%ebx)
4:
cmp $start_dtors, %ebx
jb 3b

cli
hang:
hlt # halt cpu
jmp hang

 

The Kernel will be coded in kernel.c. Where you can find that there is a Magic Number check, and if things goes ok, it will print some messages into the screen writing directly to the Video Memory.

#define KERNEL_MAGIC_NUMBER 0x2BADB002

#include "kernel_video.h"

extern "C" void kmain( void* mbd, unsigned int magic )
{
if ( magic != KERNEL_MAGIC_NUMBER )
{
kvideo_write_char('n');
kvideo_write_str((char*)"Kernel Panic. Invalid Magic Number");

return;
}

// Locked and Loaded
kvideo_write_char('n');
kvideo_write_str((char*)"loading Kernel");
kvideo_write_char('n');
kvideo_write_str((char*)"Work In Progress.");
}

 

The video functions are coded in the kernel_video files. And basically let us write strings and handle some non printable chars.

kernel_video.h

#ifndef _KVIDEO_H_
#define _KVIDEO_H_

#define _KVIDEO_HORIZONTAL_CHARACTERS 80
#define _KVIDEO_VERTICAL_LINES 25
#define _KVIDEO_RAM_SIZE 2000

void kvideo_clrscr();
void kvideo_handle_non_printable_char(char c);
void kvideo_write_char(char c);
void kvideo_write_str(char* str);

#endif

kernel_video.c

#include "kernel_video.h"

unsigned short* _kvideo_ram = (unsigned short*)0xB8000; // where the video ram starts
unsigned int _kvideo_block_offset = 0;
unsigned int _kvideo_block = 0;

void kvideo_clrscr()
{
unsigned int i = 0;
for (;i < _KVIDEO_RAM_SIZE;i++)
{
_kvideo_ram[i] = (unsigned char)' ' | 0x0700;
}
_kvideo_block_offset = 0;
_kvideo_block = 0;
}

void kvideo_handle_non_printable_char(char c)
{
if (c == 'n')
{
_kvideo_block += _KVIDEO_HORIZONTAL_CHARACTERS;
_kvideo_block_offset = 0;
}
else if (c == 'r')
{
_kvideo_block_offset = 0;
}
}

void kvideo_write_char(char c)
{
if (c < 30)
{
kvideo_handle_non_printable_char(c);
return;
}
if (_kvideo_block_offset >= _KVIDEO_HORIZONTAL_CHARACTERS)
{
_kvideo_block_offset = 0;
_kvideo_block += _KVIDEO_HORIZONTAL_CHARACTERS;
}

if (_kvideo_block >= _KVIDEO_RAM_SIZE)
{
kvideo_clrscr();
}
_kvideo_ram[_kvideo_block + _kvideo_block_offset] = (unsigned char)c | 0x0700;
_kvideo_block_offset++;
}

void kvideo_write_str(char* str)

{
while (*str)
{
kvideo_write_char(*str);
str++;
}
}

 

With all of this files, you just need to build and link using

as -o kernel_loader.o kernel_loader.s
g++ -o kernel_video.o -c kernel_video.c -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector
g++ -o kernel.o -c kernel.c -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector
ld -T kernel_linker.ld -o kernel.bin kernel_loader.o kernel_video.o kernel.o

And that will generate a binary file with the kernel, video functions and loader that can be run in your PC or in a virtual machine like QEMU. In order to execute this binary in QEMU, simple write

qemu -no-kvm -net none -kernel kernel.bin

And you will see something like this: