JOS 核心啟動過程

2021-06-04 00:04:06 字數 4284 閱讀 4661

開機引導程式bootstrap



+------------------+  

| 32-bit |

| devices |

| |

/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\

| |

| unused |

| |

+------------------+

| |

| |

| extended memory |

| |

| |

+------------------+

| bios rom |

+------------------+

| 16-bit devices, |

| expansion roms |

+------------------+

| vga display |

+------------------+

| |

| low memory |

| |

+------------------+

bios:

從上圖可以看出,bios的位址是從0xf0000-0x100000(1m),這時候是實模式(real mode)定址空間為20位,1m.具體實體地址的計算方法為physical address = 16 * segment + offset 。intel把8088處理器設計成當pc 上電以後cs:ip的值固定為0xffff0,bios「硬連線」到位址為0x000f0000-0x000fffff的範圍。

[f000:fff0] 0xffff0:    ljmp   $0xf000,$0xe05b
上電後執行的第一條指令一般是轉移指令,因為只差16位元組就到了定址空間的最大處了,這16個位元組的空間幾乎什麼都不能做,所以,一般回跳到更小的位址。比如這裡的0xfe05b。

bios執行時會建立起乙個中斷描述符表(這個中斷描述符表貌似不是以後的中斷描述符表,也就是說是暫時的)、初始化各種型別的裝置(比如vga顯示器)。當初始化pci匯流排和所有bios檢測到的重要的裝置之後,bios會去查詢乙個可以啟動的裝置(硬碟,光碟,軟盤,u盤等等)。找到之後,會把該裝置的boot loader讀進記憶體並且把控制轉移給boot loader。對於硬碟啟動器,這時就是把硬碟的第乙個扇區(512b)載入進記憶體(載入到從位址為0x7c00開始的記憶體段),並且從0x7c00開始執行。0x7c00是行業標準。

the boot loader

boot loader,顧名思義,就是在boot階段去load。load什麼呢?當然是kernel了!

對!在這個階段,計算機要做的事情是把bios找到的啟動盤裡的作業系統核心載入進記憶體並且開始執行核心。因為所有程式只有載入進記憶體了才可能被執行(bios程式例外,因為硬體專門給bios分配了一段位址空間),所以,這個願望的產生應該很容易理解了。

核心其實就是乙個可執行程式,格式是elf。它被固定的放在boot loader所在扇區的後乙個扇區。一般認為boot loader 小於512b,所以可以放在第乙個扇區之內,而核心檔案則從第二個扇區放起,至於到**結束,這就不一定了,但是在elf頭中有指明,所以只要拿到elf的頭就可以了。但是現在的boot loader越來越大,乙個扇區可能已經裝不下了,這種情況應該在寫boot loader的時候會解決,這裡不討論它。

bios 把bootloader程式所在的扇區固定載入到0x7c00-0x7dff(512b),並且用乙個jmp指令把cs:ip設定成0000:7c00. cpu的控制權就從bios傳遞到了bootloader。

前面說過,bootloader的任務是把真正的核心程式從硬碟讀到記憶體當中,那麼,讀到**呢?讀到從位址為0x100000(1m)開始的記憶體裡!可是這個時候的cpu定址空間只有20位,1m!這個時候處於實模式下,是不能訪問高於1m 的位址空間的!怎麼辦?只能進入保護模式了,因為在保護模式下可以定址高於1m 的空間。

所以,bootloader 實際做了兩件事:進入保護模式和載入核心程式。

進入保護模式:

lgdt    gdtdesc

movl %cr0, %eax

orl $cr0_pe_on, %eax

movl %eax, %cr0

這裡:

.p2align 2                                # force 4 byte alignment

gdt:

seg_null # null seg

seg(sta_x|sta_r, 0x0, 0xffffffff) # code seg

seg(sta_w, 0x0, 0xffffffff) # data seg

gdtdesc: #48bit in total

.word 0x17 # sizeof(gdt) - 1(lower 16bit)

.long gdt # address gdt (higher 32bit)

從這裡可以看出,核心只設定了資料段和**段,並且起始位址都設為0x0,limit都設為最大值0xffffffff。也就是說,保護模式內定址時,線性位址等於offset,在沒有開啟分頁機制時,這個位址也等於實體地址。三種位址的關係見下圖:

bootloader分為兩部分,開啟保護模式是用組合語言寫的,讀磁碟檔案是用c語言寫的。在進入c 語言環境之前還有一件事情要做,那就是設定堆疊。本著簡單明瞭,又不浪費記憶體的原則,可以把bootloader的開始位址(start/0x7c00)設為sp的值。由於棧是向下增長的,所以不會和bootloader的**相衝突。

另外,將控制保護模式開啟的cr0_pe_on置位以後,並沒有真正進入保護模式,因為cs 、ds等的值還是原來的值。在movl    %eax, %cr0 語句之後不可能再用另外乙個語句來設定cs的值了,因為下一步的定址將是按照保護模式的定址方式了,而cs 原來的值自然就不對了,從而會導致出錯。乙個很好的解決方案是使用一條ljmp語句來實現:

ljmp    $prot_mode_cseg, $protcseg。
當然,ds 的值就可以直接用彙編語句設定咯:

movw    $prot_mode_dseg, %ax    # our data segment selector

movw %ax, %ds # -> ds: data segment

movw %ax, %es # -> es: extra segment

movw %ax, %fs # -> fs

movw %ax, %gs # -> gs

movw %ax, %ss # -> ss: stack segment

至此,就可以安心的去執行c語言**了:

call bootmain
進入用c 語言編寫的讀核心**的程式了。

載入核心:

首先載入包含核心的第乙個扇區到0x10000,注意,不是1m,因為這裡要得到的只是elf頭,不是真正的核心**。

在驗證魔數之後,再通過讀取elf頭裡面的引數(包括核心**的偏移位址,核心**的長度,核心**載入到記憶體的位置等等)去載入真正的作業系統核心(elf裡面的乙個program)。

最後,跳轉到elf頭裡面指定的e_entry所在的位置進行執行。

((void (*)(void)) (elfhdr->e_entry))();
至此,核心啟動了!大 功 告 成!!

MIT 6 828 JOS 啟動過程總結

1.1 cpu啟動後,它會先載入bios模組 到記憶體中執行。1.2 bios會初始化硬體裝置,初始化初始中斷描述符表,這個中斷描述符表叫 初始 是因為它是供記憶體描述符表初始化之前使用的,核心描述符表初始化後就會被清除。1.3 bios會從啟動盤中讀取第一塊內容並載入到記憶體的0x7c00,之後就...

linux核心啟動過程

第一步 電腦加電後cpu開始自身初始化,然後從某個固定位置 一般為0xfffffff0 取指令開始執行,此指令為跳轉指令,跳轉到bios 首部。第二步 bios開始加電自檢進行post power on self test 此階段完成系統硬體檢測,包括記憶體 系統匯流排檢測等,然後bios讀取啟動裝...

Linux核心啟動過程

linux核心啟動過程 第一篇日誌就摘錄一下linux核心的啟動過程,參考了 linux核心移植和yaffs2根檔案系統製作 嵌入式linux系統從軟體角度看可以分為四部分 引導引導程式 bootloader linux核心,檔案系統,應用程式。bootloader是系統啟動時執行的第一段 它主要用...