YeXo - een kernel programmeren

15 maart 2006

Een kernel laden op 3gb in het virtueel geheugen

Na lang zoeken met google en eindeloos uitproberen is het me eindelijk gelukt om mijn kernel op virtueel adres 3gb te laden. De voordelen hiervan zijn duidelijk: je kunt een process laden op virtueel adres 0 en toch de kernel in dezelfde adress-space laten staan. Hier de asm-code die door grub geladen wordt en waarmee ik paging aanzet en naar 3gb jump:



[BITS 32]

; Zorg dat het begin van de setup sectie door de linker gezien kan worden
global _setup
; _kmain is de c-functie die we aanroepen. Er moet hier een underscore voor omdat
; djgpp aan alle functies en variabelen een underscore toevoegd.
extern _kmain

; Eerst een paar definities voor de multiboot header, zie multiboot specificatie voor info.

; Zorg dat alle boot-modules geladen worden op een adres
; dat samenvalt met een page (deelbaar is door 4096).
MULTIBOOT_MODULE_ALIGN equ 1<<0

; maak dat grub een memory_info structure in het geheugen laad
; Let op: grub geeft ons het adres hiervan in ebx, maar dat is een
; 'echt' adres, geen virtueel adres.
MULTIBOOT_MEMINFO equ 1<<1

; de multiboot flag
MULTIBOOT_FLAGS equ (MULTIBOOT_MODULE_ALIGN | MULTIBOOT_MEMINFO)

; Een magisch getal om te zorgen dat grub deze gegevens vind en de
; de kernel kan laden
MULTIBOOT_MAGIC equ 0x1BADB00

; Als bij de checksum de flags en het magische getal
; opgetelt worden moet de uitkomst 0 zijn.
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)

; Het virtuele adres waarop het tweede gedeelte van de code van de kernel begint
; Dit is dus het adres waar we heen jumpen na paging ingeschakelt te hebben.
KERNEL_VMA equ 0xC0101000

; Het adres waar onze eerste page directory komt te staan. Zodra we eenmaal boven de 3gb zitten
; wordt er in _kmain een nieuwe page_directory opgezet. Het adres van deze page_directory
; maakt niet zoveel uit zolang het geen andere dingen overschrijft (bios, eigen kernel).
PAGE_DIRECTORY equ 0x9C000
; Het adres van de eerste en enige page table die we hier gebruiken.
PAGE_TABLE equ 0x9D000

; Mogelijke waardes voor de attributen van de page directory's en de page table's
PAGE_PRESENT equ 0x0001
PAGE_READWRITE equ 0x0002
PAGE_USER equ 0x0004
; De bits 0x0008 en 0x0010 zijn gereserveerd voor toekomstig gebruik door intel
PAGE_ACCESSED equ 0x0020
PAGE_DIRTY equ 0x0040
; Het attribuut van de elementen in de page directory en de page table
ptattribuut equ (PAGE_PRESENT | PAGE_READWRITE)


SECTION .setup
_setup:
cli
; Vul de page table zodat deze de eerste 4 mb opvult.
mov eax, PAGE_TABLE
mov edx, 0x00000000 | ptattribuut
call map

; Hier vullen we de page directory met nullen zodat er niet per ongeluk
; vreemde waardes in de page directory terechtkomen.
mov ecx,1024
mov edi,PAGE_DIRECTORY
xor eax,eax ; eax = 0
rep stosd

; Hier slaan we het adres van de page table en het attribuut ervan op in het eerste
; en in het 768de element. Hierdoor is het echte adres 0 bereikbaar via virtueel
; adres 0 en via virtueel adres 3gb (0xC000 0000)
mov dword [PAGE_DIRECTORY], PAGE_TABLE | ptattribuut
mov dword [PAGE_DIRECTORY + 768*4], PAGE_TABLE | ptattribuut
; Eerst kopieeren we het adres van de page directory in cr3
mov eax, PAGE_DIRECTORY
mov cr3, eax
; Hier wordt paging ingeschakeld door de laatste bit van cr0 aan te zetten.
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
;Hier maken we een far jump naar 3gb, we komen nu uit in de sectie kernel bij _start.
jmp 0x08:KERNEL_VMA

; Dit is de plaats waar de echte waardes van de multiboot header in het bestand komen te staan
; align 4 is hier nodig om ervoor te zorgen dat grub het magische getal en dus deze gegevens vindt.
align 4
dd MULTIBOOT_MAGIC
dd MULTIBOOT_FLAGS
dd MULTIBOOT_CHECKSUM

; De functie map neemt twee argumenten aan:
; edx: Het 'echte' adres van het begin van de 4mb die in de page table gemapt moeten worden.
; eax: Het adres van de page table waarin de adressen moeten komen en het attribuut
map:
mov ecx, 1024 ; We doorlopen de volgende lus 1024 keer (een page table heeft 1024 elementen)
lmap:
mov dword [eax], edx ; Kopieer het 'echte' adres naar de goede plaats in de page table
add eax, 4 ; We schuiven een element op in de page table (een element is 32bits, dus 4 bytes)
add edx, 4096 ; We schuiven ook het 'echte' adres 4kb (4096 bytes) op
loop lmap ; En we gaan weer naar het begin van de loop
ret

; Zogau paging aangezet is komen we hier aan. We zitten nu op virtueel adres 0xC0101000
; oftewel 3gb + 1mb + 4kb. Die 4kb komt omdat deze sectie 4kb aligned is dmv het linker-script.
SECTION .kernel
_start:
;Het eerste wat we doen is een stack aanmaken
mov esp,stack
; Sla ebx op op de stack. Let erop dat we hierboven nergens ebx gewijzigd hebben
; dus dat ebx nog steeds een pointer naar het multiboot_info structure bevat.
; Deze pointer is nu het eerste argument voor _kmain.
push ebx
call _kmain

; Dit is de sectie die de stack bevat. De stack is 4kb groot. Dit lijkt me voorlopig meer dan genoeg.
; Als de kernel zo uitgebreid wordt dat een grotere stack nodig is, moet alleen de waarde 4096 hieronder
; aangepast worden.
SECTION .bss
resb 4096
stack:

En dan hier mijn linker-script:



OUTPUT_FORMAT("elf32-i386")
ENTRY(_setup)
phys = 0x00100000;
virt = 0xC0100000;
SECTIONS
{
.setup phys : AT( phys )
{
setup = .;
*(.setup)
. = ALIGN(4096);
}
.kernel virt : AT( ADDR(.setup) + SIZEOF(.setup) )
{
kernel = .;
*(.kernel)
*(.text)
. = ALIGN(4096);
}
.data : AT( ADDR(.setup) + SIZEOF(.setup) + SIZEOF(.kernel) )
{
data = .;
*(.data)
. = ALIGN(4096);
}
.bss : AT( ADDR(.setup) + SIZEOF(.setup) + SIZEOF(.kernel) + SIZEOF(.data) )
{
bss = .;
*(.bss)
. = ALIGN(4096);
}
_end = .;
}