Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Booting Docs #26

Merged
merged 19 commits into from
Apr 24, 2023
Merged
37 changes: 37 additions & 0 deletions docs/boot/boot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Booting

## Preliminaries

Before booting the OS, the Basic Input/Output System (BIOS) does a lot of stuff to prepare.
The actual workings of the BIOS differ from motherboard to motherboard but they share common interfaces that we can use. These interfaces are accessed similarly to how you would on a 32-bit x86 Linux system, via the `int` instruction! Although, syscalls and BIOS calls are very different.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
The BIOS does a lot of work behind the scenes; It creates and maintains the Interrupt Vector Table (IVT) and has the ability to interface with hardware, most importantly, the disk.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
Finally, we get to the OS, BIOS will load the first sector (512 bytes) of the disk into main memory at 0x7c00 for us (as long as we say the magic word)!

## Boot Sector

The boot sector (see boot_sect.asm) is the very first code that we write that gets executed. It is important to keep in mind at this point we only have 512 bytes of code/data loaded! That's not very much! In addition we also need the "magic word" 0xaa55 as the last word in the sector to signify that this is a bootable sector. `times 509 - ($ - $$) db 0` is a neat assembly trick that gets our binary to exactly 512 bytes.
emilkovacev marked this conversation as resolved.
Show resolved Hide resolved
You may have noticed `[bits 16]` in the assembly, this is because BIOS starts us out in "real mode" which is fancy for 16-bit. One thing to note is that real mode uses segmentation heavily, but discussing segmentation is outside of the scope of this document since we (luckily) don't have to deal with it.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
The first thing we do is store the boot drive into a defined place in memory, BIOS puts the boot drive in dl on boot. Next we set the stack to be at 0x9000 (bp and sp are the 16-bit stack registers). Our next step is the most important, loading the rest of the kernel. BIOS uses Cylinder Head Sector (CHS) addressing for disk access, the exact details are outside of the scope of this document but some things must be mentioned. A sector is a 512byte section that is indexed from 1. A cylinder is a ring on a platter that is indexed from 0. A head is the physical reader that is also indexed from 0. Since we want the second sector (first is the boot sector!) onwards, we only need the very first cylinder and head. Telling the BIOS to do a read starts now.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
We move 2 into `ah` to signify we want to read. Then `42` into al to signify we want to read 42 sectors (512 * 42 = 21504 bytes). Then `cl` gets 2 for the sector, `ch` and `dh` get 0 for cylinder and head respectively. Then `OS_OFFSET` (0x1000) is put in `bx`, this tells BIOS where we want the result of our read to be stored in memory. Finally, we do `int 0x13` which is the disk interrupt for BIOS.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
Now that we have the OS loaded, we need to get into 32-bit (also known as protected mode, long mode is 64-bit). Now we switch over to enter_pm.asm, and open gdt.asm while you're at it.

### Protected Mode

Protected (32-bit) mode is our main mode of operation. Something to note is that we cannot interact with BIOS outside of real (16-bit) mode. So we start by disabling interrupts, since we don't want BIOS interfering with the switch. Next we load the Global Descriptor Table (GDT), which was brought in with our boot sector. The GDT allows us to protect our data! But we don't use the GDT for that functionality. We have 3 entries in our table, each being 8 bytes long. The first entry is just zeros. Next we have our code segment, which we have as spanning as large as it can, offset by 0, and it is executable. The data segment is the same except it is marked read/write. Our GDT descriptor describes the size (in bytes) of our GDT and the address of it in memory. We finalize all this via `lgdt` which loads the GDT.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved

Now we need to set the first bit of control register 0 (`cr0`), which tells the cpu we want to enable protected mode. Finally we do a "far jump" to init_pm. A far jump is a jump that changes segment, this is the only segmentation we will ever do manually. Now we are in 32-bit protected mode! The only thing left is to set the segment registers to our data segment and move the stack. We move the stack to 0x90000 since that is the top of our free space, and it's a large open area.

### Entry

Now is the time to open os_entry.asm. Our only remaining task is calling OS_OFFSET (0x1000), which is where we loaded our kernel earlier. This should not return but if it does we execute `hlt`. Keep in mind interrupts are still off at this point. Now we have officially entered the world of C and left the world of booting and assembly. We call os_main and that puts us in C.
emilkovacev marked this conversation as resolved.
Show resolved Hide resolved

#### Notes

Since we only load `42` sectors, if our kernel grows larger this will have to change. The first thing we would notice is things like strings being messed up or missing. Then after that comes static data being missing. The Final thing to go is code itself, which would be very tough to diagnose.

Sploder12 marked this conversation as resolved.
Show resolved Hide resolved
#### Important Addresses

- `0x1000`: location of the kernel.
- `0x7c00`: location of boot sector.
- `0x9000`: location of stack at boot.
- `0x90000`: location of stack at kernel entry.
Sploder12 marked this conversation as resolved.
Show resolved Hide resolved