進入中斷
在 main()
中:
- 首先在
A()
函數中系統調用fork()
,將B()
的地址壓入用戶棧。 fork()
引起中斷0x80
,進入內核。- 執行
int 0x80
時,還未進入內核,首先找到內核棧,壓入當前棧地址(即用戶棧);壓入當前CS:IP(用戶態)(ret = CS:IP) - 進入內核,執行
system_call
。
進入內核
-
剛進入內核,首先在內核態中的各種寄存器壓到棧中,即保護現場。
-
執行
sys_fork()
,繼續向下執行 -
state(%eax)
相當於state + _current
,與0
(就緒或運行態)作比較,非0
即阻塞,_current
即PCB,阻塞則調度(reschedule
)。 -
接下來再次判斷
counter + _current
判斷是否時間片用盡,若是則切換(reschedule
) -
執行中斷返回(
ret_from_sys_call
) -
reschedule:
執行的是_schedule()
.
schedule
next
是下一個進程的PCB- 核心是
switch_to
。
- linux 0.11 中基於TSS(Task Struct Segement) 切換,實驗中將其改爲基於內核棧的切換,TSS切換速度較慢,現代操作系統中大多爲內核棧切換。
- TSS 類似於內存快照,
ljmp
改變了當前TSS,所以CPU需要從新的TSS重新加載所有寄存器。
中斷出口
還原現場,並恢復到用戶態。
sys_fork()
創建一個進程(或內核級線程),就是要做成能切換的樣子。
copy_process
將父進程的棧都作爲參數,C語言中參數越靠後越靠近棧頂。
創建棧:
- get_free_page()
申請內存空間,注意:malloc()
是用戶態代碼。
- 設置TSS,使其能夠切換。
- 父子線程用戶棧相同,內核棧不同。
- 子進程的
eax = 0
,由於父進程和子進程的關係,它們都將執行int 0x80
的下一個代碼:mov res,%eax
而子進程將返回0,於是觀察fork()
經典的使用:
子進程將進入if
塊內,調用exec
,子進程將被更換新的代碼:
更換新的代碼,我們知道iret
指令將把棧彈出,這時CS:EIP將被更改到用戶態代碼段,那麼我們只需要更改棧中儲存的CS:IP即可,先偏移量EIP=0x1C
,並將EIP+%esp
壓入棧中,即EIP在棧中位置,執行do_execve
。
do_execve
,將程序入口地址給eip,更改代碼段;eip[3]正好等於SP,更改棧。