從零開始寫 OS 內(nèi)核 - 加載并進(jìn)入 kernel
作者:hyuan
來源:SegmentFault 思否社區(qū)
系列目錄
序篇 準(zhǔn)備工作 BIOS 啟動(dòng)到實(shí)模式 GDT 與保護(hù)模式 虛擬內(nèi)存初探 加載并進(jìn)入 kernel 顯示與打印 GDT 和 IDT,中斷處理 虛擬內(nèi)存完善 實(shí)現(xiàn)堆和 malloc 創(chuàng)建第一個(gè)內(nèi)核線程 多線程運(yùn)行與切換 鎖與多線程同步 進(jìn)程的實(shí)現(xiàn) 進(jìn)入用戶態(tài) 一個(gè)簡單的文件系統(tǒng) 加載可執(zhí)行程序 系統(tǒng)調(diào)用的實(shí)現(xiàn) 鍵盤驅(qū)動(dòng) 運(yùn)行 shell
kernel 磁盤鏡像

disk 和 memory(物理內(nèi)存)的數(shù)據(jù)對應(yīng)關(guān)系:
kernel page tables,即第一張圖的橙色部分,共 256 張占地 1MB。編寫 kernel
kernel ,即圖中綠色部分,它現(xiàn)在實(shí)際上還不存在,所以首先我們需要實(shí)現(xiàn)、編譯一個(gè)簡單的 demo 性質(zhì)的 kernel。如果對 kernel 是什么還沒有概念的同學(xué),可能會(huì)問:到底 kernel 長什么樣?main() {
while (1) {}
}
while 循環(huán),沒有任何其它東西,但它足以用作我們這里的 demo。編譯 kernel
gcc -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -no-pie -fno-pic -c main.c -o main.o
鏈接 kernel:
ld -m elf_i386 -Tlink.ld -o kernel main.o
link.ld:ENTRY(main)
SECTIONS
{
.text 0xC0800000:
{
code = .; _code = .; __code = .;
*(.text)
}
.data ALIGN(4096):
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
}
.bss ALIGN(4096):
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
end = .; _end = .; __end = .;
}
text 段的起始地址 0xC0800000,也是整個(gè) kernel 編址的起始。如果你還記得上一篇的內(nèi)容,我們規(guī)劃了 kernel 空間的虛擬內(nèi)存分布:
0xC0800000 將是 kernel 的入口地址,因?yàn)?nbsp;text 段會(huì)被加載到此處,往后依次是 data,bss 等段。loader 結(jié)束后將會(huì)跳轉(zhuǎn)到該地址。main。objdump -dsx kernel

main 函數(shù)的地址為 0xC080000,這是進(jìn)入 kernel 后的第一條指令。制作 kernel 鏡像
dd if=kernel of=scroll.img bs=512 count=2048 seek=9 conv=notrunc
seek=9 是因?yàn)榍懊?nbsp;mbr 和 loader 已經(jīng)在磁盤上占據(jù)了前 9 個(gè) sectors。這里 kernel 大小為 2048 個(gè) sectors 共 1MB,對于我們這個(gè)項(xiàng)目而言已經(jīng)足夠大了,完全夠用。
讀取并加載 kernel
mbr 和 loader 的加載不同,這里將讀取和加載兩個(gè)詞分開,是因?yàn)樗鼈兪莾蓚€(gè)步驟:讀?。菏菍?kernel 磁盤鏡像的 原始二進(jìn)制 復(fù)制到內(nèi)存中某空閑處,這里的二進(jìn)制是 ELF 格式的; 加載:是將前一步得到的 ELF 可執(zhí)行二進(jìn)制進(jìn)行解析,將每一個(gè) section復(fù)制到它們被 編址 的地方;
(0xFFFFFFFF - 1MB) ~0xFFFFFFFF 的 1MB 空間作為二進(jìn)制鏡像的存放地址。當(dāng)然也要為它分配相應(yīng)的物理頁 frames,在 page table 中建立映射。然后就可以像之前讀取 mbr 和 loader 一樣,將 kernel 鏡像讀取進(jìn)來。program header table 中獲取每個(gè) section 的位置和大小,以及加載的內(nèi)存地址(當(dāng)然是 virtual 地址),然后將數(shù)據(jù) copy 過去。這一次加載的內(nèi)存地址,才是 0xC0800000 開始的位置。當(dāng)然在 copy 之前,當(dāng)然要為它們預(yù)先分配好 frames 并且在 page table 中建立好內(nèi)存映射。這一切工作都在 allocate_pages_for_kernel 這個(gè)函數(shù)中提前完成了。進(jìn)入 kernel
init_kernel:
call allocate_pages_for_kernel
call load_hd_kernel_image
call do_load_kernel
; init floating point unit before entering the kernel
finit
; move stack to 0xF0000000
mov esp, KERNEL_STACK_TOP - 16
mov ebp, esp
; let's jump to kernel entry :)
jmp eax
retstack 移到了比較高的地址 0xF0000000 位置,這當(dāng)然不是必須的,當(dāng)前的 stack 位置其實(shí)也不錯(cuò)(大約在 0x7B00 以下附近的位置,這是在 mbr 中轉(zhuǎn)移過去的,如果你還記得的話)。只是我希望后面的 stack 位置能被移到 0xC0000000 以上的 kernel 空間中,所以才這么做了一步。stack 的位置是比較靈活的,只要是一個(gè)閑置的,不會(huì)受到干擾的地方就可以。jmp eax 一條指令跳到了 kernel 入口處。eax?這是上面函數(shù) do_load_kernel 的返回值,這個(gè)函數(shù)就是我們解析加載 kernel 的 ELF 二進(jìn)制的函數(shù),它會(huì)返回值 kernel 的入口地址,即 main 函數(shù)地址,這個(gè)地址是由 ELF 文件中 ELF Header 的 e_entry 字段給出的。ELF 可執(zhí)行二進(jìn)制的入口地址是在鏈接階段確定的,它實(shí)際上是由之前的 link.ld 里的 ENTRY(main) 指定的。
0xC0800003 處,就是那個(gè) while 循環(huán)的位置,這將是 kernel 征途的真正開篇:)
評論
圖片
表情
