I've been working off and on for the past 13 months on a hobby operating system. I put some of the initial code up on github but I haven't updated that repo in a long time. I've made a lot of progress, but haven't gotten anything to a state where I dare share it.

The bootloader portion is what gave me the most grief by far. There are not exactly a lot of resources concerning producing one's own bootloader. Most would consider it ancillary to OS development and easily avoided by use of an open source offering. I chose to write my own, since I was interested in all aspects of hobby OS development, not just the kernel.

Anyways, the bootloader was quite a pain to get going (I say going since I'm sure it will need tweaking) and I wanted to write some thoughts and things I learned during the process of writing my own bootloader for my operating system.

I decided early on to only support modern hardware. This means x86 64 bit support only on IBM/PC clones. The implementation of the bootloader relies heavily on the attributes of the platform, and on decisions made by designers several decades ago. For example consider one of the simplest bootloaders possible

BITS 16

jmp $

TIMES 510-($-$$) DB 0
DW 0xAA55

This code simply identifies itself as a bootsector, and does nothing (its an infinite loop). The hex signature 0xAA55 at the end of the 512 byte sector is key. Why? Its a legacy requirement; the BIOS POST sequence expects to find this signature in the boot sector. Evidently some modern BIOSes have shed this requirement, but not all have, so its certainly not a bad idea to include it. Once the BIOS finds the boot sector, it loads the sector into a well known address, usually 0x0000:0x7C00, although some will load to 0x07C0:0x0000 which resolves to the same address physical memory address. Its good practice to enforce the segment address with an ORG statement like so:

BITS 16
ORG 0x0000

jmp $

TIMES 510-($-$$) DB 0
DW 0xAA55

Once the boot sector is copied to RAM, a lot of work is still needed to actually boot the operating system. Since my goal is a 64 bit OS, this means I need to enable long mode (or 64 bit mode). To do so, some setup needs to take place:

  • Load the kernel to a well known address in memory
  • Setup a page table
  • Setup the interrupt descriptor table (IDT)
  • Setup the global descriptor table (GDT)
  • Enable long mode
  • Jump to the kernel

Sounds simple! Its not, primarily because of the lack of documentation and other resources for doing these steps. There is some documentation, but its buried away in obscure or old manuals and documents. I'll cover these in an upcoming post!