This section serves the purpose of a quick introduction to the process of booting the OS (specifically xv6). It will not contain some specifics that are present just in the xv6. More detailed information can be found in the references.

The first two important notions in this section are the definitions:

<aside> đź’ˇ Boot Loader - the one, specific for every OS program which boots the system

</aside>

<aside> đź’ˇ Boot Manager - a universal manager that is capable of running boot loaders.

</aside>

The internet definitions can be misleading, but we will stick to those defined above - the boot loader loads its system, and the boot manager launches boot loaders. Or other boot managers. If there is a need. An example of a boot manager could be Refind or Grub. The boot loader example we will be discussing in great detail in this section pretty soon…

The xv6 system is a popular OS often used for educational purposes for its simplicity. It runs on the x86 hardware and the notion of it will be important later.

Normally, the process of booting the operating system is quite complicated, but in the xv6 case, the standard boot loader does not have that much code and is perfect for introduction to the world of boot. So we will look over the boot process of the Operating System in the example of XV6.


Basically, the simplified version of everything the boot loader does is this: find and load the kernel of OS into memory, give the System needed information (for instance provide the boot options), and give control to the kernel which does the rest of the job with OS.

The boot loader simply contains the instructions. Right after launching, the hardware is initialized by the BIOS or UEFI (specifics and differences of each of them can be found in the corresponding section). The standard xv6 bootloader works in BIOS, which means that all bootloader is located in the first 512 bytes (the first sector of the disk). BIOS always reads it in the specific location in memory (0x7C00).

<aside> ❓ Why do we even need the bootloader? Can’t BIOS just run the Kernel directly?

</aside>

<aside> ❗ It can’t. BIOS simply doesn’t know where the Kernel is located, it has no idea. It just knows that he has to jump to a specific address and everything will somehow be good afterwards. The work of making everything good does bootloader.

</aside>

First of all, the specifics of the xv6 disks are important to note: there are only two volumes xv6 works with. The 0 Volume contains the boot loader (in the first 512 bytes) and the OS kernel. The 1 Volume contains the file system with everything else. There is an option to run xv6 without disk 1, which will mean no storage and running exclusively on RAM. It’s also important to remember, that every part of xv6 needs to be compiled separately, that is the boot loader separately compiled from the kernel (while still on the same disk), and everything else separately too.

In the source code of xv6, there are two files that will be of interest for this section: bootasm.S and bootmain.c

We will start in the bootasm.S file.

Firstly the start label is created to tell the processor where to start the code execution.

Next, there is the first part of this “information the kernel really needs from bootloader”, which is GDT - Global Descriptor Table. This is a table that stores the information for Kernel about memory segments: code, data, stack, extra, and general purpose segments. It has information where those segments start and end in memory and the ring of permission they have (x86 architectures have 0-3 privilege levels known as ring levels). Since x86 architectures convert the physical address into a virtual one by segmentation and then paging and xv6 tends to ignore the segments we must provide the corresponding mapping for xv6 kernel to work correctly. It will just map the segments from 0x0 till the end to identity-map all the memory.

In the assembly it happens here:

.p2align 2      # force 4-byte alignment
gdt:
    SEG_NULLASM                             # null segment
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   # code segment
    SEG_ASM(STA_W, 0x0, 0xffffffff)         # data segment

gdtdesc:
    .word   (gdtdesc - gdt - 1)     # sizeof(gdt) - 1
    .long   gdt                     # address of gdt

The next step is to set up the stack. It is done by setting the esp register to the address of the start of the stack. Where is the start could you ask? Well, since we know the bootloader starts at the address 0x7C00 and takes 512 bytes, then the stack could start at the 0x7E00 address and grow downwards.