Introduction
So you'd like to start developing a microkernel? To do so you obviously need to have some experience with:
- C programming
- low level computer fundamentals
and have some UNIX (possibly Linux) installed.
If you don't know what L4 is, please read a wikipedia page about L4 microkernel family.
To start working with L4 you'll first have to understand how it works. The good starting point is L4 User Manual by Ihor Kuz from NICTA.
Due to its nature L4 perfectly matches the requirements of modern embedded systems. And now days almost all embedded systems are targeting the ARM architecture. The ARM reference manual contains all the information you could possibly need working with this architecture. However if this subject is completely new for you - you should probably search for more beginner-friendly documentation.
Implementation
There are several implementations of L4 out there, but the one that I've looked for to start playing with had to be:
- Open Sourced
- written in C & assembler
- easy to start playing with
After some time I've discovered Codezero Embedded Hypervizor and OS. As this project is quite new its codebase is not that overwhelming for a newcomer, however complete enough to give working and efficient starting point for my microkernel developing journey. Developers seem to put a lot of effort to maintain really well structured and clean code which seems very good for a project from which I'd like to learn.
Starting
While the Codezero Starting Guide is a good starting point and most of the time really useful it has some problems too: it's too lengthy and time consuming to follow and it contains some mistakes. That's why I've decided to automate all the work and set up my own Codezero git fork for newcomers. The init-build-env branch is where I accumulate all the changes that make life easier for a Codezero newcomer like me. With this changes you should be able to have L4 based project running in up to 30 minutes with most of the time spent on waiting for automated download and compilation process to complete.
Setting up tools and building codezero
To start just grab a copy of my Codezero's init-build-env repository with git:
git clone git@github.com:dpc/codezero.git
If you don't know what git is, how to run it or how it works - you should really spend some time getting familiar with it. Try Github's Introduction to Git.
So now, enter the project directory and run the tool script that will setup building environment for you:
cd codezero
./tools/init-build-env.sh
This script will do almost everything what is described in the Codezero's Getting Started page. It will download, build and install environment tools in [[~/opt/arm]] so that the only thing that you have to do (hopefully!) is to press enter once during installation, and add one directory to your PATH environment afterwards.
Just follow the process, install missing software if needed and rerun the script if required.
After successful execution you will have a build environment ready to build a codezero project.
First configure the project. The defaults are fine so just run:
./configure.py
and press q to save the settings.
To build the project run:
./build.py
If everything was prepared correctly the build process should create something like the following in the build directory:
codezero$ ls build
config.cml configdata config.h config.rules cont0 conts final.elf kernel.elf loader rules.compiled
Runing codezero
To run and debug Codezero we will use a Qemu to emulate a fully working ARM-based system for us. So you should have qemu installed:
codezero$ which qemu-system-arm
/usr/bin/qemu-system-arm
There are two options on how to debug the system:
- plain gdb
- insight frontend to gdb
As I don't like GUI applications in development process I'll describe using ./tools/run-qemu-gdb only. For Insight - you may use ./tools/run-qemu-insight - it does essentially the same thing.
So to make life easier link the script:
ln -s tools/run-qemu-insight gdb
and run it with:
./gdb
so you should get:
codezero$ ./gdb
Qemu started.
System serial console accessible via `telnet localhost 12445`
GNU gdb (Sourcery G++ Lite 2009q3-67) 6.8.50.20090630-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-none-linux-gnueabi".
For bug reporting instructions, please see:
<https://support.codesourcery.com/GNUToolchain/>.
QEMU 0.11.0 monitor - type 'help' for more information
(qemu) 0x00100000 in ?? ()
add symbol table from file "build/kernel.elf" at
.text_addr = 0x8000
(gdb)
What have just happened?
The script started Qemu to emulate an ARM system for us with a Codezero kernel image loaded. The virtual machine is stopped and waiting for commands. Two network sockets were created by the Qemu - one for gdb to control and debug the system and second emulating ARM system serial console - which embedded systems usually use as a basic I/O system.
Then - right after qemu - gdb is being started with symbols loaded and connected to qemu virtual machine so we can navigate through the source code as it's being executed and control the execution.
To connect to the system serial console, open new terminal and run:
$ telnet localhost 12445
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Voila! You now have a L4 microkernel running in an ARM virtual machine and you're connected to it's serial port. Was not that hard, was it?
Initial debugging
As you may not know - the starting process for the Codezero is done in two steps. First - the loader is initializing the platform and then puts the kernel into the right place in the system's memory. Then it passes the execution to the kernel itself and gets out the way.
So, in the running gdb session try dissasemble command (or just dissas alias):
(gdb) disas
Dump of assembler code for function _start:
0x00100000 <_start+0>: ldr sp, [pc, #4] ; 0x10000c <_start+12>
0x00100004 <_start+4>: bl 0x102a8c <platform_init>
0x00100008 <_start+8>: bl 0x1003a0 <main>
0x0010000c <_start+12>: andseq lr, lr, r8, lsr #31
End of assembler dump.
(gdb)
It's here where everything starts. Corresponding code is in the loader/libs/c/crt/sys-userspace/arch-arm/crt0.S file and all the loader code is int the loader subdirectory. The starting point is written in the ARM assembler.
stepi will advance one CPU instruction forward. Run it two times, like this:
(gdb) stepi
0x00100004 in _start ()
(gdb) stepi
platform_init () at conts/libdev/uart/src/platform_init.c:56
56 {
(gdb)
As you may see the execution flow has reached the C code that is responsible for initializing the platform code. As we're now in the C code we can use list command to see the C code listing (or a l alias):
(gdb) l
51
52 return 0;
53 }
54
55 void platform_init(void)
56 {
57 uart.base = PL011_BASE;
58 pl011_initialise(&uart);
59 }
60
(gdb)
So we can see that platform_init function is doing only one thing here: initializing the UART.
As this is not a GDB tutorial I will not cover more about using gdb commands here. There are plenty of such tutorial on web - just look for it.
To advance to the point where the loaded gets out of the way do:
(gdb) tbreak arch_start_kernel
Temporary breakpoint 1 at 0x100374: file loader/main.c, line 103.
(gdb) c
Continuing.
Breakpoint 1, arch_start_kernel (entry=0x102aa8) at loader/main.c:103
103 printf("elf-loader:\tStarting kernel\n\r");
(gdb)
And then:
(gdb) l
98 return 0;
99 }
100
101 void arch_start_kernel(void *entry)
102 {
103 printf("elf-loader:\tStarting kernel\n\r");
104 void (*func)(unsigned long) = (void (*)(unsigned long)) (*(unsigned long*)entry);
105 func(0);
106 }
107
(gdb) n
103 printf("elf-loader:\tStarting kernel\n\r");
(gdb) n
arch_start_kernel (entry=0x102aa8) at loader/main.c:104
104 void (*func)(unsigned long) = (void (*)(unsigned long)) (*(unsigned long*)entry);
(gdb) n
105 func(0);
(gdb) n
0x00008000 in _start_text ()
(gdb)
You could be wondering at this point where are we. Well, we are at src/arch/arm/head.S on the line 27.
(gdb) disassemble
Dump of assembler code for function _start_text:
0x00008000 <_start_text+0>: msr CPSR_fc, #19
0x00008004 <_start_text+4>: mrc 15, 0, r0, cr1, cr0, {0}
0x00008008 <_start_text+8>: bic r0, r0, #1
0x0000800c <_start_text+12>: bic r0, r0, #4
0x00008010 <_start_text+16>: bic r0, r0, #4096 ; 0x1000
0x00008014 <_start_text+20>: bic r0, r0, #8
0x00008018 <_start_text+24>: mcr 15, 0, r0, cr1, cr0, {0}
0x0000801c <_start_text+28>: ldr sp, [pc, #40] ; 0x804c <_kernel_init_stack>
0x00008020 <_start_text+32>: msr CPSR_fc, #215 ; 0xd7
0x00008024 <_start_text+36>: ldr sp, [pc, #36] ; 0x8050 <_kernel_abt_stack>
0x00008028 <_start_text+40>: msr CPSR_fc, #210 ; 0xd2
0x0000802c <_start_text+44>: ldr sp, [pc, #32] ; 0x8054 <_kernel_irq_stack>
0x00008030 <_start_text+48>: msr CPSR_fc, #209 ; 0xd1
0x00008034 <_start_text+52>: ldr sp, [pc, #28] ; 0x8058 <_kernel_fiq_stack>
0x00008038 <_start_text+56>: msr CPSR_fc, #219 ; 0xdb
0x0000803c <_start_text+60>: ldr sp, [pc, #24] ; 0x805c <_kernel_und_stack>
0x00008040 <_start_text+64>: msr CPSR_fc, #211 ; 0xd3
0x00008044 <_start_text+68>: bl 0x1493c <start_kernel>
0x00008048 <_start_text+72>: b 0x8048 <_start_text+72>
End of assembler dump.
(gdb)
To skip this code use step. This will get us to the next C code:
(gdb) s
Single stepping until exit from function _start_text,
which has no line number information.
start_kernel () at src/glue/arm/init.c:295
295 printascii("\n"__KERNELNAME__": start kernel...\n");
(gdb) l
290 scheduler_start();
291 }
292
293 void start_kernel(void)
294 {
295 printascii("\n"__KERNELNAME__": start kernel...\n");
296
297 /*
298 * Initialise section mappings
299 * for the kernel area
(gdb)
Last words
I think this is enough to start with. You should now concentrate on using debugger, reading a source code and available documentation to learn how do the things work in the Codezero L4 microkernel.
It may take you some time, but I hope you will have much more enthusiasm now - with a working codebase and debugger - to learn about L4 and Codezero. And eventually you may start to contribute to this wonderful project yourself.
If you had any problems or you find anything not clear enough, just leave me a note in a comment and I'll try to help and correct any mistakes that I've made.