中斷與異常詳解(二)


中斷或異常發生之前

CPU 執行了當前指令之后,CS EIP 這對寄存器中所包含的內容就是下一條將要執行 指令的邏輯地址。在對下一條指令執行前,CPU 先要判斷在執行當前指令的過程中是否發生 了中斷或異常

如果發生了一個中斷或異常

那么 CPU 將做以下事情

• 確定所發生中斷或異常的向量i(在 0~255 之間)。

• 通過 IDTR 寄存器找到 IDT 表,讀取 IDT 表第i項(或叫第i個門)。

分兩步進行有效性檢查:首先是“段”級檢查,將 CPU 的當前特權級 CPL(存放在 CS 寄存器的最低兩位)與 IDT 中第i項段選擇符中的 DPL 相比較,如果 DPL3)大於 CPL0), 就產生一個“通用保護”異常(中斷向量 13),因為中斷處理程序的特權級不能低於引起中 斷的程序的特權級。這種情況發生的可能性不大,因為中斷處理程序一般運行在內核態,其 特權級為 0。然后是“門”級檢查,把 CPL IDT 中第 i個門的 DPL 相比較,如果 CPL 大於 DPL,也就是當前特權級(3)小於這個門的特權級(0),CPU 不能“穿過”這個門,於是 產生一個“通用保護”異常,這是為了避免用戶應用程序訪問特殊的陷阱門或中斷門。但是 請注意,這種“門”級檢查是針對一般的用戶程序,而不包括外部 I/O 產生的中斷或因 CPU 內部異常而產生的異常,也就是說,如果產生了中斷或異常,就免去了“門”級檢查

(這里的免去沒大明白,是不進行有效性檢查了?直接跳過這一步?)

檢查是否發生了特權級的變化。當中斷發生在用戶態(特權級為 3),而中斷處理程 序運行在內核態(特權級為0),特權級發生了變化,所以會引起堆棧的更換。也就是說,從 用戶堆棧切換到內核堆棧。而當中斷發生在內核態時,即 CPU 在內核中運行時,則不會更換堆棧

 

 

找到對應的門

異常處理沒說怎么在idt_table中找到對應的門的,中斷倒是因為有中斷號,可以根據這個中斷號去idt_table中找到門,反正cpu硬件干的

堆棧變化

如果堆棧變化則將當前的(SS,ESP)壓入棧中,此時的棧為內核棧,因為一旦出現中斷或異常,堆棧就切換到了內核堆棧,上面這些操作是由硬件完成的,至於具體怎么操作的也沒大明白,壓內核棧不是得先更新成內核的SSESP嗎?ESP更新了,那壓的ESP不就是內核的?看見書中有提到此時的內核堆棧是空的,也許壓的固定位置吧。看到后面搞懂了再更新。

更新:有看到說從TSS中取到的內核堆棧,難道是用movl實現的?

 10/29/2015更新:看到后面有些理解了,因為每個進程有task_struct記錄其所有信息,也就是常說的pcb,而這個task_struct與該進程的內核堆棧共用8KB的存儲空間,所以中斷或異常發生的時候完全可以從tss_struct取出內核esp指針,再得到內核堆棧,因為內核堆棧開始與頁首部,而且以8KB為單位,所以esp&~8191UL就可以取到內核堆棧起始地址了,壓棧操作的目的地就是這兒,壓完了再進行esp切換。tss_struct是任務狀態段,是intel設計的cpu任務切換硬件支持,linux為了保證靈活性和對出錯恢復的可操作性以及性能考慮,未完全使用這種切換方式。

 

處理程序格式對於異常和中斷是不同的

 

異常處理

handler_name:

處理程序名稱如:debug,nmi,int3,overflow,bounds等異常名,與異常類型相對應

pushl $0 /* only for some exceptions */

沒有錯誤碼的需要壓一個0,使內核堆棧保持一致性

pushl $do_handler_name

將真正的處理函數地址壓棧

jmp error_code

跳至公共異常處理

此時堆棧狀態

 

error_code:

公共異常處理

pushl %ds

ds入棧

pushl %eax

eax入棧

xorl %eax,%eax

eax清零

pushl %ebp

ebp入棧

pushl %edi

edi入棧

pushl %esi

esi入棧

pushl %edx

edx入棧

decl %eax

# eax = -1

pushl %ecx

ecx入棧

pushl %ebx

ebx入棧

cld

eflag中的DF標志,使EIP朝向增長方向

movl %es,%ecx

es移入ecx

movl ORIG_EAX(%esp), %esi

# get the error code,移入esi

movl ES(%esp), %edi

# get the function address,上面壓棧的do_handler_name函數的地址,移入edi

movl %eax, ORIG_EAX(%esp)

-1移入esp+ORIG_EAX的位置,對應原來的錯誤碼的位置

movl %ecx, ES(%esp)

ecx中的值(es)存入esp+ES的位置,即是ES本該存的地方

movl %esp,%edx

將當前的堆棧地址存入edx

pushl %esi

# push the error codeesi中錯誤碼入棧

pushl %edx

# push the pt_regs pointer,將現場信息的起始地址壓棧,類似於pt_reg的作用,使異常處理程序可以按照統一規則去訪問出錯現場的數據

movl $(__KERNEL_DS),%edx

讀取內核數據段

movl %edx,%ds

加載內核數據段

movl %edx,%es

加載內核ES

GET_CURRENT(%ebx)

將當前進程的task_struct存入ebx,中斷返回進程調度和信號處理需要task_struct中的信息,而且只能在內核態操作,因為task_struct存在內核底部

call *%edi

call執行真正的異常處理程序

addl $8,%esp

真正的異常處理程序返回后丟棄錯誤碼和異常處理程序地址

jmp ret_from_exception

跳轉異常返回,還需對進程調度,信號處理,vm模式和是否返回用戶態進行處理,恢復現場

 

 

 

中斷處理

預處理后的結果

IRQn_interrupt:

中斷號為n的中斷處理程序

pushl $n-256

將中斷號入棧,與異常處理的硬件自動壓棧錯誤碼或者手動壓0相對應

jmp common_interrupt

跳至公共中斷處理

預處理后的結果(因為加了asmlinkage標識,所以do_IRQ會在棧中尋找參數,即pt_regs就是ESP

common_interrupt:

公共異常處理

SAVE_ALL

保存現場到棧中,作為pt_regs參數,與公共異常處理前半截壓棧操作相對應,異常處理程序還需將錯誤碼和真正的異常處理程序地址取出來,將es存入正確位置,然后才調用真正的異常處理程序進行處理,同樣的采用棧傳參數的方式,傳入的是*pt_regs和錯誤碼兩個參數,只是第一個參數是ESP地址,而中斷的第一個參數取到的就是ESP

call do_IRQ

call調用中斷處理程序

jmp ret_from_intr

跳至從中斷返回


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM