In order to get the project of Multiboot Compliance easier, this document will demonstrate the differences between current Minix Boot Monitor (BM) and GRUB2 (as a main representative of Multiboot loaders), followed by a brief to the idea of how to change the kernel to a Multiboot compliant one.
As a reference for the work of making Minix Multiboot compliant, this page is focusing on two things. One is the comparison of states of the kernel loaded by each loader, such as the memory layout and register values. The other is about the differences of the initializing operation taken by the kernel start up code. We will not specify much about the comparison of commands and features of both loaders, as long as they have no affect on the above two things. Either we won't talk about the commons of two loaders.
This section will discuss the differences in these aspects:
With the addresses and length designated in Multiboot header in image, GRUB can load a continuous part or the whole file into given place. (Except for ELF images, multiple sections can be loaded into separate addresses, which Minix doesn't support and we don't consider for now.) BM accepts a special tar-style kernel image that is made of multiple a.out files, and loads each segment of the programs according to its a.out header(also initializes bss and stack segments). Now we have more than ten programs in kernel image, which are loaded independently. More precisely, kernel is loaded at address 0x800 and the others above 1MB. The memory layout is incontinuous. One more thing to notice is that BSS and STACK segments are not directly present in an executable image, but must be properly initialized together with the other segments. That's what GRUB doesn't do. So here's our first obstacle: GRUB intact copy image file into continuous address. While BM loads programs separately and initializes segments for each.
We compare by matches of conditions.
BM passes his stack to Minix kernel, which contains return address and boot parameters. More precisely, top down items on the stack are:
GRUB doesn't provide any stack for kernel.
Kernel copies the GDT to its own table and further initializes it. Entries are in fixed order (indices can be found in /usr/src/kernel/arch/i386/include/archconst.h), some of them are flat 4G segments and others are limited and related to kernel location.
According to the specification, GRUB doesn't provide an available GDT.
Though in different format, both loaders provide boot information parameters to kernel. As discussed above, BM puts the parameters on stack while GRUB put the parameter structure's address in EBX. Typical BM parameter names are listed below
Multiboot information structure from GRUB has three most useful values:
The first two can be easily converted into BM parameters “memory” and “rootdev”.
We want to make current Minix kernel image Multiboot compliant, and on the other hand make no big influence on the functions of existing boot programs (bootblock and boot monitor). I.e. with minor changes of the kernel image, both GRUB and current BM can load and execute Minix image, without chainloading.
For doing this, we must first make the kernel image recognized and accepted by GRUB as a Multiboot kernel. It's an easy job, all we have to do is inserting a defined header which contains a magic number and some address fields.
With the header near the beginning of file, the image can be loaded into memory by GRUB, as a whole and a continuous piece. Since the kernel needs each program placed in proper location and its segments initialized, and as GRUB won't do this for us, we have to write a short piece of code (we call it pre-init code here) right after the entry of image: When GRUB starts the execution of our kernel, there must be an extraction of programs and initialization of segments (which should already be done if booted by BM). But before that, we must have something else done. GDT and selectors are freshly initialized here, otherwise kernel cannot access its data. This is essential so that it's good to go first.
Besides, we need everything else in expected state too. One of the most important is the boot parameters, which is passed by GRUB. We should ensure they are properly saved in a safe place before writing memory so as not to overwrite them, and they are eventually converted to the format that kernel likes. This may also involve initializing a temporary stack, which is not provided by GRUB. After all of these work, a jump to the real entry of kernel comes, from when almost no change is needed. Except that kernel must be informed not to try to return to BM if it's loaded by GRUB.
Of course, above pre-init things won't happen at all if BM is the loader, which is quite easy to detect by the magic number passed in EAX.