Initialisation (x86)
Assembly Bootstrapper
When booting on an x86 system, Rotary expects to be booted via the GRUB bootloader, which will load Rotary into memory at the physical address 0x100000.
The linker script at arch/x86/platforms/default/linker.ld includes an initial assembly bootstrapping routine, located at arch/x86/boot/boot.asm.
The bootstrap code begins by setting up an initial kernel stack of approximately 32KB, and switches the stack pointer to it. This ensures that the kernel can be guaranteed of 32KB of usable stack memory, instead of relying on whatever unknown memory GRUB had set aside prior for its own purposes.
Next, the bootstrapper pushes a pointer to the Multiboot information struct to the stack for kernel_main() to take as arguments, then proceeds to construct an initial page table for the system to use once we switch to protected mode.
The initial page table maps the lowest 4MB of physical memory to respective addresses in the highest gigabyte of memory (e.g. virtual address 0xC0000000 will map to physical address 0x00000000). This is a critical step, as once we transition to protected mode, we will no longer be able to access any kernel code or data from its physical address.
Once the page table has been configured and assigned to the CR3 register, the bootstrap code enables Page Size Extensions and Page Global Enable via the CR4 register.
Now that the system has a valid page table that’s ready to use, we transition to protected mode by setting the relevant bit in the CR0 control register.
Next, the bootstrap code updates the stack pointer (ESP) to point to the kernel’s new virtual memory by adding the offset of 0xC0000000 - otherwise the stack pointer would still be pointing to physical memory that we can no longer access.
Finally, we transfer execution to the C kernel, by calling kernel_main.
C Kernel Entry
Currently, the bootstrapper passes control the C function kernel_main(), located in kernel/core/main.c. This entry function then immediately passes control to an architecture-specific initialisation function, arch_init().
Nearly all kernel initialisation is then performed in the arch_init() routine. This is a temporary stopgap until I implement a more comprehensive init system, that will allow me to define an architecture-independent initialisation routine that architecture implementations can hook into as they wish.
Presently, the x86 arch_init() routine performs the following tasks, in this order:
- Retrieves CPU capabilities via
CPUID. - Initialises the paging system and creates new kernel page tables mapping the entire lowest gigabyte of physical memory to the highest gigabyte of virtual memory.
- Initialises the page frame allocator (buddy allocator).
- Marks available memory regions as free for the buddy allocator to use.
- Configures the Global Descriptor Table (GDT).
- Assigns Interrupt Descriptor Table (IDT) gates and loads the IDT.
- Configures the Programmable Interrupt Controller (PIC).
- Initialises the Programmable Interval Timer.
- Initialises keyboard input handlers.
- Enables hardware interrupts.
- Initialises the task scheduler and sets up initial task state.
- Finally, provides a temporary debugging shell.