2. Inside the MaRTE OS boot process

The boot process of MaRTE OS is quite similar to other Operating Systems. Just in case you don't know anything about booting we are going to start from the very begining. In the next section, we will go more into details of how to boot from a particular device. I'd like to stress one more time that we are going to talk only about the x86 architecture of MaRTE OS. This section is an adaptation to MaRTE OS of the article [1], which explains the boot process for a Linux kernel.

2.1. The BIOS

When a MaRTE OS application is running in our system what we have is just a chunk of code and data (our mprogram) loaded in RAM that is being executed (at some point) by the CPU. But, how did the mprogram file end up in the RAM? and who gave him the control?.

When a system is first booted, or is reset, the processor executes code at a well-known location. In a PC, this location is in the basic input/output system (BIOS), which is stored in flash memory on the motherboard. The first step of the BIOS is the power-on self test (POST). The job of the POST is to perform a check of the hardware. The second step of the BIOS is local device enumeration and initialization.

The BIOS is made up of two parts: the POST code and runtime services. After the POST is complete, it is flushed from memory, but the BIOS runtime services remain and are available to the target operating system.

To boot an operating system, the BIOS runtime searches for devices that are both active and bootable in the order of preference defined by the complementary metal oxide semiconductor (CMOS) settings. The CMOS is a small memory that mantains its data thanks to a lithium battery. A boot device can be a floppy disk, a CD-ROM, a partition on a hard disk, a device on the network, or even a USB flash memory stick.

This is the first point where we can take part, selecting the order of preference of the boot devices. This is typically made by changing some parameters at the BIOS setup program (press "supr" when your PC starting).

For the most typical device for booting, a hard disk, the BIOS will load the first sector (512-bytes). This first sector, called MBR (Master Boot Record) contains the primary boot loader. After the MBR is loaded into RAM, the BIOS yields control to it. Take a look at the code and the raw bytes in your MBR with the following commands:

$ sudo apt-get install nasm
$ sudo dd if=/dev/hda bs=512 count=1 | ndisasm - | more
$ sudo dd if=/dev/hda bs=512 count=1 | od -t x1

This is the second point where we can take part, changing the contents of the MBR. We have to store a less than 512-byte chunk of code in the MBR, the primary boot loader!.

2.2. The Boot Loader

Ok, now we have this chunk of 512 bytes in RAM being executed. We can't do a lot with just 512 bytes so this chunk of code is in charge of loading a second-stage boot loader. Therefore a boot loader is typically composed of two stages. The job of the primary boot loader is to find and load the secondary boot loader (stage 2). It does this by looking through the partition table for an active partition. When it finds an active partition, it scans the remaining partitions in the table to ensure that they're all inactive. When this is verified, the active partition's boot sector (a boot sector is the first sector of a partition and it looks like the MBR.) is read from the device into RAM and executed.

Figure 1. Layout of the MBR ([1])

The secondary, or second-stage, boot loader could be more aptly called the kernel loader. The task at this stage is to load the MaRTE OS application. MaRTE OS is compliant with the Multiboot Specification which is a protocol between a boot loader and an OS kernel ([3]).

One of the most used boot loaders is the GRand Unified Bootloader (GRUB) boot loader ([2]) which is also multiboot compliant. The great thing about GRUB is that it includes knowledge of a lot of file systems. Instead of using raw sectors on the disk (as LILO boot loader does), GRUB can load a MaRTE OS from several file systems. It does this by making the two-stage boot loader into a three-stage boot loader. Stage 1 (MBR) boots a stage 1.5 boot loader that understands the particular file system containing the MaRTE OS image. Example: e2fs_stage1_5 (to load from an ext2 or ext3 file system). When the stage 1.5 boot loader is loaded and running, the stage 2 boot loader can be loaded.

With stage 2 loaded, GRUB can, upon request, display a list of available kernels (defined in /boot/grub/grub.conf or /boot/grub/menu.lst). You can select a kernel and even amend it with additional kernel parameters. Optionally, you can use a command-line shell for greater manual control over the boot process:

press 'c'
grub> kernel /mprogram
grub> boot

2.3. MaRTE OS

As we said before, the second stage of the boot loader will load our MaRTE OS application, mprogram, in RAM and yield the control to it. Ok, but where is it loaded? The file mprogram is just an ELF executable file so we can view its properties with the following command:

$ readelf -a mprogram

You will see from the output that mprogram will be loaded at the RAM address 0x00100000. This address is set in linking time by MaRTE OS's script mld (which is called when you run mgcc and mgnatmake). You can change the value at marte/utils/globasl.pl ($ARCH_LD_OPTS=" -Ttext 100000 ";)

As any other ELF program MaRTE OS starts and ends at certain points. There is no magic in it. So let's see where MaRTE OS starts. With the following commands we compile an application with debug option and then dump its contents:

$ mgnatmake -g marte/examples/hello world.adb
$ objdump -d export/mprogram | more

      mprogram: file format elf32-i386
      Disassembly of section .text:
      00100000 <_start>:
         100000: eb 0e jmp 100010 <boot_entry>
            100002: 89 f6 mov %esi,%esi
      ...

As we said before MaRTE OS starts at 0x00100000. Thanks to the '-g' flag we can see some labels among the code that help us to understand it. As we just want to know the execution flow of MaRTE OS it is enough to know the meaning of a few x86 assembler instructions like jmp, call or ret (check [5] for more info). The first label is 'boot_entry' so let's look for it in the MaRTE OS hierachy:

$ grep -R -n -i boot_entry *

x86_arch/hwi/boot/multiboot.S:43:   jmp     boot_entry
x86_arch/hwi/boot/multiboot.S:59:   .long   boot_entry              /* entry */
x86_arch/hwi/boot/multiboot.S:62:boot_entry:

We find out that the first executed code is located at x86_arch/hwi/boot/multiboot.S. This file is written in assembler and it calls to the function multiboot_main located at x86_arch/call_main/base_multiboot_main.c.

This function is in charge of making some initialization stuff by calling other functions (it shows the welcome message of MaRTE OS! Good point to make your first hack!). Most of this code has been taken from the OSKit project ([4]). Finally, this is the last line:

exit(wrapper_main(argc, argv, environ));

wrapper_main is located either at x86_arch/call_main/wrapper_main_c.c or at x86_arch/call_main/wrapper_main_ada.c depending on the language of the application. This diference is needed because if the application is written in 'C' language, the Ada packages that export functions to 'C' need to be elaborated. This is made by calling two functions:

  adainit();
  ret = main(argc, argv, envp);
  adafinal();

From this wrapper it is called the real main, that is, our application!. From now on we are the owners of the computer. If our application ends, everything end!. When we create several tasks or threads there is a trick to pass the control to the kernel so it can schedule which thread is going to be executed next. The only way to do that is by means of hardware interrupts. Without them the tasks would execute over and over!. MaRTE OS needs a real-time timer in order to program it for returning the control of the CPU back to the kernel after a few ms. This is one of the most basic functionality of a kernel and in the case of MaRTE OS it is located at Kernel.Scheduler and Kernel.Timed_Events_And_Timer Ada packages.

When the application ends, it returns a value to ret and after some finalization code a message is shown at x86_arch/hwi/boot/_exit.c:

  printc(" _exit(%d) called; rebooting...\n", rc);