DEV Community

Void
Void

Posted on

From BIOS to x64 - How I Built Nyvela's Bootloader.

I've always wondered how operating systems work underneath the surface.

How does my computer go from BIOS to userspace, where I can play games, study, and explore the internet?

In this post, I'll take you through how I built Nyvela's bootloader and the journey from the first instructions executed by the CPU to my 64-bit kernel.

BIOS - Where It All Begins

BIOS stands for Basic Input/Output System. It provides firmware services that can be used during the early stages of booting, such as disk I/O, keyboard input, and basic display output. It can also provide information about the system, such as the available memory.

Today, BIOS is considered legacy firmware and has largely been superseded by UEFI, which greatly simplifies the boot process. However, in this article I'll focus on BIOS, since that's the boot path Nyvela currently uses.

Boot Sector - The First Code I Control

The boot sector is the first 512 bytes of my OS image. When booting through legacy BIOS, the BIOS loads this sector into memory and transfers execution to it.

512 bytes is tiny, so I use the boot sector as the first stage of Nyvela's bootloader. Its main job is to load the next stage of the bootloader into memory and transfer control to it.

Stage 1 - Going from Real Mode to Protected Mode

Real Mode is the initial execution mode of x86 processors after reset. It provides virtually no memory protection, meaning programs can access memory without the isolation that modern operating systems require.

Suppose we have a userspace text editor. Due to a bug, it accidentally tries to write to memory belonging to the kernel. Without memory protection, this could corrupt critical operating-system data. Protected Mode allows the OS to isolate userspace from kernel memory and prevent such access.

Protected Mode provides the foundation for features such as memory protection and privilege levels. To enter Protected Mode, Nyvela's Stage 1 bootloader has to:

  • Set up the Global Descriptor Table (GDT)
  • Load the GDT with LGDT
  • Set the Protection Enable (PE) bit in CR0
  • Perform a far jump to reload CS and begin executing 32-bit protected-mode code

However, because of Nyvela's bootloader design, we need to do more before entering Protected Mode.

Once Protected Mode is enabled, we can no longer use BIOS interrupts in the same way as we could in Real Mode. This means we need to perform operations such as disk and hardware information gathering beforehand.

The layout of Stage 1 therefore looks more like this:

  • Load Stage 2
  • Load the kernel
  • Enable the A20 gate
  • Read the BIOS memory map
  • Set up the GDT
  • Load the GDT
  • Enable Protected Mode
  • Far jump to 32-bit code

By completing the BIOS-dependent operations first, Nyvela can continue its boot process without relying on BIOS interrupts after entering Protected Mode. The memory map also gives the kernel information about available physical memory, which will later be used by Nyvela's memory manager.

Stage 2 - Going from Protected Mode to Long Mode

Long Mode is the 64-bit execution mode of x86-64. It introduces 64-bit registers and instructions, a much larger address space, 64-bit paging, and several changes to memory protection and segmentation. This is the mode in which Nyvela's 64-bit kernel executes.

To enter Long Mode, Nyvela's Stage 2 bootloader needs to:

  • Set up an Interrupt Descriptor Table (IDT)
  • Load the IDT with LIDT
  • Disable paging
  • Enable Physical Address Extension (PAE)
  • Read IA32_EFER with RDMSR, set the Long Mode Enable (LME) bit, and write it back with WRMSR
  • Set up paging and identity-map the first GiB of memory
  • Enable paging
  • Update the segment registers
  • Perform a far jump to Kernel Code

This is more complex than Stage 1. Some steps, such as setting up the IDT, aren't strictly required to enter Long Mode, but they're extremely useful for debugging and will become important later when Nyvela starts handling CPU exceptions and hardware interrupts.

Kernel - The backbone of an OS

After 3 Stages of booting we're finally at the destination, the Kernel. Here's where most of OS functionality will be made, such as Memory Management, Processes, Threads, and way more.

All of the bootloader and Kernel code described in this post is available on
GitHub
Feedback is welcome! :)

Top comments (0)