作者:李志敏,華清遠(yuǎn)見嵌入式學(xué)院上海中心講師。
一:引言
在Intel的文檔中,把中斷分為兩種。一種是異常,也叫同步同斷。一種稱之為中斷,也叫異常中斷。同步中斷指的是由CPU控制單元產(chǎn)生,之所以稱之為同步,是因?yàn)橹挥幸粭l指令執(zhí)行完畢后才會(huì)發(fā)出中斷。例如除法運(yùn)算中,除數(shù)為零的時(shí)候,就會(huì)產(chǎn)生一個(gè)異常。異步中斷是由外部設(shè)備按照CPU的時(shí)鐘隨機(jī)產(chǎn)生的。例如,網(wǎng)卡檢測到一個(gè)數(shù)據(jù)到來就會(huì)產(chǎn)生一個(gè)中斷。
二:x86的中斷處理過程
由于中斷是開著的,所以當(dāng)執(zhí)行完一條指令后,cs和eip這對寄存器中已經(jīng)包含了下一條將要執(zhí)行的指令的邏輯地址。在處理那條指令之前,控制單元會(huì)檢查在運(yùn)行前一條指令時(shí)是否發(fā)生了一個(gè)中斷或異常。如果發(fā)生了一個(gè)中斷和異常,那么控制單元執(zhí)行下列操作:
1. 確定與中斷或異常關(guān)聯(lián)的向量i(0≤ i ≤255)
2. 讀由IDTr寄存器指向的IDT表中的第i項(xiàng)。
3. 從gdtr寄存器獲得GDT的基地址,并在GDT中查找,以讀取IDT表項(xiàng)中的選擇符標(biāo)識的段描述符。這個(gè)描述符指定中斷或異常處理程序所在的段的基地址。
4. 確信中斷是由授權(quán)的(中斷)發(fā)生源發(fā)出的。首先將當(dāng)前特權(quán)級CPL(存放在cs寄存器的低兩位)與段描述符(存放在GDT中)的描述符特權(quán)級DPL比較。如果CPL小于DPL,就產(chǎn)生一個(gè)“通常保護(hù)”異常,因?yàn)橹袛嗵幚沓绦虻奶貦?quán)級不能低于引起中斷的程序的特權(quán)。對于編程異常,則做進(jìn)一步的安全檢查:比較CPL與處于IDT中的門描述符的DPL,如果DPL小于CPL,就產(chǎn)生一個(gè)“通常保護(hù)”異常,這最后一個(gè)檢查可以避免用戶應(yīng)用程序訪問特殊的陷阱門和中斷門。
5. 檢查是否發(fā)生了特權(quán)級的變化,也就是說,CPL是否不同于所選擇的段描述符的DPL。如果是,控制單元必須開始使用與新的特權(quán)級相關(guān)的棧,通過執(zhí)行以下步驟來保證這一點(diǎn):
A. 讀tr寄存器,以訪問運(yùn)行進(jìn)程的TSS段。
B. 用與新特權(quán)級相關(guān)的棧段和棧指針的正確值裝載ss和esp寄存器。這些值可以在TSS中找到。
C. 在新的棧中保存ss和esp以前的值,這些值定義了與舊特權(quán)級相關(guān)的棧的邏輯地址。
6. 如果故障已發(fā)生,用引起異常的指令地址裝載cs和eip寄存器,從而使得這條指令能再次被執(zhí)行。
7. 在棧中保存eflag、cs和eip的內(nèi)容。
8. 如果異常產(chǎn)生了一個(gè)硬件出錯(cuò)碼,則將它保存在棧中。
9. 裝載cs和eip寄存器,其值分別是IDT表中第i項(xiàng)門描述符的段選擇符和偏移量字段。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址??刂茊卧鶊?zhí)行的最后一步就是跳轉(zhuǎn)到中斷或異常處理程序。換句話說,處理完中斷信號后,控制單元所執(zhí)行的指令就是被選中處理程序的第一條指令。
上面的處理過程的描述摘自<<深入理解linux內(nèi)核>>,其中有幾點(diǎn)值得注意的地方:
1:通過門后,只能提高運(yùn)行級別。就像上面所述的 “當(dāng)前特權(quán)級CPL(存放在cs寄存器的低兩位)與段描述符(存放在GDT中)的描述符特權(quán)級DPL比較。如果CPL小于DPL,就產(chǎn)生一個(gè)“通常保護(hù)”異常”。在中斷處理中,通常把IDT中的相應(yīng)段選擇符設(shè)為__KERNEL_CS。即最高的運(yùn)行級別
2:上面C所述:“在新的棧中保存ss和esp以前的值,這些值定義了與舊特權(quán)級相關(guān)的棧的邏輯地址”,那ss,esp以前的值是如何找到的呢?應(yīng)該是從TSS中。在中斷發(fā)生的時(shí)候,如果檢測到運(yùn)行級別發(fā)生了改了,將寄存器SS,ESP中的值保存進(jìn)TSS的相應(yīng)級別位置。再加載新的SS,ESP的值,然后從TSS中取出舊的SS,ESP值,再壓棧。
3:堆棧的改變,如下圖所示:
從上圖中可以看到,硬件自動(dòng)保存的硬件環(huán)境是非常少,要在中斷后恢復(fù)到以前的環(huán)境,還需要保存更多的寄存器值,這是由操作系統(tǒng)完成的。這在內(nèi)核的代碼中可以看到中斷和異常被處理完畢后,相應(yīng)的處理程序必須產(chǎn)生一條iret指令,把控制權(quán)轉(zhuǎn)交給被中斷的進(jìn)程,這將迫使控制單元:
1. 用保存在棧中的值裝載cs、eip和eflag寄存器。如果一個(gè)硬件出錯(cuò)碼曾被壓入棧中,并且在eip內(nèi)容的上面,那么,執(zhí)行iret指令前必須先彈出這個(gè)硬件出錯(cuò)碼。
2. 檢查處理程序的CPL是否等于cs中的低兩位的值。如果是,iret終止返回;否則,轉(zhuǎn)入下一步。
3. 從棧中轉(zhuǎn)載ss和esp寄存器,因此,返回到與舊特權(quán)級相關(guān)的棧。
4. 檢查ds、es、fs及gs段寄存器的內(nèi)容,如果其中一個(gè)寄存器包含的選擇符是一個(gè)段描述符,并且其DPL值小于CPL,那么,清相關(guān)的段寄存器??刂茊卧@么做是為了禁止用戶態(tài)的程序利用內(nèi)核以前所用的段寄存器。如果不清除這些寄存器的話,惡意的用戶程序就會(huì)利用他們來訪問內(nèi)核地址空間。
注意到4:舉例說明一下。如果通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)。然后將DS,ES的值賦為__KERNEL_DS(在2。4的內(nèi)核里),處理完后(調(diào)用iret后),恢復(fù)CS,EIP的值,此時(shí)CS的CPL是3。因?yàn)镈S,ES被設(shè)為了__KERNEL_DS,所以其DPL是0,所以要將DS,ES中的值清除。在2。6內(nèi)核中,發(fā)生中斷或異常后,將DS,ES的值設(shè)為了__USER_DS,避免了上述的清除過程,提高了效率。
“本文由華清遠(yuǎn)見http://www.embedu.org/index.htm提供”
華清遠(yuǎn)見