# 十一、
來源:[JOS學習筆記(十一)](http://blog.csdn.net/roger__wong/article/details/9632311)
不知不覺已經寫了11篇日志了,本篇博客將完成LAB 4的PART A的剩余部分,包括內核鎖、進程(環境)的簡單調度算法,以及fork系統調用。
## 一、內核鎖
### 1、鎖實現
考慮到當多個CPU同時陷入內核的場景,若對于關鍵數據結構不加鎖必然就會導致重入錯誤(如cprintf不加鎖會在屏幕上輸出奇怪的結果),因此使用鎖來保證內核函數內部的邏輯正確性是很有必要的。
內核鎖相關代碼都在spinlock.c和spinlock.h中,關鍵代碼如下:

以及x86.h里面的xchg函數:

可以看到,xchg函數首先使用lock指令使xchgl變為原子操作,然后嘗試將xchgl兩個操作數互換,并把原先第一個操作數的結果放入result中返回。
若此時鎖是空閑的,則xchg返回0,spin_lock函數執行完成,否則繼續執行pause指令,然后接著執行xchg函數直到其返回值為1.
關于lock引用一段匯編手冊的資料:
總線加鎖前綴“lock”,它是為了在多處理器環境中,保證在當前指令執行期間禁止一切中斷。這個前綴僅僅對ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD,XCHG指令有效,如果將Lock前綴用在其它指令之前,將會引起異常。
關于pause:
提升spin-wait-loop的性能,當執行spin-wait循環的時候,笨死和小強處理器會因為在退出循環的時候檢測到memory order violation而導致嚴重的性能損失,pause指令就相當于提示處理器哥目前處于spin-wait中。在絕大多數情況下,處理器根據這個提示來避免violation,藉此大幅提高性能,由于這個原因,我們建議在spin-wait中加上一個pause指令。(出自于intel 匯編手冊)
### 2、實驗相關:
實驗要求在以下4個地方加鎖:
(1)i386_init中,啟動多個ap之前。
(2)mp_main中,開始把任務調度到cpu上之前。
(3)trap中,若從用戶態陷入內核則加鎖。
(4)env_run中,從內核態返回用戶態需要釋放鎖。
加鎖后,將原有的并行執行過程在關鍵位置變為串行執行過程,整個啟動過程大概如下:
i386_init-->BSP獲得鎖-->boot_ap-->(BSP建立為每個cpu建立idle任務、建立用戶任務,mp_main)--->BSP的sched_yield-->其中的env_run釋放鎖-->AP1獲得鎖-->執行sched_yield-->釋放鎖-->AP2獲得鎖-->執行sched_yield-->釋放鎖.....
其中括號表示并行執行
具體代碼如下:
(1)i386_init

(2)mp_main

(3)trap

(4)env_run

## 二、任務調度
### 1、原理
在JOS中,任務調度在內核中是函數sched_yield,同時在用戶態有相應的系統調用sys_yield也可以調用內核中的這個函數。
i386_init啟動時,在boot_ap函數調用后,為每個CPU創建一個idle任務,相應代碼在user/idle.c,通過代碼可以看到,這些任務使用一個死循環,不斷調用sys_yield嘗試切換任務。
所以在這種機制下我們需要實現sched_yield函數,具有以下幾個要求:
(1)找到狀態為runnable的任務,并切換執行
(2)如果找到一個running狀態的任務,且此任務執行的CPU為當前CPU,也可將此任務切換執行
(3)若沒有runnable任務,則執行idle任務。
(4)從當前CPU執行的任務處開始遍歷鏈表(為了保證公平性)
### 2、代碼
sched_yield的任務調度代碼如下:

首先找到CPU當前任務的下標,然后從下標的下一個開始遍歷數組。博主這段代碼寫的比較笨,主要是為了方便調試所用。
同時還要增加系統調用的部分代碼,把進入內核態后的系統調用號和具體的系統調用對應起來,較為容易故不再詳細論述。
## 三、fork
### 1、原理
fork作為一個系統調用,其功能是根據父進程創建出一個一模一樣的子進程,若返回的是0則說明是子進程,否則是父進程,同時返回值為子進程的系統調用號。
JOS實現fork過程采用的是用戶態“類庫”的形式封裝了一系列系統調用,包括創建新進程、設置新進程狀態、虛擬地址映射等等,這部分已經在user/dumbfork.c中封裝好了,實驗所要求的是實現相關的系統調用,包括:
+ sys_exofork:若為父進程返回子進程號,子進程則返回0。
+ sys_env_set_status:設置子進程格式為runnable或者not_runnable
+ sys_page_alloc:分配一個物理頁并對應到某個虛擬地址
+ sys_page_map:拷貝父進程的某個PTE,以此來建立子進程的虛擬內存映射
+ sys_page_unmap:解除某個虛擬地址的映射(在PART B中使用)
### 2、代碼
sys_exofork:

可以看出,該函數首先復制各寄存器的狀態(env_tf),然后系統調用本身返回子進程的id,因為調用此系統調用的進程為父進程。同時將子進程的eax寄存器設置為0,因為系統調用的結果存放在eax寄存器中,這樣子進程返回后得到的系統調用返回結果就為0。
sys_env_set_status

首先判斷狀態會否合法,然后根據進程ID進行查找,最后設置狀態并返回。
sys_page_alloc:

按實驗要求判斷各種條件。
sys_page_map:

主要是一些判斷+LAB 2中的函數的封裝,沒什么可說的。
sys_page_unmap:

到此PART A基本結束了,運行結果如下:

PS:寫起來怎么感覺這個實驗好簡單啊,做起來怎么感覺難到爆啊。。。。