<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從零開始寫 OS 內(nèi)核 - 加載并進(jìn)入 kernel

          共 4729字,需瀏覽 10分鐘

           ·

          2021-06-13 20:28

          作者: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 磁盤鏡像

          接上一篇 虛擬內(nèi)存初探,本篇將正式加載并啟動(dòng) kernel,也就是圖中綠色的部分:


          當(dāng)然 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 長什么樣?
          答案非常簡單:kernel 和你平時(shí)用 C 語言寫的可執(zhí)行程序幾乎沒有任何區(qū)別,也是從一個(gè) main 函數(shù)開始。
          下面我們就實(shí)現(xiàn)我們的第一個(gè) kernel:
          main() {
            while (1) {}
          }
          就是這樣簡單,除了一個(gè) while 循環(huán),沒有任何其它東西,但它足以用作我們這里的 demo。

          編譯 kernel

          這里有很多編譯參數(shù),例如以 32 位編碼,禁用 C 標(biāo)準(zhǔn)庫等(這是我們自己定制的 OS,和 C 標(biāo)準(zhǔn)庫不可能兼容)。
          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
          這里會(huì)用到一個(gè) link 配置文件 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)到該地址。
          另外上面還定義了整個(gè)可執(zhí)行文件的入口函數(shù)為 main。
          編譯鏈接后的 kernel 是一個(gè) ELF 格式的二進(jìn)制,我們不妨將它反匯編 dump 看一下:
          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)足夠大了,完全夠用。
          現(xiàn)在磁盤鏡像終于變成了這樣:

          讀取并加載 kernel

          鏡像準(zhǔn)備完畢,接下來就可以將 kernel 讀取并且加載了。首先還是給出代碼鏈接 init_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ù)制到它們被 編址 的地方;
          首先來看第一步“讀取”。我們選擇的是虛擬內(nèi)存頂部的 1MB,即 (0xFFFFFFFF - 1MB) ~0xFFFFFFFF 的 1MB 空間作為二進(jìn)制鏡像的存放地址。當(dāng)然也要為它分配相應(yīng)的物理頁 frames,在 page table 中建立映射。然后就可以像之前讀取 mbr 和 loader 一樣,將 kernel 鏡像讀取進(jìn)來。
          接下來是第二步“加載”。這里涉及到了根據(jù) ELF 文件格式的規(guī)范進(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

          一切準(zhǔn)備就緒,接下來就可以真正進(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
            ret
          首先初始化了 CPU 的浮點(diǎn)數(shù)單元,防止它后面異常。
          然后我將 stack 移到了比較高的地址 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) 指定的。
          順利的話,運(yùn)行的結(jié)果如下:


          程序已經(jīng)成功地進(jìn)入 kernel 并且運(yùn)行到了 0xC0800003 處,就是那個(gè) while 循環(huán)的位置,這將是 kernel 征途的真正開篇:)


          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 27
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级a一级a爱片免费免免高潮按摩 | 青青操美女视频 | 男女互操网站 | 就要撸在线视频 | 国产乱妇交换做爰XXXⅩ麻豆 |