ThomasLovén.com

Booting Procedure

To boot up the operating system kernel, I use
GRUB. It takes care of things like getting
into protected mode, checking the memory and activating processor flags. It can
also load any file you ask it to into memory - which is good, because we won't
see a disk driver here anytime soon - before starting the loaded kernel.

I want to write a kernel that resides in a high part of memory (0xC0000000 and
above) because I think it looks tidy. In order to load a high part kernel
without paging, I use the trick described at
osdev.org. This requires
a special Linker file for the kernel.

GRUB drops us off at the kernel entry point - start as defined in the
linkfile - without paging and with an unknown GDT. So setting that up will be
the first order of business. This is done in the assembly bootstrap.

Here's another new thing for me. Macros. Can't believe I could do without them
before. SetSegments is a macro that in this case loads 0x10 into ecx and then
loads cx into each segment selector register. It is defined in an included file
which also contains some constants.

There are also references to some data structures, i.e. BootPageDirectory and
gdt_ptr. Those are hardcoded in the bootstrap file.

kernel/boot.s

%include "include/asm_macros.inc"[bits32]section.bssalign0x8; Stack for booting[globalBootStack]BootStackTop:resbBOOT_STACK_SIZEBootStack:section.dataalign0x1000; Page directory for booting up.; First four megabytes are identity mapped as well as; mapped to 0xC0000000[globalBootPageDirectory]BootPageDirectory:dd(BootPageTable-KERNEL_OFFSET)+0x3times((KERNEL_OFFSET>>22)-1)dd0x0dd(BootPageTable-KERNEL_OFFSET)+0x3times(1022-(KERNEL_OFFSET>>22))dd0x0dd(BootPageDirectory-KERNEL_OFFSET)+0x3BootPageTable: %assign i 0 %rep 1024dd(i<<12)|0x3 %assign i i+1 %endrep; Hard-coded GDT.; GDT pointer is wrapped into the first entry[globalgdt]gdt_ptr:gdt:dw0x002Fddgdtdw0x0000dd0x0000FFFF,0x00CF9A00dd0x0000FFFF,0x00CF9200dd0x0000FFFF,0x00CFFA00dd0x0000FFFF,0x00CFF200section.textalign4; GRUB Multiboot dataMultiBootHeader:ddMBOOT_HEADER_MAGICddMBOOT_HEADER_FLAGSddMBOOT_HEADER_CHECKSUM

Well. This is first of all some area saved for a stack. Then there's the page
directory which has the same page table at virtual memory 0x00000000 and
0xC0000000. The rest of the page directory is cleared. The page table maps the
first four megabytes of physical memory. The hard-coded GDT has five entries.
The first one is always empty, so this space is used to store the gdt pointer.
Then there's kernel code, kernel data, user code and user data. Each segment
has base 0 and size 4 gb, so they all contain all of the virtual memory space.
Finally, there's the multiboot header for GRUB. This has to be at the very
start of the .data section, so it is also loaded first by the makefile.

The last thing we need before we can go into a higher level language is
a stack, but let's take this opportunity to also remove the identity mapping
from the page directory.

kernel/boot.s

.gdtLoaded:; Clear the identity mapping from the page directorymovedx,BootPageDirectoryxorecx,ecxmov[edx],ecxinvlpg[0]; Load a stack for bootingmovesp,BootStackmovebp,BootStack; eax contains the magic number from GRUB 0x2BADB002pusheax; ebx contains the address of the Multiboot information structureaddebx,KERNEL_OFFSETpushebx; Call the c function for setting up[externkinit]callkinitjmp$

The final thing we do before jumping into the c kernel stub is push the values
of eax and ebx which contains the multiboot magic number and information
structure location respectively.

The only thing that's left now in order to get this to run is the c stub.

kernel/kinit.c

voidkinit(){}

Compiling and running this through bochs, we are presented with a black and
white screen completely void of error messages. Perfect!

The code up to this point can be found in my github repository.
Commit 66dd86fc12