由於中斷這塊的知識和代碼都占較大篇幅,因此分成兩章來講,本章不包含任何中斷的代碼,只講理論部分,以及中斷的大概流程。代碼實現部分由下一章來講解
一、到目前為止的程序流程圖
為了讓大家清楚目前的程序進度,畫了到目前為止的程序流程圖,如下。
二、什么是中斷
這里我們先從形象的角度來描述,中斷就是讓操作系統停止手中正在進行的工作,先把中斷信號對應的處理程序執行完畢,再回到之前的程序中繼續進行,這樣一個機制。
一個很形象的說法是,我們的操作系統就是 中斷驅動 的,可以把操作系統簡單理解為一個 死循環,無時無刻不在等待中斷的來臨,被動 地執行相應的任務。
while(true){
操作系統代碼
}
三、中斷的分類
外部中斷
外部中斷通過兩個引腳連接到 CPU 上,一個是可屏蔽中斷 INTR,一個是不可屏蔽中斷 NMI
- INTR:硬盤、打印機、網卡等設備發出的中斷信號,可通過 eflags 寄存器的 IF 位將所有這些外部設備的中斷屏蔽
- NMI:電源掉電、內存讀寫錯誤、總線奇偶校驗錯誤等災難性的錯誤,不可屏蔽,CPU 必須立刻處理
對於可屏蔽中斷,Linux 的處理方式是分成 上半部 和 下半部。上半部執行時關閉中斷,立刻執行完畢;下半部執行時打開中斷,此時如果有其他中斷進來,則讓給其他中斷(也是上半部執行完畢)。
內部中斷
內部中斷可分為 軟中斷 和 異常,二者均是不可屏蔽的(即不受 eflags 的 IF 位影響)
-
軟中斷:就是軟件發起的中斷,最常見的也是我們之后進行系統調用的,就是 int 8位立即數,可表示 256 中中斷。還有一些不常用的,甚至可以叫做異常,下面簡單列出
- int3:中斷向量號3,調試斷點指令
- into:中斷向量號4,中斷溢出指令
- bound:中斷向量號5,檢查數組索引越界指令
- ud2:中斷向量號6,未定義指令,常用於軟件測試中主動發起這個中斷
-
異常:指令執行期間 CPU 內部產生的錯誤引起,如分母為 0 將發起 6 號中斷(異常),未定義的指令發起 6 號中斷
- Fault(故障):可恢復的錯誤。發生此中斷時,CPU 將機器狀態恢復到異常之前的狀態,之后調用中斷處理程序,結束后返回。常見的如 缺頁異常
- Trap(陷阱):有意的異常。通常是調試程序中用 int3 指令主動觸發。
- Abort(終止):不可恢復的異常。直接將此程序從進程表中去掉。
四、中斷號
我們知道一個中斷對應着一個 中斷號(中斷向量號),下面列表說明
中斷號 | 含義 | 來源 | 類型 | 是否有錯誤碼 |
---|---|---|---|---|
0 | divide error | DIV and IDIV instructions | Fault | 無 |
1 | debug | any code or data reference | Fault/Trap | 無 |
2 | NMI Interrupt | NMI | Interrupt | 無 |
3 | Breakpoint | INT3 instruction | Trap | 無 |
4 | Overflow | INTO instruction | Trap | 無 |
5 | bound range exceeded | BOUND instruction | Fault | 無 |
6 | invalid opcode | UD2 instruction or reserved opcode.1 | Fault | 無 |
7 | device not available | floationg-point or WAIT/FWAIT instruction | Fault | 無 |
8 | double fault | any instruction that can generate an exception, an NMI, or an INTR | Fault | Y(0) |
9 | CoProcessor Segment Overrun | Floating-point instruction.2 | Fault | 無 |
10 | invalid TSS | task switch or TSS access | Fault | Y |
11 | segment not present | loading segment registers or accessing system segments | Fault | Y |
12 | stack segment fault | stack operations and SS register loads | Fault | Y |
13 | general protection | any memory reference and other protection checks | Fault | Y |
14 | page fault | any memory reference | Fault | Y |
15 | reserved | |||
16 | floating-point error | floating-point or WAIT/FWAIT instruction | Fault | 無 |
17 | alignment check | any data refrence in memory.3 | Fault | Y(0) |
18 | machine check | error codes and source are model dependent.4 | Fault | 無 |
19 | SIMD floating-point exception | SIMD floating-point instruction5 | Fault | 無 |
20-31 | reserved | |||
32-255 | maskable interrupts | External Interrupt from INTR pin or INT n instruction | Interrupt | 無 |
五、中斷描述符表 IDT
我們先來回顧一下上一講 【自制操作系統07】深入淺出特權級 說的四種門描述符
門 | type值 | 存在位置 | 用法 |
---|---|---|---|
任務門 | 0101 | GDT、LDT、IDT | 與TSS配合實現任務切換,不過大多數操作系統都不這么玩 |
中斷門 | 1110 | IDT | 進入中斷后屏蔽中斷(eflags的IF位置0),linux利用此實現系統調用,int 0x80 |
陷阱門 | 1111 | IDT | 進入中斷后不屏蔽中斷 |
調用門 | 1100 | GDT、LDT | 用戶用call或jmp指令從用戶進程進入0特權級 |
你看,正如上一講所說,中斷門進入后先是屏蔽了中斷,也就是中斷例程的 上半部,程序中可以隨時打開中斷,也就自然到了 下半部,這就是 linux 系統的處理方式。
如何找到中斷描述符表呢?你猜的沒錯,正如找 段描述符表,頁表 等一樣,有個 IDTR 寄存器存儲它的位置(0-15位是表界限,16-47位表示表基址),有個 lidt 指令負責加載 IDTR。經典做法,我們見過太多次了,就不多說啦,不理解的可以從本系列開頭開始看喲。
六、中斷處理過程
上圖就表示了整個中斷處理的過程,不過還有幾處圖中沒有顯示
特權級檢查:CPL <= 門描述符DPL && CPL > 目標代碼段DPL
棧的處理:將 CS、EIP、EFLAGS、SS、ESP 寄存器的值壓入中斷處理程序使用的棧
七、8259A芯片
我們之前說過,外部設備發出中斷信號,進入 CPU 的 INT 引腳上。但如果有多個外部設備近乎同時發送中斷信號,CPU 先處理哪一個呢?未被處理的中斷信號又記錄在哪里呢?這時候就需要有個 中間的代理設備 來負責這個事情。
這個代理設備叫做 可編程中斷控制器 PIC,其中 8259A 芯片是最常見的一種,我們這里把它的內部結構展示出來,由於是硬件相關,就不展開細說了,但由於之后要為其進行編程,所以大家先有個印象。
八、中斷代碼實現
由於到此篇幅過長,且中斷代碼的實現也是需要很大篇幅描述的,包括 可編程中斷控制器的初始化,IDT 的初始化,以及中斷例程代碼的編寫,所以將放在下一章進行講解。
寫在最后:開源項目和課程規划
如果你對自制一個操作系統感興趣,不妨跟隨這個系列課程看下去,甚至加入我們(下方有公眾號和小助手微信),一起來開發。
參考書籍
《操作系統真相還原》這本書真的贊!強烈推薦
項目開源
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你可以通過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都准備一個可執行的代碼。當然文章中的代碼也是全的,采用復制粘貼的方式也是完全可以的。
如果你有興趣加入這個自制操作系統的大軍,也可以在留言區留下您的聯系方式,或者在 gitee 私信我您的聯系方式。
課程規划
本課程打算出系列課程,我寫到哪覺得可以寫成一篇文章了就寫出來分享給大家,最終會完成一個功能全面的操作系統,我覺得這是最好的學習操作系統的方式了。所以中間遇到的各種坎也會寫進去,如果你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即使沒有,交個朋友也是好的哈哈。
目前的系列包括