Linux 0.11源碼閱讀筆記-中斷過程 是什麼中斷 中斷發生時,電腦會停止當前運行的程式,轉而執行中斷處理程式,然後再返回原被中斷的程式繼續運行。中斷包括硬體中斷和軟體中斷,硬中斷是由外設自動產生的,軟中斷是程式通過int指令主動調用。中斷產生時,會有一個中斷號,根據中斷號可在中斷向量表中選擇 ...
Linux 0.11源碼閱讀筆記-中斷過程
是什麼中斷
中斷發生時,電腦會停止當前運行的程式,轉而執行中斷處理程式,然後再返回原被中斷的程式繼續運行。中斷包括硬體中斷和軟體中斷,硬中斷是由外設自動產生的,軟中斷是程式通過int指令主動調用。中斷產生時,會有一個中斷號,根據中斷號可在中斷向量表中選擇對應的中斷處理程式執行。
中斷在linux當中非常重要,是用戶態代碼與和心態代碼相互切換運行的橋梁。進程調度依賴於時鐘中斷進入內核,系統調用也是依賴int 80
軟中斷進入內核執行。
中斷處理過程
以int 80中斷為例。system_call
過程代碼是 int 80
中斷處理程式,是所有系統調用的入口。位於linux-0.11/kernel/system_call.s
文件中。
sytem_call
執行過程
- 保存運行環境:保存被中斷程式的運行環境,包括指令地址(PC)、段等寄存器的值。
- 執行系統調用:根據系統調用號,查找系統調用函數地址表,並執行系統調用函數
- 執行調度程式:判斷進程時間片是否處於不可運行狀態(時間片用完或者處於阻塞狀態),若不可運行,則執行調度程式。
- 進行信號處理:若被中斷程式為用戶態進程,會先判斷信號是否產生,並執行相應的信號處理程式。
- 恢復運行環境:恢復被中斷程式的運行環境。
system_call代碼及註釋
### int 0x80 - linux系統調用入口點(調用中斷int 0x80,eax 中是調用號)
system_call:
cmpl $nr_system_calls-1,%eax # 調用號如果超出範圍的話就在eax中置-1並退出
ja bad_sys_call
# 保存原段寄存器值
push %ds
push %es
push %fs
# edx、ecx、ebx作為系統調用的參數
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
# 設置內核段
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
# fs指向用戶程式的數據段。
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
# 調用系統調用號對應的系統調用函數
call sys_call_table(,%eax,4) # 間接調用指定功能C函數
pushl %eax # 把系統調用返回值入棧
# 如果進程時間片(counter)用完或者狀態(state)非就緒,則執行調度程式
movl current,%eax # 取當前任務(進程)數據結構地址→eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
# 中斷處理程式後半段,返回被中斷程式繼續執行。
ret_from_sys_call:
# 若被中斷程式為0號進程,直接返回。
movl current,%eax # task[0] cannot have signals
cmpl task,%eax
je 3f # 向前(forward)跳轉到標號3處退出中斷處理
# 若被中斷程式運行在內核態(例如其它可被中斷中斷程式),則直接返回
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
# 若被中斷程式為用戶進程,則先處理進程的信號
# 通過信號點陣圖,判斷產生的信號,然後調用do_signal執行對應的信號處理程式
movl signal(%eax),%ebx # 取信號點陣圖→ebx,每1位代表1種信號,共32個信號
movl blocked(%eax),%ecx # 取阻塞(屏蔽)信號點陣圖→ecx
notl %ecx # 每位取反
andl %ebx,%ecx # 獲得許可信號點陣圖
bsfl %ecx,%ecx # 從低位(位0)開始掃描點陣圖,看是否有1的位,若有,則ecx保留該位的偏移值
je 3f # 如果沒有信號則向前跳轉退出
btrl %ecx,%ebx # 複位該信號(ebx含有原signal點陣圖)
movl %ebx,signal(%eax) # 重新保存signal點陣圖信息→current->signal.
incl %ecx # 將信號調整為從1開始的數(1-32)
pushl %ecx # 信號值入棧作為調用do_signal的參數之一
call do_signal # 調用C函數信號處理程式(kernel/signal.c)
popl %eax # 彈出入棧的信號值
# 返回被中斷程式
3: popl %eax # eax中含有上面入棧系統調用的返回值
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret # 特權級中斷返回指令
用戶棧與內核棧之間的切換
特權級發生變化時,會涉及到內核棧和用戶棧的切換。linux具有兩個特權級,內核態(0)和用戶態(3)。int指令可以從用戶態轉入內核態,iret指令可以從內核態返回用戶態。
用戶棧到內核棧
中斷引起CPU特權級從3級到0級的變化,此時CPU會進行用戶棧到內核棧的切換操作。
- 獲取內核棧記憶體地址信息。CPU從當前任務狀態段TSS(PCB)中取得新棧的段選擇符和偏移值,內核棧指針從TSS的ss0(選擇符)和esp0欄位中獲得。
- 將用戶棧地址信息保存在內核棧中。將用戶態棧指針和代碼地址信息ss、esp、cs、eip壓入內核棧中。
- 將ss、esp、cs、eip指向內核棧和代碼地址。
內核棧到用戶棧
iret指令引起CPU特權級從0級到3級的變化,此時CPU會進行內核棧到用戶棧的切換操作。
iret指令將先前壓入內核棧的用戶進程棧和代碼地址信息cs、esp、ss、esp信息從棧里彈出,載入到相應的寄存器中,重新執行用戶進程。
進程調度的實現機制
每個用戶進程有自己的內核棧,進程調度時,將棧寄存器指向需要運行的進程的內核棧,執行iret指令即可繼續運行該進程。
內核棧的切換
進程調度的通過內核棧之間的切換,實現進程之間的切換運行。為什麼每個進程都需要一個內核棧?進程需要保存運行狀態信息(各種寄存器信息),等待被調度程式選中運行。
進程切換
進程切換的實質在於進程狀態(上下文)的切換,即將CPU的當前進程狀態替換成新進程的狀態。被替換進程的進程狀態會保存在其對應的tss數據結構中,等待恢復運行。任務寄存器TR會保存當前任務的TSS指針,TSS數據結構總保存進程的運行狀態。
具體過程為:
- 當前進程通過中斷進入內核,用戶態ss、esp、cs、ip寄存器信息保存在內核棧中,切換到內核態運行,使用內核堆棧
- schedule調度程式時,將當前進程內核態運行信息保存在當前任務的tss數據結構中,然後切換到新進程的內核運行狀態,在內核中運行新的進程。
- 新運行的進程通過iret指令從內核態轉到用戶態運行,用戶態的運行狀態信息在其內核棧中。
參考
- Linux 內核完全註釋 內核版本0.11 - 趙炯
PS:錯漏指出,請大佬指正,在評論區可交流學習。