235 lines
6.4 KiB
Markdown
235 lines
6.4 KiB
Markdown
layout: post
|
|
title: "Booting Procedure"
|
|
subtitle: "Starting up the x86"
|
|
tags: [osdev]
|
|
|
|
[Previous part](/blog/2012/06/Setting-Up/)
|
|
|
|
To boot up the operating system kernel, I use
|
|
[GRUB](http://www.gnu.org/software/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](http://wiki.osdev.org/Higher_Half_bare_bones). 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*
|
|
|
|
:::nasm
|
|
; 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*
|
|
|
|
:::nasm
|
|
; 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*
|
|
|
|
:::nasm
|
|
%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*
|
|
|
|
:::nasm
|
|
.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*
|
|
|
|
:::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](https://github.com/thomasloven/os5/tree/66dd86fc128e2714e4c93c73d8a0bf8542e10573)
|