操作系統學習四--- 中斷


一 什么是中斷?為什么要有中斷?

  CPU獲知了計算機中發生的某些事,CPU暫停正在執行的程序,轉而去處理該事件的程序,當這段程序執行完畢后,CPU繼續執行剛才的程序。稱為中斷。

  中斷的目的:提升並發,提高計算機的效率。

 

二 中斷分類

  外部中斷

  外部中斷是指來自 CPU 外部的中斷,而外部的中斷源必須是某個硬件,所以外部中斷又稱為硬件中斷。比如說網卡收到了來自網絡的數據包,這時候網卡就會主動通知 CPU, CPU 得到通知后便將數據拷貝到內核緩沖區。

  CPU 為大家提供了兩條信號線。外部硬件的中斷是通過兩根信號線通知 CPU 的,這兩根信號線就是INTR (INTeRrupt)和 NMI (Non Maskable Interrupt)

 

 

   只要從 INTR 引腳收到的中斷都是不影響系統運行的,可以隨時處理,甚至 CPU 可以不處理,假裝設 看見,因為它不影響 CPU 運行。而只要從 NMI 引腳收到的中斷,那基本上全是硬傷, CPU 都沒有運行下去的必要了。

  

  可屏蔽中斷

  可屏蔽中斷是通過時TR 引腳進入 CPU 的,外部設備如硬盤、網卡等發出的中斷都是可屏蔽中斷。 可屏蔽的意思是此外部設備發出的中斷, CPU 可以不理會,因為它不會讓系統宕機,所以可以通過 eflags 寄存器的 IF 位將所有這些外部設備的中斷屏蔽。

  對於這類可屏蔽中斷,CPU 可以選擇不用理會,甚至,即使在理會后,也可以像 Linux 那樣,把中斷分為上半部和下半部分開處理。

  操作系統是中斷驅動的,中斷發生后會執行相應的中斷處理程序,我們希望 CPU 中斷響應的時間越 短越好,這樣便能響應更多設備的中斷。但是中斷處理程序還是需要完整執行的,不能光為了提高中斷響 應效率而只執行部分中斷處理程序。 於是,把中斷處理程序分為上半部和下半部兩部分,把中斷處理程序 中需要立即執行的部分(分分鍾不能耽誤的部分)划分到上半部,這部分是要限時執行的,所以通常情況下只完成中斷應答或硬件復位等重要緊迫的工作。而中斷處理程序中那些不緊急的部分則被推遲到下半部 中去完成。由於中斷處理程序的上半部是刻不容緩要執行的,所以上半部是在關中斷不被打擾的情況下執 行的。當上半部執行完成后就把中斷打開了,下半部也屬於中斷處理程序,所以中斷處理程序下半部則是在開中斷的情況下執行的,如果有新的中斷發生,原來這個舊中斷的下半部就會被換下 CPU,先執行新 的中斷處理程序的上半部,等待線程調度機制為舊中斷處理程序擇一 日期(就是指調度算法認為的某個恰 當時機)后,再調度其上 CPU 完成其下半部的執行。

 

  不可屏蔽中斷

  不可屏蔽中斷是通過 NMI 引腳進入 CPU 的,它表示系統中發生了致命的錯誤,它等同於宣布:計算機的運行到此結束。

 

  中斷處理

  CPU 收到中斷后,得知道發生了什么事情才能執行相應的處理辦法。這是通過中斷向量表或中斷描述 符表(中斷向量表是實模式下的中斷處理程序數組,在保護模式下已經被中斷描述符表代替,在后面章節 中會細說〉來實現的,首先為每一種中斷分配一個中斷向量號,中斷向量號就是一個整數,它就是中斷向 量表或中斷描述符表中的索引下標,用來索引中斷項。中斷發起時,相應的中斷向量號通過 NMI 或 INTR 引腳被傳入 CPU,中斷向量號是中斷向量表或中斷描述符表里中斷項的下標, CPU 根據此中斷向量號在 中斷向量表或中斷描述符表中檢索對應的中斷處理程序井去執行。

  內部中斷

  內部中斷分為軟中斷和異常。

  軟中斷,就是由軟件主動發起的中斷,因為它來自於軟件,所以稱之為軟中斷。由於該中斷是軟件運 行中主動發起的,所以它是主觀上的,井不是客觀上的某種內部錯誤。軟中斷又有分類:

  1. "int 8位立即數"這是我們以后常用的指令,我們要通過它進行系統調用, 8位立即數可表示256
    種中斷,這與處理器所支持的中斷數是相吻合的。

  2. “ int3”。這可不是 int 空格 3,它們之間無間隙。 int3 是調試斷點指令,其所觸發的中斷向量號是 3,以后在中斷和異常表中大家會看到。我們用 gdb 或 bochs 調試程序時,實際上就是調試器 fork 了一個子進程,子進程用於運行被調試的程序。調試器中經常要設置斷點,其原理就是父進程修改了子進程的指令,將其用 int3指令替換,從而子進程調用了 int3 指令觸發中斷。用此指令實現調試的原理是 int3 指令的機器碼是 0xcc,斷點本質上是指令的地址,調試器(父進程〉將被調試進程(子進程〉斷點起始地址的第 1 個字節備份好之后,在原地將該指令的第 1 字節修改為 0xcc。這樣指令執行到斷點處時,會去執行機器碼為 0xcc 的 int3 指令,該指令會觸發 3 號中斷,從而會去執行 3 號中斷對應的中斷處理程序,由於中斷處理程序在運行時也要用到寄存 器,為了保存所調試進程的現場,該中斷處理程序必須先將當前的寄存器和相關內存單元壓校保存(提醒,當前寄存器和相關內存都屬於那個被調試的進程),用戶在查看寄存器和變量時就是從樓中獲取的。當恢復執行所調試的進程時,中斷處理程序需要將之前備份的 1字節還原至斷點處,然后恢復各寄存器和內存單元的值, 修改返回地址為斷點地址,用iret指令退出中斷,返回到用戶進程繼續執行。

  3. into。這是中斷溢出指令,它所觸發的中斷向量號是 4。 不過,能否引發 4 號中斷是要看 eflags標 志寄存器中的 OF 位是否為 1,如果是 1 才會引發中斷,否則該指令悄悄地什么都不做
  4. bound。這是檢查數組索引越界指令,它可以觸發 5 號中斷,用於檢查數組的索引下標是否在上下 邊界之內。該指令格式是"bound 16/32 位寄存器, 16/32 位內存"。目的操作數是用寄存器來存儲的內容是待檢測的數組下標值。源操作數是內存,其內容是數組下標的下邊界和上邊界。 當執行bound指令時,若下標處於數組索引的范圍之外,則會觸發 5 號中斷。

  5. ud2。未定義指令,這會觸發第 6 號中斷。該指令表示指令無效, CPU 無法識別。主動使用它發起中斷,常用於軟件測試中,無實際用途 。

  

  異常是另一種內部中斷,是指令執行期間 CPU 內部產生的錯誤引起的。

  上述軟中斷中,除了第一種,其他幾種也可以成為異常。如cpu不認識某個指令了,會觸發ud2異常。

  異常根據嚴重類型分為以下幾種:

  1. Faule,也稱為故障。這種錯誤是可以被修復的一種類型,屬於最輕的一種異常,它給軟件一次“改過自新”的機會。當發生此類異常時 CPU 將機器狀態恢復到異常之前的狀態,之后調用中斷處理程序時, CPU將返回地址依然指向導致 fault 異常的那條指令。通常中斷處理程序中會將此問題修復,待中斷處理程序返回后便能重試。最典型的例子就是操作系統課程中所說的缺頁異常 page fault,話說 Linux 的虛擬內存就是基於 page fault 的,這充分說明這種異常是極易被修復的,甚至是有益的。 
  2. Trap,也稱為陷阱,這一名稱很形象地說明軟件掉進了 CPU 設下的陷阱,導致停了下來。此異常通常用在調試中,比如 int3 指令便引發此類異常,為了讓中斷處理程序返回后能夠繼續向下執行,CPU 將中斷處理程序的返回地址指向導致異常指令的下一個指令地址。 
  3. bort,也稱為終止,從名字上看,這是最嚴重的異常類型, 一旦出現,由於錯誤無法修復,程 序將無法繼續運行,操作系統為了自保,只能將此程序從進程表中去掉。導致此異常的錯誤通常是硬件錯誤,或者某些系統數據結構出錯。

 

 

 

三 中斷描述符表

  中斷描述符表(Interrupt Descriptor Table, IDT)是保護模式下用於存儲中斷處理程序入口的表,當 CPU 接收一個中斷時,需要用中斷向量在此表中檢索對應的描述符,在該描述符中找到中斷處理程序的起始地址,然后執行中斷處理程序。 

  中斷描述符表中除了中斷描述符以外,還有任務門描述符和陷阱門描述符。表中所有描述符都是記錄一段程序的起始地址,相當於通向某段程序的大門,所以IDT中描述符又稱為 門。

 

  中斷門結構如下:

 

 

   中斷門包含了中斷處理程序所在段的段選擇子和段內偏移地址。當通過此方式進入中斷后,標志寄存 器 eflags 中的 E 位自動置 0,也就是在進入中斷后,自動把中斷關閉,避免中斷嵌套。 Linux 就是利用中 斷門實現的系統調用,就是那個著名的 int 0x80。中斷 門只允許存在於 IDT 中。描述符中中斷門的 type 值 為二進制 1110。

  

  在 CPU 內部有個中斷描述符表寄存器( Interrupt Descriptor Table Register, IDTR),該寄存器分為兩部分:第 0 ~ 15 位是表界眼,即 IDT 大小減 1,第 16~47 位是 IDT 的基地址,這和咱們之前介紹的 GDTR 是一樣的原理。中斷描述符表地址肯定要加載到這個寄存器中,只有寄存器 IDTR 指向了 IDT,當 CPU接收到中斷向量號時才能找到中斷向量處理程序,這樣中斷系統才能正常運作。

 

 

   雖然IDT界限最大64KB,可以存儲64KB/8B=8192=8K個描述符,但是處理器僅支持256個中斷,即0~254。

 

  中斷處理過程及保護

  1. 處理器根據中斷向量號定位中斷門描述符。中斷向量號是中斷描述符的索引,當處理器收到一個外部中斷向 量 號后,它用此向量號在中斷描述符 表中查詢對應的中斷描述符,然后再去執行該中斷描述符中的中斷處理程序。由於中斷描述符是 8個字節, 所以處理器用中斷向量號乘以 8 后,再與 IDTR 中的中斷描述符表地址相加,所求的地址之和便是該中斷 向量號對應的中斷描述符 。
  2. 處理器進行特權級檢查。(a)如果是由軟中斷intn, int3和into引發的中斷,這些是用戶進程中主動發起的中斷,由用戶代碼控制,處理器要檢查當前特權級 CPL 和門描述符 DPL,這是檢查進門的特權下限,如果 CPL 權限大於等於 DPL,即數值上 CPL<=門描述符 DPL,特權級“門檻”檢查通過,進入下一步的“門框”檢查。否則, 處理器拋出異常 。(b)這一步檢查特權級的上限(門框〉:處理器要檢查當前特權級 CPL和門描述符中所記錄的選擇子 對應的目標代碼段 DPL,如果 CPL 權限小於目標代碼段 DPL,即數值上 CPL>目標代碼段 DPL,檢查通過 。 否則 CPL 若大於等於目標代碼段 DPL,處理器將引發異常,也就是說,除了用返回指令從高特權級返回,特權轉移只能發生在由低向高。
  3. 執行中斷處理程序。特權級檢查通過后,將門描述符目標代碼段選擇子加載到代碼段寄存器 cs 中,把門描述符中中斷處 理程序的偏移地址加載到 EIP,開始執行中斷處理程序。

  中斷發生后,eflags中的 NT位和 TF位會被置 0。如果中斷對應的門描述符是中斷門,標志寄存器 eflags 中的 IF 位被自動置 0,避免中斷嵌套,即中斷處理過程中又來了個新的中斷,這是為防止在處理某個中斷 的過程中又來了個相同的中斷,即同一種中斷未處理完時又來了一個,這會導致一般保護性(GP)異常。 這表示默認情況下,處理器會在無人打擾的方式下執行中斷門描述符中的中斷處理例程。
  若中斷發生時對應的描述符是任務門或陷阱門的話, CPU 是不會將 IF 位清 0 的 。 因為陷阱門主要用於調試,它允許 CPU 響應更高級別的中斷,所以允許中斷嵌套。而對任務門來說,這是執行一個新任務, 任務都應該在開中斷的情況下進行,否則就獨占 CPU 資源,操作系統也會由多任務退化成單任務了。

 

  進入中斷時要把 NT 位和 TF 位置為 0。 TF 表示 Trap Flag,也就是陷阱標志位,這用在調試環境中,當 TF 為 0 時表示禁止單步執行,也就是說,進入中斷后將 TF 直為 0,表示不允許中斷處理程序單步執行, 這是好理解的。至於為什么要把 NT 位置為 0 呢?這和中斷返回指令 iret有關。

  NT位表示NestTaskFlag,即任務嵌套標志位,也就是用來標記任務嵌套調用的情況。任務嵌套調用是指 CPU將當前正執行的舊任務掛起,轉去執行另外的新任務,待新任務執行完后, CPU再回到舊任務繼續執行。 為什么 CPU 執行完舊任務后還能回到新任務呢?原因是在執行新任務之前, CPU 做了兩件准備工作:

  1. 將舊任務 TSS 選擇子寫到了新任務 TSS 中的“上一個任務 TSS 的指針”字段中 
  2. 將新任務標志寄存器 eflags 中的 NT 位置 1,表示新任務之所以能夠執行,是因為有別的任務調用了它。

  當 CPU 執行 iret 時,它會去檢查 NT 位的值,如果 NT 位為 1,這說明當前任務 是被嵌套執行的,因此會從自己 TSS 中“上一個任務 TSS 的指針”字段中獲取舊任務,然后去執行該任 務。如果 NT 位的值為 0,這表示當前是在中斷處理環境下,於是就執行正常的中斷退出流程。 

 

  中斷發生時的壓棧

  中斷在發生時,處理器收到一個中斷向量號,根據此中斷向量號在中斷描述符表中找到相應的中斷門描述符,門描述符中保存的是中斷處理程序所在代碼段的選擇子及在段內偏移量 ,處理器從該描述符中加載目標代碼段選擇子到代碼段寄存器 cs 及偏移量到指令指針寄存器 EIP。 注意,由於 cs 加載了新的目標代碼段選擇子,處理器不管新的選擇子和任何段寄存器(包括 CS)中當前的選擇子是否相同,也不管這兩個選擇子是否指向當前相同的段,只要段寄存器被加載,段描述符緩沖寄存器就會被刷新,處理器都認為是換了一個段,屬於段間轉移,也就是遠轉移。所以,當前進程被中斷打斷后,為了從中斷返回后能繼續運行該進程,處理器自動把 cs 和 EIP 的當前值保存到中斷處理程序使用的棧中。不同特權級別下處理器使用不同的棧,至於中斷處理程序使用的是哪個棧,要視它當時所在的特權級別,因為中斷是可以在任何特權級別下發生的。除了要保存 cs、 EIP 外,還需要保存標志寄存器 EFLAGS,如果涉及到特權級變化,還要壓入 SS 和 ESP 寄存器。

  下面看看以上寄存器入校情況及順序:

  1. 處理器根據中斷向量號找到對應的中斷描述符后,拿 CPL 和中斷門描述符中選擇子對應的目標代碼段的 DPL 比對,若 CPL 權限比 DPL 低,即數值上 CPL>DPL,這表示要向高特權級轉移,需要切換到高特權級的棧。這也意味着當執行完中斷處理程序后,若要正確返回到當前被中斷的進程,同樣需要將棧恢復為此時的舊棧。處理器先臨時保存當前舊棧SS和 ESP 的值,記作 SS_old和 ESP_old,然后在 TSS 中找到同目標代碼段 DPL 級別相同的棧加載到寄存器 SS 和 ESP 中,記作 SS_new 和 ESP_new,再將之前臨時保存的 SS old 和 ESP old 壓入新棧備份,以備返回時重新加載到棧段寄存器 SS 和棧指針 ESP。 由於 SS_old 是 16 位數據, 32 位模式下的戰操作數是 32 位,所以將 SS_old 用 0 擴展其 高 16 位 ,成為 32 位數據后入棧。
  2. 在新棧中壓入 EFLAGS 寄存器。
  3. 由於要切換到目標代碼段,對於這種段間轉移,要將 cs 和 EIP 保存到當前棧中備份,記作 CS old 和 EIP_old,以便中斷程序執行結束后能恢復到被中斷的進程。同樣 CS_old是 16位數據, 需要用 0填充 其高 16 位,擴展為 32 位數據后入棧。
  4. 某些異常會有錯誤碼,此錯誤碼用於報告異常是在哪個段上發生的,也就是異常發生的位置,所以錯誤碼中包含選擇子等信息。錯誤碼會緊跟在 EIP 之后入棧,記作 ERROR CODE。

  如果在第 1 步中判斷未涉及到特權級轉移,便不會到 TSS 中尋找新棧,而是繼續使用當前舊棧, 因此也談不上恢復舊棧,此時中斷發生時棧中數據不包括 SS_old和 ESP_old。比如中斷發生時當前正在運行的是內核程序,這是 0 特權級到 0 特權級,無特權級變化。

 

 

 


免責聲明!

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



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