不知不覺已經寫了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_runnablesys_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:寫起來怎麼感覺這個實驗好簡單啊,做起來怎麼感覺難到爆啊。。。。