注:本文圖片引用自慕課的PPT
b站:https://www.bilibili.com/video/BV1d4411v7u7
用戶級線程
一個瀏覽器的模型:
兩個執行序列與一個棧的弊端:
Yield()
是一個切換函數,在線程切換中,我們需要像Yield()
這樣的函數允許B->C->D並直接返回 ->B,否則只能逐層調用返回,這不是線程切換想要的結果。
這種情況如果我們希望用Yield()
從D返回到B,即直接跳轉到204,但當函數返回時(ret指令),我們發現此時棧已經亂了,ret將會導致意外,會使程序跳轉到404.
從一個棧到兩個棧
如果使用兩個棧,那麼我們自然需要在Yield()
中切換棧,把棧頂指針存入到該線程的TCB(線程控制塊)中。
當程序在D中,調用Yield()
,當前棧2頂部壓入404,調用Yield()
時,交換棧,此時Yield()
函數返回時彈出的棧是棧1,而此時棧1頂部爲204,正好與我們跳轉到204的目的相同,因此Yield()
不需要jmp 204。
兩個線程:兩個TCB+兩個棧+切換的PC在棧中
用戶級線程的弊端
當用戶級線程調用內核時,進入內核態之後,若內核進程需要等待網卡IO,需要較長時間而導致進程阻塞,用戶態的多線程將沒有任何作用。
核心級線程
用戶線程是Yield()
,內核線程切換Schedule()
,Schedule()
調度是由系統決定,具有強制性,Yield()
用戶可主動釋放。
如果實現了核心級線程,則有效解決了僅用戶級線程的缺陷。
切換進程與切換線程:
- 進程需要分配資源,所以只有內核級的進程。
- 切換進程:切換指令流 + 切換資源
- 切換內核級線程即爲切換指令流,是切換進程中的一部分。
多處理器與多核的區別,多處理器每個運算器都有自己的緩存和內存映射,多核心處理器共用一個。
多核處理器要想有作用,必須支持核心級線程。
在多核處理器中, 因爲多個線程共用一個資源,多核被分配到多個線程,而只需一套MMU(內存管理單元)。
從一個棧到一套棧
每個用戶級線程即需要一個棧,而實現用戶級線程需要一套棧即兩個棧:用戶棧 + 內核棧
用戶棧到內核棧的關聯:
SS:SP爲用戶棧指針,CS:PC(IP)記錄用戶程序的位置。
esp 被賦值 爲TCB1中的值,執行sys_read()
而阻塞,內核線程調度使用switch_to()
,並切換棧。
cur是當前線程的TCB,next是下一個線程的TCB。
PC:CS 爲返回用戶態的地址,SS:SP指向用戶態棧。
要讓以上PC,CS,SS,SP都彈出棧,那麼? ? ? ? 的指令應爲iret
(中斷返回)
小結:內核線程switch_to的五段論
- 準備中斷:爲整個處理鏈條做準備,中斷進入內核態
- 中斷處理:當引發阻塞(IO 或時鐘中斷)時
- 引發切換,並找到下一個TCB(
next =...
) - 內核棧切換:交換指針完成棧交換後,執行 ret 指令。
- 中斷出口:交換後將彈出當前棧頂即上文圖中的
? ? ? ?
的命令,在上文已經解釋到,? ? ? ?
爲iret
(中斷返回),使 CS,PC,SS,SP等彈出棧,恢復到用戶態。 - (附加)因爲進程切換,還要切換地址映射表,到內存管理部分時再研究。
ThreadCreate 實現模型
- 申請內存:
get_free_page()
作爲TCB - 內核棧初始化
- 傳入用戶棧
- 並將兩個棧通過指針連接起來,將內核函數地址(圖中爲500)和 CS (0F)壓入棧,並壓入參數。
- 設置TCB指向內核棧
- TCB就緒,入隊。