JOS(1) BootLoader

JOS在啟動(dòng)的過(guò)程(bootload過(guò)程)中一共經(jīng)歷了三個(gè)階段,分別是BIOS加載以及硬件檢測(cè)、實(shí)模式向保護(hù)模式的轉(zhuǎn)變以及加載kernel。故本文組織過(guò)程也按照啟動(dòng)過(guò)程依次敘述。

一、BIOS加載

計(jì)算機(jī)系統(tǒng)在電源鍵開(kāi)啟后,首先接管整個(gè)系統(tǒng)的是BIOS。BIOS主要完成以下幾個(gè)內(nèi)容:

  • 對(duì)計(jì)算機(jī)的硬件進(jìn)行檢測(cè)
  • 加載啟動(dòng)文件

在更進(jìn)一步的敘述BIOS之前,我們需要簡(jiǎn)單的了解x86的物理地址空間。下圖是一個(gè)典型的x86地址空間,這里需要注意的是,BIOS的內(nèi)存空間是從0x000F 0000 ~ 0x0010 0000共64KB。在啟動(dòng)電源后,BIOS將會(huì)加載到上述的內(nèi)存空間內(nèi)。

繪圖1.jpg

按照慣例,BIOS將會(huì)被加載到CS:0xF000,IP:0xFFF0 處,即0x000F FFF0。由于此地址已經(jīng)非常接近于0x0010 0000,留給BIOS執(zhí)行的內(nèi)存空間過(guò)少,所以BIOS加載執(zhí)行的第一條指令就是:
ljmp $0xF000, $0xE05B
將自己移動(dòng)到較低的地址空間,以保證足夠的內(nèi)存使其能夠繼續(xù)執(zhí)行。在BIOS運(yùn)行的過(guò)程中,將設(shè)立終中斷述表以及初始化各種設(shè)備,當(dāng)完成上述過(guò)程后,BIOS搜索能夠啟動(dòng)的設(shè)備(如軟盤(pán)、硬盤(pán)、CD-ROM等),對(duì)該設(shè)備的第一個(gè)扇區(qū)進(jìn)行讀取,最后將控制權(quán)轉(zhuǎn)移給扇區(qū)中存放的bootloader。
這里需要說(shuō)明的是:(1)在上述啟動(dòng)的過(guò)程中,CPU是運(yùn)行在實(shí)模式下; (2)對(duì)于硬盤(pán)來(lái)說(shuō),最基本存儲(chǔ)單元是扇區(qū)(sector),每個(gè)扇區(qū)容量為512個(gè)字節(jié)。對(duì)于一個(gè)可啟動(dòng)的硬盤(pán),其第一個(gè)扇區(qū)必須是bootloader,故bootloader不能占據(jù)過(guò)大的空間。

二、保護(hù)模式

在JOS中,bootloader將完成兩部分工作:

  • 實(shí)模式轉(zhuǎn)變?yōu)楸Wo(hù)模式
  • 加載內(nèi)核文件

在這部分中,我們將簡(jiǎn)要的敘述實(shí)模式向保護(hù)模式的轉(zhuǎn)變過(guò)程(/boot/boot.S)。保護(hù)模式下,系統(tǒng)將具有更大的尋址空間,并提供虛擬內(nèi)存、分段、分頁(yè)等機(jī)制。保護(hù)模式下的JOS介紹將會(huì)在后續(xù)的文章中介紹。
在這里需要注意的是,開(kāi)啟保護(hù)模式涉及到了cr0寄存器下的PE位(即第0位),當(dāng)PE置1后,CPU將開(kāi)啟保護(hù)模式,此時(shí)保護(hù)模式下的分段保護(hù)機(jī)制將會(huì)被一同開(kāi)啟(分頁(yè)機(jī)制沒(méi)有開(kāi)啟),故在開(kāi)啟保護(hù)模式前,需要設(shè)置好全局描述符表。
JOS中對(duì)于保護(hù)模式的開(kāi)啟有以下代碼:

lgdt   gdtdesc                      # 加載全局描述符表
movl   %cr0,         %eax
orl    $CR0_PE_ON,   %eax           # CR0_PE_ON = 0x1
movl   %eax,         %cr0           # 開(kāi)啟保護(hù)模式

gdt為預(yù)先設(shè)定好的全局描述符表。對(duì)于全局描述符表的介紹將會(huì)在下一篇文章中涉及,在這里需要知道的是,全局描述符表的第0項(xiàng)存儲(chǔ)的內(nèi)容必須為空(即為0)

gdt:
    SEG_NULL                                 # 空項(xiàng)
    SEG(STA_X | STA_R, 0x0, 0xffffffff)      # 代碼段
    SEG(STA_W, 0x0, 0xffffffff)              # 數(shù)據(jù)段
gdtdesc:
    .word 0x17        # 在全局描述符表中共設(shè)置了三項(xiàng),每項(xiàng)8字節(jié),共24字節(jié),故在此處設(shè)為(24-1),即0x17
    .long gtd

對(duì)于SEG_NULL、SEG()定義如下:

#define SEG_NULL     \
        .word 0, 0;  \
        .byte 0, 0, 0, 0
#define SEG(type, base, lim)                                   \
        .word  (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
        .byte  (((base) >> 16) & 0xff), (0x90 | (type)),       \
               (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)

可以看出,在gdt中,將代碼段和數(shù)據(jù)段全部映射到了4GB內(nèi)存空間中,這對(duì)于啟動(dòng)過(guò)程來(lái)說(shuō),是完全夠用的。

三、加載內(nèi)核

這一部分中,主要完成的工作就是將內(nèi)核文件加載到內(nèi)存中(/boot/main.c),并將控制權(quán)限交給內(nèi)核。在更進(jìn)一步的介紹之前,首先闡述ELF文件格式。對(duì)于ELF文件格式的定義在<inc/elf.h>中。我們無(wú)需深入的了解ELF文件格式(如希望深入了解的話,在MIT6.828的指定文獻(xiàn)中列出了ELF文件的詳細(xì)格式內(nèi)容),實(shí)際上來(lái)說(shuō),ELF類似于一個(gè)超大的“結(jié)構(gòu)體”,每一個(gè)部分都存放了一定的內(nèi)容,而對(duì)于該內(nèi)容的描述在“頭部”中存放。這里給出了JOS下<inc/elf.h>中的定義以及解釋。

struct Elf {
    uint32_t e_magic;   // must equal ELF_MAGIC
    uint8_t e_elf[12];
    uint16_t e_type;     // 表示該文件類型
    uint16_t e_machine;  // 運(yùn)行該程序需要的體系結(jié)構(gòu)
    uint32_t e_version;  // 文件版本
    uint32_t e_entry;    // 程序入口地址
    uint32_t e_phoff;    // Program header table在文件中的偏移量(以字節(jié)計(jì)數(shù))
    uint32_t e_shoff;    // Section header table在文件中的偏移量
    uint32_t e_flags;    // 對(duì)于IA32來(lái)說(shuō),計(jì)為0
    uint16_t e_ehsize;   // 表示ELF header大小
    uint16_t e_phentsize; // Program header table中每一項(xiàng)目的大小
    uint16_t e_phnum;     // Program header table有多少個(gè)項(xiàng)目
    uint16_t e_shentsize; // Section header table中每一項(xiàng)目的大小
    uint16_t e_shnum;     // Section header table有多少個(gè)項(xiàng)目
    uint16_t e_shstrndx;  // 包含節(jié)名稱的字符串是第幾個(gè)節(jié)(0開(kāi)始計(jì)數(shù))
};

struct Proghdr {
    uint32_t p_type;     // 當(dāng)前Program header所描述的段的類型
    uint32_t p_offset;   // 段的第一個(gè)字節(jié)在文件中的偏移
    uint32_t p_va;       // 段的一個(gè)字節(jié)在內(nèi)存中的虛擬地址
    uint32_t p_pa;       // 在物理內(nèi)存定位的相關(guān)系統(tǒng)中,此項(xiàng)是為物理地址保留的
    uint32_t p_filesz;   // 段在文件中的長(zhǎng)度
    uint32_t p_memsz;    // 段在內(nèi)存中的長(zhǎng)度
    uint32_t p_flags;    // 與段相關(guān)的標(biāo)志
    uint32_t p_align;    // 根據(jù)此值來(lái)確定段在文件以及內(nèi)存中如何對(duì)齊
};

有了上述的認(rèn)識(shí),就可以很容易的讀懂下述代碼。下述代碼主要是將內(nèi)核讀取到磁盤(pán)中,并最后將控制權(quán)移交給內(nèi)核。

#define SECTSIZE    512
#define ELFHDR    ((struct Elf *) 0x10000) // scratch space

void readsect(void*, uint32_t);
// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
void readseg(uint32_t, uint32_t, uint32_t);

void
bootmain(void)
{
    struct Proghdr *ph, *eph;

    // read 1st page off disk
    // 可以看出,內(nèi)核加載于0x10000處之上,一共加載了512字節(jié) * 8 = 4K,即分頁(yè)模式下一個(gè)完整的頁(yè)的大小
    readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);

    // is this a valid ELF? JOS中要求ELF文件的第一項(xiàng)必須為ELF_MAGIC
    if (ELFHDR->e_magic != ELF_MAGIC)
        goto bad;

    // load each program segment (ignores ph flags) 
    // 加載代碼段, 可以看出每個(gè)代碼段都規(guī)定了加載的位置以及大小
    ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
    eph = ph + ELFHDR->e_phnum;
    for (; ph < eph; ph++)
        // p_pa is the load address of this segment (as well
        // as the physical address)
        readseg(ph->p_pa, ph->p_memsz, ph->p_offset);

    // call the entry point from the ELF header
    // note: does not return!
    // 移交控制權(quán),e_entry即為入口函數(shù), 此函數(shù)不會(huì)返回,如果返回則意味著執(zhí)行出現(xiàn)了某種問(wèn)題,此后系統(tǒng)進(jìn)入死循環(huán),需要手動(dòng)重啟
    ((void (*)(void)) (ELFHDR->e_entry))();

bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);
    while (1)
        /* do nothing */;
}

對(duì)于內(nèi)核加載部分還是很容易理解的,不過(guò)由于從磁盤(pán)加載內(nèi)核過(guò)程中涉及到了大量的磁盤(pán)操作,而這些操作過(guò)于底層化,同時(shí)使用了c語(yǔ)言嵌套匯編(inb, insb等),這些函數(shù)的定義全部在<inc/x86.h>中,感興趣的話可以去閱讀。
以上內(nèi)容就是JOS啟動(dòng)的過(guò)程,其中主要涉及了實(shí)模式向保護(hù)模式的轉(zhuǎn)變以及內(nèi)核加載的過(guò)程。內(nèi)容還是相對(duì)容易理解的。在下一篇文章中,將會(huì)涉及JOS內(nèi)存機(jī)制的建立。我也會(huì)按照MIT6.828實(shí)驗(yàn)的順序依次寫(xiě)完。加油:{
PS:如果有想一同學(xué)習(xí)內(nèi)核/JOS的童鞋,歡迎聯(lián)系:zfzhang1992@126.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 背景 原本計(jì)劃自己學(xué)習(xí)寫(xiě)個(gè)操作系統(tǒng)的,但是工欲善其事必先利其器,先學(xué)習(xí)下別人是怎么做出來(lái)的,自己再動(dòng)手,自然...
    pingpong_龘閱讀 17,695評(píng)論 5 16
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡(jiǎn)單分配策略的問(wèn)題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 8,133評(píng)論 0 27
  • 轉(zhuǎn)載聲明:本文雖然不是本人100%原創(chuàng),但也是辛辛苦苦整理的,可以轉(zhuǎn)載,但請(qǐng)注明出處 這篇文章是關(guān)于折騰Windo...
    SOMCENT閱讀 8,307評(píng)論 3 37
  • 無(wú)非是一堆虛妄之火 燃就的一抔矜持的灰燼 2017.6.6
    查文瑾閱讀 527評(píng)論 2 4
  • 五顏六色的紙片脫離了黑色,展現(xiàn)著獨(dú)特的顏色。五顏六色的花瓣脫離了花蕾,凋謝時(shí)換得了憐憫。五顏六色的彩虹脫離了天空,...
    bo_ss閱讀 215評(píng)論 0 0

友情鏈接更多精彩內(nèi)容