# 一、
來源:[JOS學習筆記(一)](http://blog.csdn.net/ROger__wonG/article/category/1310602)
> 起初 神創造天地。
> 地是空虛混沌.淵面黑暗. BIOS運行在水面上。
> 神說、要有mbr、就加載了mbr。
> 神看mbr是好的、就用mbr加載了kernel。
> 神稱mbr為引導程序、稱kernel為內核.有晚上、有早晨、這是頭一日。
用課余時間重新拾起JOS,作為一個碼農通過了解不同技術層面的機制對自己的技術水平提高非常大,而JOS作為一個MIT的開放課程,可讓我們從一無所有構造一個自己的操作系統,這無疑是學習OS的一個非常好的方法。
然而不可否認,操作系統本身是非常復雜的,即使是一個簡化過的、只有基本功能的OS,里面的代碼也夠我研究好久,所以我在學習之余寫這么幾篇博客,當作學習筆記。
我使用的是6.828版本。地址http://pdos.csail.mit.edu/6.828/2011/schedule.html
## 1、環境搭建
1. 建立一只ubuntu11.10虛擬機,剛做好的系統是裸系統并,沒有裝任何東西。
2. 裝git,vim,cscope,qemu,eclipse(本來想用vim+cscope看代碼的,結果因為本人太低端,vim還是玩不轉,所以又裝了eclipse用來看代碼)
## 2、start
首先讓我們從lab1開始,lab1的目的也就是讓我們熟悉一下os的啟動過程,所以這篇筆記也就不拘泥于里面的excerise了而直接嘗試去理解里面的代碼。
先按lab1的pdf里的說明將jos的代碼git下來:
```
git clone http://pdos.csail.mit.edu/6.828/2011/jos.git lab
```
然后拖到eclipse里,我們就可以閱讀代碼了。
通過講義(或者是經驗)我們知道,當計算機加電,首先會把bios加載進內存執行,然后bios從硬盤加載mbr,之后由mbr來加載操作系統或者grab之類的東西。
那么這個過程我們就會面臨很多問題
### (1)bios加載進了內存的什么地方?
直接上圖不解釋:
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
+------------------+ <- 0x00000000
```
通過圖我們可以看到加載到了960K--1MB的地方,為啥會加載到這個位置,估計是一些約定俗成的規定吧。
### (2)mbr被加載到了哪里?
在項目文件夾下make一下,編譯好內核然后打開obj/boot/boot.asm,這是編譯好的boot的反匯編文件,在剛開始的那幾行我們就能很明顯的看到有這么一個標志:
```
00007c00 <start>:
```
這說明start符號在內存中的位置“應該是”0x7c00,換句話說,這段程序“認為”它所處的位置是內存中的0x7c00,因此為了使mbr程序能正確的執行,bios會將其加載到0x7c00的位置,然后跳轉到0x7c00,將控制權給它。這時候我們的內存布局是這個樣子的:
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
| | <-0x00007c00 (boot loader here!)
| |
+------------------+ <- 0x00000000
```
### (3)boot loader被加載進來做了些什么?
這就是一個較為復雜的問題,查看源碼lab/boot.S,這是一個匯編文件,讀起來比較痛苦,當然還是比那個asm的反匯編要強很多。
首先關中斷,估計是在執行過程中不希望被打擾,然后開A20線(貌似為了和早期PC相兼容,算了,不要在意這些細節,只關注那些操作系統本身的東西就好),然后加在段表并開保護模式。
為啥要開保護模式?原因有2,首先是只有開了保護模式,才能訪問64K以上的地址空間,其次是因為在實模式下程序可以訪問整個地址空間的任意區域,太不安全了,因此在x86架構中引入了保護模式。
如何開啟保護模式?設置cr0寄存器的某一位即可,代碼:
```
movl %cr0,%eax
orl $cr0_PE_ON,%eax
movl %eax,%cr0
```
開啟保護模式之后,基址:偏移這種尋址方式就變成了段選擇子:偏移這種方式,而所謂的段選擇子就是段表中的索引,因此為了正確的進行段式地址變換,還需要加載段表。這就是為什么在裝在cr0之前需要先使用指令lgdt gdtdesc加載段表的原因。
再看段表的內容,也就是符號gdtdesc的位置,同樣在boot.S這個文件下方。
可以發現gdt里面有3個段,第一個段為空段(查相關資料才知道,這是x86中的規定,第一個段均為空段),第二個和第三個段的定義使用了SEG宏,跟蹤代碼到mmu.h,發現宏的第一個參數是type,第二個是base,第三個是limit,所以我們可知定義的第二個和第三個段均是基質為0,長度是4G的段,也就是整個32位地址空間。
可以看出,jos并沒有使用x86的段式地址變換來進行內存管理(起碼在lab1里沒有用),加載段表只是為了能正確的訪問32位地址空間而已。
之后boot.S 設置一些寄存器的值,然后就call bootmain,跳轉到boot/main.c這個文件里執行了。
值得注意的是,在任何函數調用前都要初始化棧,boot.S里很巧妙的將start作為棧的基址,因為棧空間是向下增長的,所以內存布局:
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
|------------------| <-0x00007c00 (boot loader here!)
| bootmain stack |
+------------------+ <- 0x00000000
```
### (4) bootmain做了什么:
bootmain終于是c文件了,終于不用再看蛋疼的匯編文件了。
bootmain負責的事情是講硬盤上的kernel加載到內存里并執行,那么第一個問題是,這個kernel存在硬盤的什么地方。
因為我們現在沒有任何文件系統,所以jos就“很友好”的將編譯好的kernel就放在mbr的后面,也就是第二個扇區(也可以說是第1個扇區,在這之前還有第0個扇區)的位置。
然后main.c里定義了兩個函數,readseg和readsect,從邏輯上將,第一個函數的功能是“將相對于kernel基址便宜offset個自己處之后的count各字節的東西讀到物理地址pa處”,而第二個的功能是“將相對于第二個扇區便宜offset字節的扇區里的內容讀到dst的位置”。
第一個函數調用第二個函數完成自己的功能,第二個函數牽扯到硬盤數據的讀寫,當然我沒有過多的花精力在這些更底層的內容上,不過看代碼貌似是將地址的不同位寫出到不同的端口(應該就是地址線吧),然后等待讀取。
大致明白了這兩個函數,就可以去看bootmain的邏輯了。
首先將8\*512B的字節讀到ELFHDR處,而ELFHDR是指向Elf結構體的指針,Elf結構代表一個elf頭,接著通過elf頭里的信息讀出這個elf的其它部分,并加載到相應地址上。
ELFHDR的位置是0x10000,所以elf頭會被加載到內存的這個位置(確切的說是線性地址(未經過段式變換的地址)的這個位置,但因為段表中的段基址是0,因此實際加載的位置也就是內存的位置,大概在bootloader上面一點點。
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
|------------------| <-0x00010000 (elf herader here!)
|------------------| <-0x00007c00 (boot loader here!)
| bootmain stack |
+------------------+ <- 0x00000000
```
通過elf header,并讀取里面的信息,可以逐段的把elf里面的段加載進來,并加載到相應位置,至于加載到哪里,要由ELF頭里面的信息所決定。因此,我們應該先研究一下elf頭里讀到的信息。
使用objdump -h obj/kern/kernnel可以看一下kernel的obj信息:

bootmain所做的工作就是先讀到file off得到在文件中的偏移,然后再讀取Size個字節,之后放到LMA所指定的地址處。
之后bootmain找出這個elf文件的entry(也在elf頭里),然后跳轉到這個頭執行。
這樣bootloader的工作就算完成了,接下來就是內核的工作了。