中斷的歷史原因
在聊中斷機制之前,我想先和大家聊一聊中斷機制出現的前因后果。最一開始計算機操作系統的設計是能夠一次性的執行所有的計算任務的,這被稱為順序執行,也是批處理操作系統(Batch system)。
順序執行的意思是一個任務接著一個任務的依次執行,就像我們編寫代碼的時候,我們肯定是寫完一行代碼才會寫下一行代碼,此時的計算機也是這樣的,執行完一個任務后才會執行下一個。就相當于 main 函數里面只有一個 while(1) ,永不停止。
這樣的操作系統是當時最高效的系統,但是這種系統會存在兩個問題:
下一個任務只能在當前任務執行完成后才得以執行,拿上圖來說就是任務 A 執行完成后才會執行任務 B,任務 C 在任務 A 和任務 B 執行完成后才會得到執行,任務 D 同理。當任務執行遇到問題或者出錯時,就直接修改當前任務的 PC 指針,將其指向下一個任務就完事了。
任務執行的次序是單項的,就是只能以 A -> B -> C -> D 這樣的次序執行,不能以 D -> C -> B -> A 這樣反向的順序執行。
這樣的操作系統無疑是很簡陋的(或者此時不應該稱之為操作系統,實際上就是一個監控系統)。
隨著時代的發展,后來出現了很多計算機,不過此時計算機還沒有改變依次執行順序,當計算機在做 IO 任務的時候,計算任務必須等待;在做計算任務的時候,IO 任務必須等待。這顯然是一個急需解決的問題。
一直等到 IBM 開發的 OS/360 計算機才解決了這個問題,OS/360 可以說算是一個劃時代的標志,因為它有一個很重要的特點是能夠允許多道程序運行,并且能夠實現多道任務之間的切換,這些任務可以是 IO 任務,也可以是計算任務。但是這些任務執行于何處停止,何時進行切換卻沒有一個明確的標準。
后來出現了 MIT 開發的 MULTICS 操作系統,這種操作系統是一種分時系統,它允許每個任務都各自運行一段時間后再進行切換,這樣能夠兼顧所有的任務,使他們都能夠得到運行。雖然解決了分時復用的問題,但是不同任務所需要的時間并不一定是恒定的,所以 MULTICS 注定了只能是個過度。
后來出現了大名鼎鼎的 UNIX,由Dennis Ritchi 丹尼斯里奇和Ken Thompson 肯湯姆森共同開發,UNIX 是一個簡化版的 MULTICS ,核心概念差不多,但是 UNIX 卻更加靈活和成功。奠定了小型化機器流行的基礎。
在 UNIX 開發出來不久,Andrew Tanenbaum 也開發出來了一套操作系統 MINIX,不過這個操作系統是用于教學目的,沒有開源,而 Tanenbaum 就是寫現代操作系統的那個大佬。
又過了幾年,Linus Torvalds 基于 UNIX 操作系統開發了 Linux,一直流傳至今。
我沒有查到中斷到底是何時引入的,但是從 Linux 問世以來就已經有了,而且 Linux 是基于 UNIX 開發的,可以認為 UNIX 就已經引入中斷機制了,而且換個角度來說,UNIX 作為如此著名的操作系統,應該會引入中斷機制的。
當然我知道大多數人對計算機歷史沒有太多興趣,所以我們現在還是切回主線了。
中斷的概念和相關原理
中斷是指計算機在運行過程中,由于某些原因(這個原因可以是系統外部、也可以是系統內部或者程序出現緊急事件)不得不停下來當前正在執行的任務,轉而處理其他任務的過程,在處理完其他事情后,計算機會返回繼續執行當前任務,這個完整的過程就被稱為中斷(Interrupt)。
還有一種處理方式是輪詢,現代計算機一般都包含輸入輸出設備,在輪詢機制中,CPU 會不斷的順序詢問每個設備是否需要提供服務,如果需要提供服務,CPU 就會轉而為設備驅動進行服務;可以看到,這種輪詢的方式性能較差,而且比較耗費 CPU 資源。
輪詢的方式可以看做是一種被動要求 CPU 為其服務的方式,而中斷可以看做是一種主動要求 CPU 為其服務的方式。從我們日常生活和學習過程中就能夠知道,主動要求的方式效率要比被動詢問的方式要高,因為你肯定也經歷過上課老師問同學們會不會的時候,有人主動站起來問問題要比老師問每個學生沒有回復效率要高的多。
在中斷的過程中,設備會向 CPU 發出的請求,而這個請求被稱為中斷請求(IRQ - Interrupt Request),CPU 針對中斷請求做出響應轉而執行相關程序被稱為中斷服務程序(ISR - Interrupt Service Routine)或者叫中斷服務過程。
這里需要認識一個新的概念:中斷控制器(PIC - Programmable Interrupt Controller),中斷控制器負責管理設備發出的這些中斷請求,簡單來說它就是這些中斷請求的管理者。這個玩意會和設備的引腳相連接以便接收設備發出來的中斷信號,當設備激活 IRQ 時,中斷控制器會立刻檢測到并對其做出響應。不過真實的情況是,計算機無時無刻都在發出 IRQ,所以中斷控制器經常會收到很多 IRQ,甚至有可能 CPU 正在執行中斷過程的同時 PIC 還收到了 IRQ,這時中斷控制器需要對這些 IRQ 排出一個響應優先級,來告知 CPU 應該首先執行哪個中斷處理程序。
PIC 更多是適用于單核 CPU ,對于多核 CPU 來說并不適用,適用于多核 CPU 的是 APIC,APIC 我們后面簡單提到一些,不過目前還是以 PIC 為主,因為 Linux 0.11 用的是 PIC。
中斷的具體過程是這樣的:PIC 會向 CPU 的引腳發出一個中斷信號,CPU 知道產生了中斷信號后會立刻停下當前進程,并詢問 PIC 需要執行哪個中斷請求,PIC 通過數據總線告知 CPU 中斷號,CPU 根據中斷號去 IDT(中斷向量表)中取得中斷向量并執行中斷處理程序,處理完成后,CPU 會返回當前的任務繼續執行。
上面聊到的這些中斷都是通過設備產生的中斷,這些中斷的本質是外部設備產生的信號來告知操作系統其狀態的變化,這種中斷被稱為硬中斷;還有一種中斷是軟中斷,軟中斷通常是由軟件中引起中斷的指令產生的,比如 int 指令就會產生軟中斷,設備產生的硬中斷不會等待太長時間,響應速度比較快,而指令產生的軟中斷是一種推后的機制,響應速度不如硬中斷快。
80x86 的中斷系統
這部分主要介紹一下 x86 所使用的中斷控制芯片相關內容,會涉及到一些嵌入式相關的知識。
80x86 組成的微機機系統中采用了 8259A 可編程中斷控制芯片。每個 8259A 芯片可以管理 8 個中斷源。通過多片級聯的方式,8259A 能構成最多管理 64 個中斷向量的系統。在 PC/AT 系列兼容機中,使用了兩片 8259A 芯片,可以管理 15 級中斷向量,如下圖所示:
從圖中可以看到,圖上方是主芯片,圖下方是從芯片,從芯片的 INT 引腳連接到主芯片的 IR2 引腳上,這也就是說,從芯片的中斷信號可以作為主芯片的輸入信號。
8259A 是一塊可編程芯片,可以通過 IN 和 OUT 指令對 8259A 進行編程,一旦完成了初始化編程,芯片就進入了操作狀態,此時芯片可以隨時響應外部設備提出的中斷請求(IRQ0 - IRQ15)。通過中斷判優選擇,芯片將當前最高優先級的中斷請求作為中斷服務對象,并通過 INT 請求通知 CPU 外中斷請求到來,然后根據中斷號執行中斷處理程序。
中斷向量表
上面提到過中斷向量表是 CPU 根據中斷號執行中斷處理程序前需要查詢的"一張表",獲取中斷向量值后就可以對應中斷服務程序的入口值。
80x86 機器支持 256 個中斷,理論上每個中斷都需要安排一個中斷處理程序。在 80x86 實模式下,每個中斷向量由 4 個字節組成,這 4 個字節組成了一個中斷處理程序的段值和段內偏移值,所以整個中斷向量表的大小是 1024 字節。在程序加電啟動時,程序進入實模式,此時 ROM BIOS 會在物理地址 0x0000:0x0000 處完成中斷向量表的初始化。在中斷向量表中,中斷向量號順序排列,每個中斷向量號占用 4 字節,因此每個中斷向量的內存位置就是 [0x0000:N 乘 4,0x0000:N+1 乘 4 - 1) 。
中斷向量表在 32 位保護模式下也叫做中斷描述符表,也是我們常說的 IDT 表。
IDT 表和中斷向量表都是描述中斷服務程序地址的表項,基本上中斷向量表和 IDT 表換湯不換藥,只不過 IDT 表除了有中斷服務程序的地址外,還包含有特權級和描述符類別等信息。
對于 Linux 內核來說,中斷信號分為兩類:硬件中斷和軟件中斷,每個中斷是由 0 - 255 之間的一個數字來標識。對于中斷 int0 - int31 來說,每個中斷的功能都由 intel 制定或保留用,這些屬于軟件中斷,但是 intel 公司稱之為異常。叫做異常也是可以理解的,因為這些中斷都是在探測到異常情況下發出的。中斷 int32 - int255 可以由用戶自己設定。常見的硬件和軟件中斷描述見下表。
在 Linux 系統中,將 int32 - int47 對應于 8259A 中斷控制芯片發出的硬件中斷請求信號 IRQ0 - IRQ15,并把程序編程發出的系統調用中斷設置為 int128 ,也就是 0x80。
下面是 8259A 芯片中斷請求發出的中斷號列表:
中斷請求號 | 中斷號 | 用途 |
---|---|---|
IRQ0 | 0x20(32) | 8253 發出的 100HZ 時鐘中斷 |
IRQ1 | 0x21(33) | 鍵盤中斷 |
IRQ2 | 0x22(34) | 接連從芯片 |
IRQ3 | 0x23(35) | 串行口 2 |
IRQ4 | 0x24(36) | 串行口 1 |
IRQ5 | 0x25(37) | 并行口 2 |
IRQ6 | 0x26(38) | 軟盤驅動器 |
IRQ7 | 0x27(39) | 并行口 1 |
IRQ8 | 0x28(40) | 實時鐘中斷 |
IRQ9 | 0x29(41) | 保留 |
IRQ10 | 0x2a(42) | 保留 |
IRQ11 | 0x2b(43) | 保留(網絡接口) |
IRQ12 | 0x2c(44) | PS/2 鼠標口中斷 |
IRQ13 | 0x2d(45) | 數學協處理器中斷 |
IRQ14 | 0x2e(46) | 硬盤中斷 |
IRQ15 | 0x2f(47) | 保留 |
在系統剛剛初始化后,內核在 head.s 程序中會對所有 256 個中斷向量進行默認設置。默認設置就是給這些中斷向量隨便設置一個初值,設置這個值的目的是為了防止出現一般保護性錯誤。
一般保護性錯誤:是指在英特爾 x86 架構和 AMDx86-64 架構和其它架構中的一種中斷情況,指正在運行的程序(內核或用戶態程序)違反處理器架構中保護措施的情況。
最常見的情況就是
Linux 中的這些中斷不會所有的都用到,有些中斷是保留中,另外對于系統中所使用的一些中斷,內核會在其初始化過程中重新設置這些中斷描述符,讓他們指向實際的處理過程。
另外,在設置中斷描述符表 IDT 表時 Linux 內核使用了中斷門和陷阱門兩種門描述符。它們之間的區別在于對標志寄存器 EFLAGS 中的中斷允許標志 IF 的影響。由中斷門描述符執行的中斷會復位 IF 標志,因此可以避免其他中斷干擾當前中斷的處理。隨后中斷結束后指令 iret 會恢復 IF 標志的原值;而通過陷阱門執行的中斷不會響應 IF 標志。
這里需要說一下兩個指令 cli 和 sti,為了避免競爭條件對臨界代碼的干擾,在 Linux 0.11 內核中很多地方都使用了 cli 和 sti 指令。cli 指令用于復位 CPU 標志寄存器 EFLAGS 中的中斷標志,使得系統在執行 cli 指令后不會響應外部中斷。sti 指令用于設置標志寄存器中的中斷標志,能夠讓 CPU 識別并響應外部設備發出的中斷。這倆相當于是個可逆的關系。
當一段代碼進入可能引起競爭條件的臨界代碼區時,內核中就會使用 cli 指令來關閉對外部中斷的響應,而在執行完競爭代碼區時內核就會執行 sti 指令以重新允許 CPU 響應外部中斷。如果不設置 cli 和 sti 的話,就可能引起對臨界代碼的多重寫操作,導致數據不一致,產生崩潰現象。
責任編輯:彭菁
-
計算機
+關注
關注
19文章
7421瀏覽量
87718 -
操作系統
+關注
關注
37文章
6738瀏覽量
123190 -
代碼
+關注
關注
30文章
4748瀏覽量
68356 -
機制
+關注
關注
0文章
24瀏覽量
9774
原文標題:聊聊 Linux 中斷機制
文章出處:【微信號:cxuangoodjob,微信公眾號:程序員cxuan】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論