# 十、
來源:[JOS學習筆記(十)](http://blog.csdn.net/roger__wong/article/details/9348057)
> 神說、進程要有多個、可以分片、切換、互不影響.
> 并要支持多任務、多處理器并行.事就這樣成了。
> 于是 神造了多個內核棧,又開辟多個寄存器。
> 就把這些擺列在內存里、內核空間里、
> 管理多核,分別任務. 神看著是好的.
> 有晚上、有早晨、是第四日。
博主寫完論文,又寫了個簡單的編譯器,然后回來再拾起來差點爛尾的JOS發現代碼已經完全看不懂了,花了一整天時間復習之前自己寫的博客,才勉強做了實驗四的一點。
本篇博客內容對應課程地址( http://pdos.csail.mit.edu/6.828/2011/labs/lab4/ )locking標簽之前的內容,也就是多核CPU的初始化工作。博客分以下2部分來進行闡述,1、多核啟動流程,2、實驗部分
## 1、多核CPU啟動流程
在內核加載和進行初始化時,即便是有一個多核CPU,也只能使用一個核,為了和JOS實驗中的描述一致,我們這里暫且把多個核(Core)稱之為多個CPU。
內核啟動過程中用于執行代碼的CPU叫做bootstrap processor (BSP),其它還未被使用的CPU叫做application processors (APs),所以在內核執行的時候必然會有的一個過程就是使用BSP來激活AP的過程。
對應JOS代碼是init.c中的init主函數,其關鍵片段如下:

lab2的 mem_init,lab3的env_init和trap_init,lab4的mp_init和lapic_init在這之后需要補充一個Big kernel Lock 暫時不用管,然后boot_ap函數啟動所有的CPU,接著有多少個CPU就建立多少個ENV。
在啟動過程中,mp_init和lapic_init是和硬件以及體系架構緊密相關的,通過讀取某個特殊內存地址(當然前提是能讀取的到,所以在mem_init中需要修改進行相應映射),來獲取CPU的信息,根據這些信息初始化CPU結構,大致流程就是這樣,博主因為能力所限,這部分就不進行詳細分析了。
boot_ap是個很有趣的函數。在函數中首先找到一段用于啟動的匯編代碼,該代碼和上一章實驗一樣是嵌入在內核代碼段之上的一部分,其中mpentry_start和mpentry_end是編譯器導出符號,代表這段代碼在內存(虛擬地址)中的起止位置,接著把代碼復制到MPENTRY_PADDR處。隨后調用lapic_startap來命令特定的AP去執行這段代碼。
這段匯編(mpentry.S)中所做的工作和entry.S所做的工作基本相同。需要注意的一點是每個CPU都有自己的寄存器和棧,需要啟動的AP目前還處于實模式,并且內存也沒有開啟分頁,因此所有和符號地址相關的操作都要非常謹慎的轉換為物理地址。
之后控制流跳轉到mp_main,值得注意的是從mpentry.S開始這些代碼都是執行在AP上的,此時BSP正在等待(while循環)AP啟動成功。在mp_main里可以看到AP初始化自己的env,trap等,接著改變自己的cpu數據結構中的cpu狀態標志為啟動成功,然后進入死循環空轉。BSP得到AP啟動成功的信息后接著嘗試啟動下一個AP。
整個啟動流程大概就是如此,下面進入實驗過程。
## 2、實驗
### 2.1
第一個任務是因為我們把代碼拷貝到了MPENTRY_PADDR處,所以要在初始化Page的時候把這一頁從Page_Free_List中取出來,以防止該頁被分配出去導致代碼拷貝的失敗進而不能正確啟動AP。

只需要在pmap.c的page_init函數的最后加上以上幾行代碼,首先獲取該地址對應的Page結構,然后從page_free_list中剝離就行。
如果正確的話,此時應該能通過check_page_free_list函數
### 2.2
第二個實驗要求為每一個核映射其內核棧,也就是對于編號為i的cpu,需要映射KSTACKTOP-i\*(KSTKSIZE+KSTKGAP)-KSTKSIZE 到KSTACKTOP-i\*(KSTKSIZE+KSTKGAP) 這塊虛擬地址空間到符號percpu_kstacks[i]所對應的物理地址處,改變mp_mem_init函數,代碼如下:

值得注意的是,雖然課程中說此時應該可以通過check_kern_pgdir,但實際上是通不過的。原因在于BSP的內核棧被映射了兩次,第一次是在lab3中將內核棧映射到了bootstack,當時只有一個CPU,自然映射的是BSP的堆棧;第二次是在我們剛才寫的代碼里,將BSP(也就是cpunum()==0的CPU)的內核棧映射到了percpu_kstack[0]。詭異的是bootstack地址不等于percpu_kstack地址,而check_kern_pgdir里居然要求兩種映射都存在的情況下才能通過。這顯然是不可能的,感覺是JOS實驗設計中的一個疏忽吧。因此我把檢查bootstack的那段代碼注釋掉了,這樣才得以通過。
### 2.3
第三個實驗是需要完成trap_init_percpu()函數,給相應的CPU加載正確的TSS段選擇子,代碼如下:

根據函數中的暗示+照葫蘆畫瓢,不難把代碼改成上述的樣子。
至此就完成了LAB4的PART A的一小部分,運行目前的OS可以看到(使用make qemu CPUS=4)可以發現創建了9個env,然后出現了未定義的sys_call,系統panic。