JOS學習筆記(十一)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章