From Bootloader to main

It's not immediately obvious when looking at the repository where the execution of code "starts". This section provides an overview of how KidneyOS gets from the bootloader (the first code the hardware runs), to its main function.

KidneyOS currently uses Grub 2 as its bootloader. Grub is resposible for enabling various CPU features, locating the KidneyOS kernel within the ISO, loading it into memory, and jumping to its entrypoint. The machine state section of the Multiboot2 specification provides a detailed description of the state the machine is left in at this point. (Multiboot2 is the standard to which Grub 2 adheres. There are other Multiboot2-compliant bootloaders; Grub 2 is the reference implementation.)

The initial entrypoint of KidneyOS (as specified by the ENTRY declaration in the build-support/i686.ld linker script) is the _start function. This function is defined in the source code in trampoline/src/lib.rs. The trampoline is an intermediate step between the bootloader and the kernel's main function which is responsible for setting up the address space such that all kernel virtual address can be above the 0xC0000000 offset, making it easy to distinguish kernel from user virtual addresses.

One might reasonably wonder why the trampoline is in a separate crate. Grub places the kernel in physical memory at 0x100000. When it transfers control to the _start function, the instruction pointer will be close to that address. We need to know at compile time what the instruction pointer will be so that we'll end up in the right place when performing absolute jumps. Thus, code within the trampoline assumes a near-0x100000 instruction pointer. But when we make it into main, we want our instruction pointer to be a near-0xC0000000 virtual address, meaning the code from main should operate based on that assumption. To produce an ELF file in which these two pieces of code respect the two different assumptions, we need to use linker scripts to place them in separate sections. While you can specify the link section of a function inline in Rust with the #[link_section = "..."] attribute, we also need this property to apply to functions from the core crate that are used by the trampoline. The easiest way we could find to accomplish this was to first build the trampoline into a static library, then link it into the final binary using the kernel/src/build.rs build script.

Once the trampoline has finished setting up the address space, it can finally jump to the kernel's main function, passing information about the size of upper memory (which was obtained from a data structure provided by Grub), as well as the number of lines of video memory that have been printed to so far (so prior logs are not overwritten).