Making NetBSD Multiboot-Compatibleby Julio M. Merino Vidal
The i386 architecture is full of cruft required to maintain compatibility with old machines that go back as far as the 8086 series. Technically speaking, these features aren't necessary anymore because any recent computer based on this architecture uses a full 32-bit operating system that could work perfectly fine without the legacy code. Unfortunately, the compatibility hacks remain in place and hurt the development of new software.
One of the details that has not changed for years is the i386 boot process. It was designed back in the days when computers had only floppy disk drives and machines had limited firmware. Since then, the procedure has not suffered any change and it makes some tasks very complex; one of these is the configuration of multiple operating systems (OSes) on a single machine.
The new firmware for Intel-based machines, the Extensible Firmware Interface (EFI), resolves this issue by providing a more versatile boot process. Other architectures have already provided improved firmware with nicer boot mechanisms, including, for example, the ability to load and execute an ELF image straight from the machine initialization code. Such is the case with OpenFirmware as shipped in PowerPC Macintoshes.
Even though alternative firmware implementations exist, the i386 architecture as we known it today will still be with us for a long time. Therefore, it would be nice to resolve some of its limitations through software. This is what The Multiboot Specification attempts to do in the boot area: provide the ability to boot any operating system from a single boot loader, hence simulating an improved firmware.
I recently modified the NetBSD's kernel to become Multiboot-compliant. There are many code references in the text, but the main idea behind the essay is to introduce Multiboot and show you that a real-world operating system can easily be converted to support this specification. Please note that all code references point to the
netbsd-4 stable branch to ensure that the code remains consistent with the explanations given here.
The i386 Boot Process
The traditional i386 architecture uses a very simple firmware known as the Basic Input/Output System, or BIOS. The BIOS is in charge of initializing the hardware after powering up the machine and provides a low-level interface to access it from boot loaders and OSes. Unfortunately, it has inherited a lot of deficiencies from the past: these services are available from real mode only and they do not provide high-level abstractions for the underlying hardware.
To put things in perspective, the BIOS is unable to access any on-disk filesystem (not even FAT) and therefore cannot directly load any executable such as the OS kernel. Instead, all the BIOS does is load the first sector of the selected boot disk into a specific memory location (
07C0h:0000h) and transfer the execution control to it. To make things worse, each OS kernel has traditionally provided its own boot code tailored to its needs. For example, the old MS-DOS OS loaded from a FAT disk and executed in real mode; on the other hand, newer systems can boot from a large variety of filesystems (FAT, NTFS, Ext2, and more) and need to run in protected mode from the very beginning because their kernels are too big to fit into the first megabyte of memory (all the memory addressable from real mode).
The fact that each OS needs its own boot loader causes a lot of problems when setting up several different systems on a single machine and poses a lot of questions that the user will most likely be unable to answer. What do you install in the MBR? Where do you put each boot loader? Why do you need to configure each of them independently? Why do you need more than one?
It could be very convenient if there was a generic interface that decoupled the load and bootstrapping of an OS kernel from its boot loader. This way, an OS developer could focus exclusively on the task at hand and forget about writing a boot loader. Similarly, boot loader developers could join forces to write a more complete utility or, alternatively, write their own with minimal code, while being able to boot any OS that supported such interfaces (ideally all OSes). The good thing is that the GRUB developers already had this idea in the past and developed such an interface: Multiboot.
The Multiboot Specification
The Multiboot Specification (MS) defines a protocol between boot loaders and OS kernels that allows any Multiboot-compliant boot loader to load and execute any Multiboot-compliant OS kernel. This permits the end user to install a single boot loader in his machine and use it to boot any system directly, without having to chain-load different boot utilities.
In order to accomplish this abstraction, the MS defines two items:
The Multiboot Header (MH)
This is a 4-byte aligned data structure located within the first 8 KB of the OS kernel image. It provides a magic number used to identify the file as being Multiboot-compliant, a set of flags indicating specific kernel needs, and additional fields describing the structure of the binary. The latter are only used if the kernel is in the a.out format (with some exceptions); using ELF makes things much simpler and also more versatile.
The Multiboot Information Structure (MIS)
This is a data structure constructed by the boot loader and passed to the OS kernel as part of the boot process. It includes information such as which disk is the boot disk, a memory map, the kernel parameters, where additional kernel modules are in memory (if loaded at boot time), etc.
There is some interaction between the two structures in the sense that the MH may request the boot loader to set some fields in the MIS for a successful boot. If the boot loader cannot fulfill the kernel's needs, the load will fail gracefully.
If you're making good use of these two structures, it's trivial to write a simple binary file that acts as an OS kernel—that is, a binary that is able to run standalone on the machine without any other OS. See the boot.S, kernel.c, and related files that form the example kernel distributed alongside GRUB in the docs directory.
It is also interesting to note that a Multiboot-compliant boot loader will enter protected mode and set up a preliminary GDT for a flat memory model, so the kernel needn't do this by itself. Of course, the kernel will have to reload the GDT with values of its own later in the initialization process, but the one set up by the boot loader is enough to get started. In some sense, it's possible to write the OS kernel as if the real mode did not exist at all, as happens on other (saner) platforms.
Pages: 1, 2