2中斷的硬件環境

1 IRQ


講到中斷的硬件環境,我們先從著名的IRQ信號談起。每個能夠發出中斷請求的硬件設備控制器都有一條名爲IRQ的輸出線。所有現有的IRQ線都與一個名爲可編程中斷控制器(PIC)的硬件電路的輸入引腳相連,可編程中斷控制器執行下列動作:

1.    監視IRQ線,檢查產生的信號。如果有條或兩條以上的IRQ線上產生信號,就選擇引腳編號較小的IRQ線。
2.    如果一個引發信號出現在IRQ線上:

 

a)    把接收到的引發信號轉換成對應的向量號(參見上一篇博文)。
b)    把這個向量存放在中斷控制器的一個I/O端口(0x20、0x21),從而允許CPU通過數據總線讀此向量。
c)    把引發信號發送到處理器的INTR引腳,即產生一箇中斷。
d)    等待,直到CPU通過把這個中斷信號寫進可編程中斷控制器的一個I/O端口來確認它;當這種情況發生時,清INTR線。

 

3.    返回第1步。

可以有選擇地禁止某條IRQ線。因此,可以對PIC編程從而禁止IRQ,也就是說,可以告訴PIC停止對給定的IRQ線發佈中斷,或者激活它們。這個特點被大多數中斷處理程序使用,因爲這允許中斷處理程序逐次地處理同一類型的IRQ。

這裏要注意一下,有選擇地激活/禁止IRQ線不同於上一篇博文中提到的可屏蔽中斷的全局屏蔽/非屏蔽,一個是對PIC編程,一個是對CPU編程(彙編)。當eflags寄存器的IF標誌被清0時,由PIC發佈的每個可屏蔽中斷都由CPU暫時忽略。cli和sti彙編指令分別清除和設置該標誌。

傳統的PIC是由兩片8259A風格的外部芯片以“級聯”的方式連接在一起的。每個芯片可以處理多達8個不同的IRQ輸入線。因爲從PIC的INT輸出線連接到主PIC的IRQ2引腳,因此,可用IRQ線的個數爲15。

從P3開始,Intel引入了一種I/O高級可編程控制器(APIC),以替代老式8259A可編程中斷控制器。首先,爲了支持老式操作系統,新系統將兩個級聯的老式PIC 8259A保留了下來。其次,新的80x86CPU的控制單元都含有一個本地的APIC。這不奇怪,爲的就是支持多CPU或者多核的體系。每個本地APIC都有32位寄存器,一個內部時鐘,一個本地定時設備以及爲本地APIC中斷保留的兩條額外IRQ線LINT0和LINT1。所有本地APIC都連接到同一個外部I/O APIC,形成一個多APIC的系統。對於APIC,其本質跟傳統8259A其實是一樣的,只是多了一層在多CPU上分發外部中斷請求的I/O APIC,這裏不在話下。

2 中斷描述符表


我們前邊談到,中斷系統的主要目的就是改變程序的執行順序。具體如何改變?也就是一個調用中斷處理程序的過程,這裏我們就要引出中斷描述符表了。中斷描述符表(Interrupt Descriptor Table,IDT)是一個系統表,它與每一箇中斷或異常向量相聯繫,每一個向量在表中有相應的中斷或異常處理程序的入口地址。內核在允許中斷髮生前,必須適當地初始化IDT。

IDT是一個向量表,表中的每一項對應一箇中斷或異常向量,每個向量由8個字節組成。因此,最多需要256 × 8=2048字節來存放IDT,正好是半個頁面。那麼,這個IDT到底放在什麼位置呢?有了idtr CPU寄存器,IDT就可以位於內存的任何地方。idtr寄存器指定IDT的線性基地址及其限制(最大長度)。在允許中斷之前,必須用lidt彙編指令初始化idtr。

IDT包含三種類型的描述符,下圖顯示了每種描述符中的64位(8個字節)的含義。尤其值得注意的是,在40~43位的Type字段的值表示三種描述符的類型。

 

中斷描述符

這些描述符是:

任務門(task gate):當中斷信號發生時,必須取代當前進程的那個進程的TSS選擇符存放在任務門中。

中斷門(interrupt gate):包含段選擇符和中斷或異常處理程序的段內偏移量。當控制權轉移到一個適當的段時,處理器清IF標誌,從而關閉將來會發生的可屏蔽中斷。

陷阱門(Trap gate):與中斷門相似,只是控制權傳遞到一個適當的段時處理器不修改IF標誌。

這裏再聯繫上一篇博文強調一下,Linux利用中斷門處理中斷,利用陷阱門處理異常

3 中斷和異常的硬件處理


好了,有了前面的基本概念後,我們現在開始分析CPU控制單元如何處理中斷和異常。我們假定內核已被初始化,因此,CPU在保護模式下運行。當執行了一條指令後,CS和eip這對寄存器包含下一條將要執行的指令的邏輯地址。在處理那條指令之前,控制單元會檢查在運行前一條指令時是否已經發生了一箇中斷或異常。如果發生了一箇中斷或異常,那麼控制單元執行下列操作:

1.    確定與中斷或異常關聯的向量i (0 ≤ i ≤ 255)。
2.    讀由idtr寄存器指向的 IDT表中的第i項(在下面的分析中,我們假定IDT表項中包含的是一箇中斷門或一個陷阱門)。
3.    從gdtr寄存器獲得GDT的基地址,並在GDT中查找,以讀取IDT表項中的選擇符所標識的段描述符。這個描述符指定中斷或異常處理程序所在段的基地址。
4.    確信中斷是由授權的(中斷)發生源發出的。首先將當前特權級CPL(存放在cs寄存器的低兩位)與段描述符(存放在GDT中)的描述符特權級DPL比較,如果CPL小於DPL,就產生一個“General protection”異常,因爲中斷處理程序的特權不能低於引起中斷的程序的特權。對於編程異常,則做進一步的安全檢查:比較CPL與處於IDT中的門描述符的DPL,如果DPL小於CPL,就產生一個“General protection”異常。這最後一個檢查可以避免用戶應用程序訪問特殊的陷阱門或中斷門。
5.    檢查是否發生了特權級的變化,也就是說,CPL是否不同於所選擇的段描述符的DPL。如果是,控制單元必須開始使用與新的特權級相關的棧。通過執行以下步驟來做到這點:


i.    讀tr寄存器,以訪問運行進程的TSS段。
ii.    用與新特權級相關的棧段和棧指針的正確值裝載ss和esp寄存器。這些值可以在TSS中找到(參見第三章的“任務狀態段”一節)
iii.    在新的棧中保存ss和esp以前的值,這些值定義了與舊特權級相關的棧的邏輯地址。


6.    如果故障已發生,用引起異常的指令地址裝載CS和eip寄存器,從而使得這條指令能再次被執行。
7.    在棧中保存eflags、CS及eip的內容。
8.    如果異常產生了一個硬件出錯碼,則將它保存在棧中。
9.    裝載cs和eip寄存器,其值分別是IDT表中第i項門描述符的段選擇符和偏移量字段。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址。

控制單元所執行的最後一步就是跳轉到中斷或者異常處理程序。換句話說,處理完中斷信號後,控制單元所執行的指令就是被選中處理程序的第一條指令。

中斷或異常被處理完後,相應的處理程序必須產生一條iret指令,把控制權轉交給被中斷的進程,這將迫使控制單元:

1.    用保存在棧中的值裝載CS、eip或eflags寄存器。如果一個硬件出錯碼曾被壓入棧中,並且在eip內容的上面,那麼,執行iret指令前必須先彈出這個硬件出錯碼。
2.    檢查處理程序的CPL是否等於CS中最低兩位的值(這意味着被中斷的進程與處理程序運行在同一特權級)。如果是,iret終止執行;否則,轉入下一步。
3.    從棧中裝載ss和esp寄存器,因此,返回到與舊特權級相關的棧。
4.    檢查ds、es、fs及gs段寄存器的內容,如果其中一個寄存器包含的選擇符是一個段描述符,並且其DPL值小於CPL,那麼,清相應的段寄存器。控制單元這麼做是爲了禁止用戶態的程序(CPL=3)利用內核以前所用的段寄存器(DPL=0)。如果不清這些寄存器,懷有惡意的用戶態程序就可能利用它們來訪問內核地址空間。


發佈了0 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章