Booting Procedure

15 Jun 2012

Previous part

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.

kernel/include/Link.ld

ENTRY(start)

SECTIONS {
    . = 0xC0100000;
    .text : AT(ADDR(.text) - 0xC0000000)
    {
        code = .; _code = .; __code = .;
        *(.text)
        *(.eh_frame)
        . = ALIGN(4096);
    }
    .data : AT(ADDR(.data) - 0xC0000000)
    {
        data = .; _data = .; __data = .;
        *(.data)
        *(.rodata)
        . = ALIGN(4096);
    }
    .bss : AT(ADDR(.bss) - 0xC0000000)
    {
        bss = .; _bss = .; __bss = .;
        *(.bss)
        . = ALIGN(4096);
    }

    _end = .;
}

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.

kernel/boot.s

; Kernel start point
[global start]
start:
    cli

; Load page directory and enable paging
    mov ecx, BootPageDirectory - KERNEL_OFFSET
    mov cr3, ecx
    mov ecx, cr0
    or ecx, 0x80000000
    mov cr0, ecx
    lea ecx, [.higherHalf]
    jmp ecx

.higherHalf:
    ; Load GDT
    mov ecx, gdt_ptr
    lgdt [ecx]

    SetSegments 0x10, cx
    jmp 0x8:.gdtLoaded

.gdtLoaded:

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.

kernel/include/asm_macros.inc

; GRUB multiboot headers
MBOOT_PAGE_ALIGNED_FLAG equ 1<<0
MBOOT_MEMORY_INFO_FLAG  equ 1<<1
MBOOT_HEADER_MAGIC  equ 0x1BADB002

MBOOT_HEADER_FLAGS  equ MBOOT_PAGE_ALIGNED_FLAG | MBOOT_MEMORY_INFO_FLAG
MBOOT_HEADER_CHECKSUM   equ -(MBOOT_HEADER_FLAGS + MBOOT_HEADER_MAGIC)


KERNEL_OFFSET   equ 0xC0000000
BOOT_STACK_SIZE equ 0x1FFF

; SetSegments 0x10 ax loads all segment selectors with 0x10 using eax
%macro SetSegments 2
    mov e%2, %1
    mov ds, %2
    mov es, %2
    mov fs, %2
    mov gs, %2
    mov ss, %2
%endmacro

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"

[bits 32]

section .bss

align 0x8

; Stack for booting
[global BootStack]
BootStackTop:
    resb BOOT_STACK_SIZE
BootStack:

section .data

align 0x1000

; Page directory for booting up.
; First four megabytes are identity mapped as well as
; mapped to 0xC0000000
[global BootPageDirectory]
BootPageDirectory:
    dd (BootPageTable - KERNEL_OFFSET) + 0x3
    times ((KERNEL_OFFSET >> 22) - 1) dd 0x0
    dd (BootPageTable - KERNEL_OFFSET) + 0x3
    times (1022 - (KERNEL_OFFSET >> 22)) dd 0x0
    dd (BootPageDirectory - KERNEL_OFFSET) + 0x3

BootPageTable:
    %assign i 0
    %rep 1024
        dd (i << 12) | 0x3
        %assign i i+1
    %endrep

; Hard-coded GDT.
; GDT pointer is wrapped into the first entry
[global gdt]
gdt_ptr:
gdt:
    dw 0x002F
    dd gdt
    dw 0x0000
    dd 0x0000FFFF, 0x00CF9A00
    dd 0x0000FFFF, 0x00CF9200
    dd 0x0000FFFF, 0x00CFFA00
    dd 0x0000FFFF, 0x00CFF200

section .text

align 4

; GRUB Multiboot data
MultiBootHeader:
    dd MBOOT_HEADER_MAGIC
    dd MBOOT_HEADER_FLAGS
    dd MBOOT_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 directory
    mov edx, BootPageDirectory
    xor ecx, ecx
    mov [edx], ecx
    invlpg[0]

    ; Load a stack for booting
    mov esp, BootStack
    mov ebp, BootStack

    ; eax contains the magic number from GRUB 0x2BADB002
    push eax

    ; ebx contains the address of the Multiboot information structure
    add ebx, KERNEL_OFFSET
    push ebx

    ; Call the c function for setting up
[extern kinit]
call kinit
jmp $

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

void kinit()
{

}

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

Comments

comments powered by Disqus
© 2012 Thomas Lovén - @thomasloven - GitHub