Roll Your Own Raspberry Pi Kernel

Getting Started.

There are many distirbutions of operating systems avaialble for the Raspberry Pi. But what if you wanted to write your own? Where would you start if you wanted a realtime system without all the overheads of a bulky operating system. This is my work through on getting started with writing my own kernel from scratch for the Raspberry Pi. I will endeavour to write this in C, mainly because it’s a language that I am familiar with, but also as it has a track record of being the language used to write most of the operating systems today, and also because it is the goto language for interfacing directly with hardware which is ultimately what we are trying to do.

There are probably many tutorials out there already that show you how to do something similar. This is not meant to compete with them, and there are certainly no guarantees that it will match them. This blog isn’t really for you. It’s for me. Though you may find it useful.

Of the other tutorials that I have found two really stand out.

Baking Pi - A Tutorial for writing an OS for the Raspberry Pi, mainly in Assembly.
Valvers.com - A tutorial for compiling a system for the Raspberry Pi, written mainly in C.

Cross Compiling

I will be doing the majority of the work on my mac. The thing with doing that is that my mac has an Intel processor, where the Raspberry Pi has a Broadcom ARM chipset. Therefore what is known as a cross compiler is needed.

The GCC ARM Embedded Project on Launchpad provides a GCC toolchain to use on mac. Addtionally you could head to https://developer.arm.com/open-source/gnu-toolchain/gnu-rm which now holds the most recent and up to date compilers. However the easiest way to install the cross compiler on a mac is to use homebrew. To install the gcc cross compiler, first of all install homebrew and then install the compiler with the command brew install gcc-arm-none-eabi-49 You should also install at this time the gnu debugger. brew install gdb-arm-none-eabi
You can now type on the command line arm-none-eabi-gcc and if all is functioning you should see a response similar to the below.

1
2
3
>arm-none-eabi-gcc
arm-none-eabi-gcc: fatal error: no input files
compilation terminated.

Again you can test the debugger by typing arm-none-eabi-gdb If all is well this should open up the debugger on the command line. You can exit by typing quit and return.

The GCC settings for compiling code for the orginal Raspberry Pi can be found on the elinux page.

1
-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s

As mentioned on that page, -Ofast may cause issues so it is recommended to use -O2 instead. Also -mcpu=arm1176jzf-s can be used in place of -march=armv6zk -mtune=arm1176jzf-s

For the Raspberry Pi 2 as it has a different architecture. The porcessor has been replaced by a quad core Cortex A7. To compile effectively for this processor the compiler options are:

1
-O2 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -mtune=cortex-a7

The Compiler and Linker

A compiler converts our C program into optimised assembly. No more no less.

The C compiler then asks the assembler to assemble that file into an object file. This will have relocateable machine code within along with symbol information that the linker will use.

The linker’s job is to link all the files together, hence the name linker, into a file that can be executed. The linker requires a linker script which tells the linker how to organise the object files. The linker will then resolve symbols to addresses when it has arranged all the objects according to the rules in the linker script.

Basically there are some things that need to happen before our c file can run. Variables need to be initialised. This is taken care of by an object file which is linked by the linker because the linker script will include a reference to it. The object file is called crt0.o

This code uses symbols that the linker can resolve to clear the start of the area where initialised variables start and end and will zero this memory section. It sets up a stack pointer, and always includes a call to _main. Symbols present in C code get prefixed with an underscore in the assembler version of code. So where the start of a C program is the main symbol, in assembler we refer to it as it’s assembler version which is _main.

The simplest c program

1
2
3
4
5
int main(void) {
while(1) {
}
return 0;
}

This basically does nothing, just implements an infinte loop which will hold all the code that we need to implement our kernel.

We compile with the following for the Broadcom BCM2835 (ARM1176)

1
arm-none-eabi-gcc -O2 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s mykernel.c

And we compile with the following for the Broadcom BCM2836 (ARM Cortex A7)

1
arm-none-eabi-gcc -O2 -mfpu=vfp -mfloat-abi=hard -march=armv7-a -mtune=cortex-a7 mykernel.c

GCC will compile the source code successfully but will fail with something similar to the following:

1
2
3
/usr/local/Cellar/gcc-arm-none-eabi-49/20150306/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/fpu/libc.a(lib_a-exit.o): In function `exit':
exit.c:(.text.exit+0x2c): undefined reference to `_exit'
collect2: error: ld returned 1 exit status
^