Like any other program, the kernel goes through a load and initialization phase before performing its normal tasks. Although this phase is not particularly interesting in the case of normal applications, the kernel — as the central system layer — has to address a number of specific problems. The boot phase is split into the following three parts:
□ Kernel loading into RAM and the creation of a minimal runtime environment.
□ Branching to the (platform-dependent) machine code of the kernel and system-specific initialization of the elementary system functions written in assembly language.
□ Branching to the (platform-independent) part of the initialization code written in C, and complete initialization of all subsystems with a subsequent switch to normal operation.
As usual, a boot loader is responsible for the first phase. Its tasks depend largely on what the particular architecture is required to do. Because in-depth knowledge of specific processor features and problems is needed to understand all details of the first phase, the architecture-specific reference manual is a good source of information. The second phase is also very hardware-dependent. Consequently, this appendix describes only some key areas of the IA-32 architecture.
In the third, system-independent phase, the kernel is already resident in memory and (on some architectures) the processor has switched from boot mode to execution mode in which the kernel then runs. On IA-32 machines, it is necessary to switch the processor from 8086 emulation, which is immediately active at boot time, to protected mode to make the system 32-bit capable. Setup work is also required on other architectures — for instance, it is often necessary to activate paging explicitly, and central system components must be placed in a defined initial state so that work can begin. All these tasks must be coded in assembly language and therefore are not the most inviting parts of the kernel.
Concentrating on the third phase of startup allows for dispensing with many architecture-specific trifles and has the added advantage that, generally speaking, the remaining sequence of operations is independent of the particular platform on which the kernel runs.
D.1 Architecture-Specific Setup on IA-32 Systems
Once the kernel has been loaded into physical memory using the bootloader (LILO, GRUB, etc.), the setup assembler "function" in arch/x86/boot/header.S is invoked by switching the control flow to the appropriate point in memory by means of a jump statement. This is possible because the setup function is always at the same place in the object file.
The code performs the following tasks, which require a great deal of assembly code:
1. It checks whether the kernel was loaded to the correct position in memory. To do this, it uses a 4-byte signature that is integrated in the kernel image and that must be located, unchanged, at the correct position in RAM.
2. It determines how big system memory is.
3. It initializes the graphics card.
4. It moves the kernel image to a position in memory where it does not get in its own way during subsequent decompression.
5. It switches the CPU to protected mode.
On completion of these tasks, the code branches to the startup_32 function (in arch/x86/boot/ compressed/head_32.s), which does the following:
1. It creates a provisional kernel stack.
2. It fills uninitialized kernel data with null bytes. The relevant area is between the _edata and _end constants. When the kernel is linked, these constants are automatically supplied with the correct values as generated for the kernel binary.
3. It calls the C routine decompress_kernel in arch/x86/boot/compressed/misc_32.c. This decompresses the kernel and writes the uncompressed machine code to position 0x100000/ directly after the first MiB of memory. Uncompressing is the first operation performed by the kernel, as indicated by the screen messages Uncompressing Linux... and Ok, booting the kernel.
The final part of processor-specific initialization is started by redirecting control flow to startup_32 in arch/x86/kernel/head_32.S.
This is a different routine from the previously described startup_32 function and is defined in a different file. The kernel need not concern itself with the fact that both "functions" have the same label because it branches directly to the appropriate address, which is patched in by the assembler and is not associated with the symbolic labels used in the source code.
1 The address can differ if the kernel was built as a relocatable kernel, but this scenario is not relevant here.
This boot section is responsible for the following steps:
1. Activating paging mode and setting up a final kernel stack.
2. Filling the .bss segment located between_bss_start and_bss_stop with null bytes.
3. Initializing the interrupt descriptor table. However, the ignore_int dummy routine is entered for all interrupts — the actual handlers are installed later.
4. Detecting the processor type. The cpuid statement can be used to recognize recent models. It returns information on the processor type and capabilities, but it does not distinguish between 80386 and 80486 processors — this is done by means of various assembler tricks that are neither important nor interesting.
Platform-specific initialization is now complete and the code branches to the start_kernel function. Unlike the code described previously, this function is implemented as a normal C function and is therefore much easier to handle.
Continue reading here: D2 High Level Initialization
Was this article helpful?