How Your Computer Boots
Pages: 1, 2
Booting Linux from hard disk
In most cases, the Linux kernel is loaded from a hard disk, and a two-stage boot loader is required. The most commonly used Linux boot loader on Intel systems is named LILO (LInux LOader); corresponding programs exist for other architectures. LILO may be installed either on the MBR, replacing the small program that loads the boot sector of the active partition, or in the boot sector of a (usually active) disk partition. In both cases, the final result is the same: When the loader is executed at boot time, the user may choose which operating system to load.
The LILO boot loader is broken into two parts, since otherwise it would be too large to fit into the MBR. The MBR or the partition boot sector includes a small boot loader, which is loaded into RAM starting from address
0x00007c00 by the BIOS. This small program moves itself to the address
0x0009a000, sets up the real mode stack (ranging from
0x0009a200), and loads the second part of the LILO boot loader into RAM starting from address
0x0009b000. In turn, this latter program reads a map of available operating systems from disk and offers the user a prompt so she can choose one of them. Finally, after the user has chosen the kernel to be loaded (or let a time-out elapse so that LILO chooses a default), the boot loader may either copy the boot sector of the corresponding partition into RAM and execute it or directly copy the kernel image into RAM.
Assuming that a Linux kernel image must be booted, the LILO boot loader, which relies on BIOS routines, performs essentially the same operations as the boot loader integrated into the kernel image described in the previous section about floppy disks. The loader displays the "Loading Linux" message; then it copies the integrated boot loader of the kernel image to address
setup( ) code to address
0x00090200, and the rest of the kernel image to address
0x00100000. Then it jumps to the
setup( ) code.
Middle Ages: The setup( ) function
The code of the
setup( ) assembly language function is placed by the linker immediately after the integrated boot loader of the kernel, that is, at offset
0x200 of the kernel image file. The boot loader can thus easily locate the code and copy it into RAM starting from physical address
Understanding the Linux Kernel
setup( ) function must initialize the hardware devices in the computer and set up the environment for the execution of the kernel program. Although the BIOS already initialized most hardware devices, Linux does not rely on it but reinitializes the devices in its own manner to enhance portability and robustness. Essentially,
setup( ) performs the following operations:
Invokes a BIOS procedure to find out the amount of RAM available in the system.
Sets the keyboard repeat delay and rate. (When the user keeps a key pressed past a certain amount of time, the keyboard device sends the corresponding keycode over and over to the CPU.)
Initializes the video adapter card.
Reinitializes the disk controller and determines the hard disk parameters.
Checks for an IBM Micro Channel bus (MCA).
Checks for a PS/2 pointing device (bus mouse).
Checks for Advanced Power Management (APM) BIOS support.
If the kernel image was loaded low in RAM (at physical address
0x00010000), moves it to physical address
0x00001000. Conversely, if the kernel image was loaded high in RAM,
setupdoes not move it. This step is necessary because, in order to be able to store the kernel image on a floppy disk and to save time while booting, the kernel image stored on disk is compressed, and the decompression routine needs some free space to use as a temporary buffer following the kernel image in RAM.
Sets up a provisional interrupt descriptor table (IDT) and a provisional global descriptor table (GDT).
Resets the floating point unit (FPU), if any.
Reprograms the Programmable Interrupt Controller (PIC) and maps the 16 hardware interrupts (IRQ lines) to the range of vectors from 32 to 47. The kernel must perform this step because the BIOS erroneously maps the hardware interrupts in the range from 0 to 15, which is already used for CPU exceptions.
Switches the CPU from real mode to protected mode by setting the
PEbit in the
cr0status register. As explained in the section "Kernel Page Tables" in Chapter 2, the provisional kernel page tables contained in
pg0identically map the linear addresses to the same physical addresses. Therefore, the transition from real mode to protected mode goes smoothly.
Jumps to the
startup_32( )assembly language function.
Renaissance: The startup_32( ) functions
There are two different
startup_32( ) functions; the one we refer to here is coded in the
arch/i386/boot/compressed/head.S file. After
setup( ) terminates, the function has been moved either to physical address
0x00100000 or to physical address
0x00001000, depending on whether the kernel image was loaded high or low in RAM.
This function performs the following operations:
Initializes the segmentation registers and a provisional stack.
Fills the area of uninitialized data of the kernel identified by the
_endsymbols with zeros.
decompress_kernel( )function to decompress the kernel image. The "Uncompressing Linux . . . " message is displayed first. After the kernel image has been decompressed, the "O K, booting the kernel" message is shown. If the kernel image was loaded low, the decompressed kernel is placed at physical address
0x00100000. Otherwise, if the kernel image was loaded high, the decompressed kernel is placed in a temporary buffer located after the compressed image. The decompressed image is then moved into its final position, which starts at physical address
Jumps to physical address
The decompressed kernel image begins with another
startup_32( ) function included in the
arch/i386/kernel/head.S file. Using the same name for both the functions does not create any problems (besides confusing our readers), since both functions are executed by jumping to their initial physical addresses.
startup_32( ) function essentially sets up the execution environment for the first Linux process (process 0). The function performs the following operations:
Initializes the segmentation registers with their final values.
Sets up the kernel mode stack for process 0.
setup_idt( )to fill the IDT with null interrupt handlers.
Puts the system parameters obtained from the BIOS and the parameters passed to the operating system into the first page frame.
Identifies the model of the processor.
idtrregisters with the addresses of the GDT and IDT tables.
Jumps to the
Modern Age: The start_kernel( ) function
start_kernel( ) function completes the initialization of the Linux kernel. Nearly every kernel component is initialized by this function; we mention just a few of them:
The page tables are initialized by invoking the
The page descriptors are initialized by the
The final initialization of the IDT is performed by invoking
The slab allocator is initialized by the
The system date and time are initialized by the
The kernel thread for process 1 is created by invoking the
kernel_thread( )function. In turn, this kernel thread creates the other kernel threads and executes the
Besides the "Linux version 2.2.14 . . . " message, which is displayed right after the beginning of
start_kernel( ), many other messages are displayed in this last phase, both by the init functions and by the kernel threads. At the end, the familiar login prompt appears on the console (or in the graphical screen if the X Window System is launched at startup), telling the user that the Linux kernel is up and running.
Daniel P. Bovet got a Ph.D.
Discuss this article in the O'Reilly Network Linux Forum.
Return to the Linux DevCenter.