中斷解析
一、中斷是什麼
中斷的漢語解釋是半中間發生阻隔、停頓或故障而斷開。那麼,在計算機系統中,我們為什麼需要“阻隔、停頓和斷開”呢?
舉個日常生活中的例子,比如說我正在廚房用瓦斯燒一壺水,這樣就隻能守在廚房裡,苦苦等着水開——如果水溢出來澆滅了瓦斯,有可能就要發生一場災難了。等啊等啊,外邊突然傳來了驚奇的叫聲“怎麼不關水龍頭?”于是我慚愧的發現,剛才接水之後隻顧着抱怨這份無聊的差事,居然忘了這事,于是慌慌張張的沖向水管,三下兩下關了龍頭,聲音又傳到耳邊,“怎麼幹什麼都是這麼馬虎?”。伸伸舌頭,這件小事就這麼過去了,我落寞的眼神又落在了水壺上。
門外忽然又傳來了铿锵有力的歌聲,我最喜歡的古裝劇要開演了,真想奪門而出,然而,聽着水壺發出“咕嘟咕嘟”的聲音,我清楚:除非等到水開,否則沒有我享受人生的時候。
這個場景跟中斷有什麼關系呢?
如果說我專心緻志等待水開是一個過程的話,那麼叫聲、電視裡傳出的音樂不都讓這個過程“半中間發生阻隔、停頓或故障而斷開”了嗎?這不就是活生生的“中斷”嗎?
在這個場景中,我是唯一具有處理能力的主體,不管是燒水、關水龍頭還是看電視,同一個時間點上我隻能幹一件事情。但是,在我專心緻志幹一件事情時,總有許多或緊迫或不緊迫的事情突然出現在面前,都需要去關注,有些還需要我停下手頭的工作馬上去處理。隻有在處理完之後,方能回頭完成先前的任務,“把一壺水徹底燒開!”
中斷機制不僅賦予了我處理意外情況的能力,如果我能充分發揮這個機制的妙用,就可以“同時”完成多個任務了。回到燒水的例子,實際上,無論我在不在廚房,瓦斯竈總是會把水燒開的,我要做的,隻不過是及時關掉瓦斯竈而已,為了這麼一個一秒鐘就能完成的動作,卻讓我死死地守候在廚房裡,在10分鐘的時間裡不停地看壺嘴是不是冒蒸氣,怎麼說都不劃算。我決定安下心來看電視。當然,在有生之年,我都不希望讓廚房成為火海,于是我上了鬧鐘,10分鐘以後它會發出“尖叫”,提醒我爐子上的水燒開了,那時我再去關瓦斯也完全來得及。我用一個中斷信号——鬧鈴——換來了10分鐘的歡樂時光,心裡不禁由衷地感歎:中斷機制真是個好東西。
正是由于中斷機制,我才能有條不紊地“同時”完成多個任務,中斷機制實質上幫助我提高了并發“處理”能力。它也能給計算機系統帶來同樣的好處:如果在鍵盤按下的時候會得到一個中斷信号,CPU就不必死守着等待鍵盤輸入了;如果硬碟讀寫完成後發送一個中斷信号,CPU就可以騰出手來集中精力“服務大衆”了——無論是人類敲打鍵盤的指尖還是來回讀寫媒體的磁頭,跟CPU的處理速度相比,都太慢了。沒有中斷機制,就像我們苦守廚房一樣,計算機談不上有什麼并行處理能力。
跟人相似,CPU也一樣要面對紛繁蕪雜的局面——現實中的意外是無處不在的——有可能是使用者等得不耐煩,猛敲鍵盤;有可能是運算中碰到了0除數;還有可能網卡突然接收到了一個新的資料包。這些都需要CPU具體情況具體分析,要麼馬上處理,要麼暫緩響應,要麼置之不理。無論如何應對,都需要CPU暫停“手頭”的工作,拿出一種對策,隻有在響應之後,方能回頭完成先前的使命,“把一壺水徹底燒開!”
先讓我們感受一下中斷機制對并發處理帶來的幫助。
讓我們用程式來探讨一下燒水問題,如果沒有“中斷”(注意,我們這裡隻是模仿中斷的場景,實際上是用異步事件——消息——處理機制來展示中斷産生的效果。畢竟,在使用者空間沒有辦法與實際中斷産生直接聯系,不過作業系統為使用者空間提供的異步事件機制,可以看作是模仿中斷的産物),設計如下:
void StayInKitchen()
{
bool WaterIsBoiled = false;
while ( WaterIsBoiled != true )
{
bool VaporGavenOff = false;
if (VaporGavenOff )
WaterIsBoiled = true;
else
WaterIsBoiled = false;
}
// 關瓦斯爐
printf(“Close gas oven./n”);
// 一切安定下來,終于可以看電視了,10分鐘的寶貴時間啊,逝者如斯夫…
watching_tv();
return;
}
可以看出,整個流程如同我們前面描述的一樣,所有工作要順序執行,沒有辦法完成并發任務。
如果用“中斷”,在開始燒水的時候設定一個10分鐘的“鬧鈴”,然後讓CPU去看電視(有點難度,具體實作不在我們關心的範圍之内,留給讀者自行解決吧:>)。等鬧鐘響的時候再去廚房關爐子。
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
// 鬧鐘到時會執行此程式
void sig_alarm(int signo)
{
//關瓦斯爐
printf(“Close gas oven./n”);
}
void watching_tv()
{
while(1)
{
// 呵呵,悠哉悠哉
}
}
int main()
{
// 點火後設定定時中斷
printf(“Start to boil water, set Alarm”);
if (signal( SIGALRM, sig_alrm ) == SIG_ERR)
{
perror("signal(SIGALRM) error");
return -1;
}
// 然後就可以欣賞電視節目了
printf(“Watching TV!/n”);
watching_tv();
return 0;
}
這兩段程式都在使用者空間執行。第二段程式跟中斷也沒有太大的關系,實際上它隻用了信号機制而已。但是,通過這兩個程式的對比,我們可以清楚地看到異步事件的處理機制是如何提升并發處理能力的。
Alarm定時器:alarm相當于系統中的一個定時器,如果我們調用alarm(5),那麼5秒鐘後就會“響起一個鬧鈴”(實際上靠信号機制實作的,我們這裡不想深入細節,如果你對此很感興趣,請參考Richard Stevens不朽著作《Unix環境進階程式設計》)。在鬧鈴響起的時候會發生什麼呢?系統會執行一個函數,至于到底是什麼函數,系統允許程式自行決定。程式員編寫一個函數,并調用signal對該函數進行注冊,這樣一旦定時到來,系統就會調用程式員提供的函數(CallBack函數?沒錯,不過在這裡如何實作并不關鍵,我們就不引入新的概念和細節了)。上面的例子裡我們提供的函數是sig_alarm,所做的工作很簡單,列印“關閉瓦斯竈”消息。
上面的兩個例子很簡單,但很能說明問題,首先,它證明采用異步的消息處理機制可以提高系統的并發處理能力。更重要的是,它揭示了這種處理機制的模式。使用者根據需要設計處理程式,并可以将該程式和特定的外部事件綁定起來,在外部事件發生時系統自動調用處理程式,完成相關工作。這種模式給系統帶來了統一的管理方法,也帶來無盡的功能擴充空間。
計算機系統實作中斷機制是非常複雜的一件工作,再怎麼說人都是高度智能化的生物,而計算機作為一個鐵疙瘩,沒有程式的教導就一事無成。而處理一個中斷過程,它受到的限制和需要學習的東西太多了。
首先,計算機能夠接收的外部信号形式非常有限。中斷是由外部的輸入引起的,可以說是一種刺激。在燒水的場景中,這些輸入是叫聲和電視的音樂,我們這裡隻以聲音為例。其實作實世界中能輸入人類CPU——大腦的信号很多,圖像、氣味一樣能被我們接受,人的資訊接口很完善。而計算機則不然,接受外部信号的途徑越多,設計實作就越複雜,代價就越高。是以個人計算機(PC)給所有的外部刺激隻留了一種輸入方式——特定格式的電信号,并對這種信号的格式、接入方法、響應方法、處理步驟都做了規約(具體内容本文後面部分會繼續詳解),這種信号就是中斷或中斷信号,而這一整套機制就是中斷機制。
其次,計算機不懂得如何應對信号。人類的大腦可以自行處理外部輸入,我從來不用去擔心鬧鐘響時會手足無措——走進廚房關瓦斯,這簡直是天經地義的事情,還用大腦想啊,小腿肚子都知道——可惜計算機不行,沒有程式,它就紋絲不動。是以,必須有機制保證外部中斷信号到來後,有正确的程式在正确的時候被執行。
還有,計算機不懂得如何保持工作的持續性。我在看電視的時候如果去廚房關了瓦斯,回來以後能繼續将電視進行到底,不受太大的影響。而計算機則不然,如果放下手頭的工作直接去處理“意外”的中斷,那麼它就再也沒有辦法想起來曾經作過什麼,做到什麼程度了。自然也就沒有什麼“重操舊業”的機會了。這樣的處理方式就不是并發執行,而是東一榔頭,西一棒槌了。
那麼,通用的計算機系統是如何解決這些問題的呢?它是靠硬體和軟體配合來協同實作中斷處理的全過程的。我們将通過Intel X86架構的實作來介紹這一過程。
CPU執行完一條指令後,下一條指令的邏輯位址存放在cs和eip這對寄存器中。在執行新指令前,控制單元會檢查在執行前一條指令的過程中是否有中斷或異常發生。如果有,控制單元就會抛下指令,進入下面的流程:
1. 确定與中斷或異常關聯的向量i (0£i£255)
2. 尋找向量對應的處理程式
3. 儲存目前的“工作現場”,執行中斷或異常的處理程式
4. 處理程式執行完畢後,把控制權交還給控制單元
5. 控制單元恢複現場,傳回繼續執行原程式
整個流程如下圖所示:
圖一:中斷處理過程
讓我們深入這個流程,看看都有什麼問題需要面對。
1、異常是什麼概念?
在處理器執行到由于程式設計失誤而導緻的錯誤指令(例如除數是0)的時候,或者在執行期間出現特殊情況(例如缺頁),需要靠作業系統來處理的時候,處理器就會産生一個異常。對大部分處理器體系結構來說,處理異常和進行中斷的方式基本是相同的,x86架構的CPU也是如此。異常與中斷還是有些差別,異常的産生必須考慮與處理器時鐘的同步。實際上,異常往往被稱為同步中斷。
2、中斷向量是什麼?
中斷向量代表的是中斷源——從某種程度上講,可以看作是中斷或異常的類型。中斷和異常的種類很多,比如說被0除是一種異常,缺頁又是一種異常,網卡會産生中斷,聲霸卡也會産生中斷,CPU如何區分它們呢?中斷向量的概念就是由此引出的,其實它就是一個被送通往CPU資料線的一個整數。CPU給每個IRQ配置設定了一個類型号,通過這個整數CPU來識别不同類型的中斷。這裡可能很多朋友會尋問為什麼還要弄個中斷向量這麼麻煩的東東?為什麼不直接用IRQ0~IRQ15就完了?比如就讓IRQ0為0,IRQ1為1……,這不是要簡單得多麼?其實這裡展現了子產品化設計規則,及節約規則。
首先我們先談談節約規則,所謂節約規則就是所使用的信号線數越少越好,這樣如果每個IRQ都獨立使用一根資料線,如IRQ0用0号線,IRQ1用1号線……這樣,16個IRQ就會用16根線,這顯然是一種浪費。那麼也許馬上就有朋友會說:那麼隻用4根線不就行了嗎?(2^4=16)。
這個問題,展現了子產品設計規則。我們在前面就說過中斷有很多類,可能是外部硬體觸發,也可能是由軟體觸發,然而對于CPU來說中斷就是中斷,隻有一種,CPU不用管它到底是由外部硬體觸發的還是由運作的軟體本身觸發的,因為對于CPU來說,中斷處理的過程都是一樣的:中斷現行程式,轉到中斷服務程式處執行,回到被中斷的程式繼續執行。CPU總共可以處理256種中斷,而并不知道,也不應當讓CPU知道這是硬體來的中斷還是軟體來的中斷,這樣,就可以使CPU的設計獨立于中斷控制器的設計,這樣CPU所需完成的工作就很單純了。CPU對于其它的子產品隻提供了一種接口,這就是256個中斷處理向量,也稱為中斷号。由這些中斷控制器自行去使用這256個中斷号中的一個與CPU進行互動,比如,硬體中斷可以使用前128個号,軟體中斷使用後128個号,也可以軟體中斷使用前128個号,硬體中斷使用後128個号,這與CPU完全無關了,當你需要處理的時候,隻需告訴CPU你用的是哪個中斷号就行,而不需告訴CPU你是來自哪兒的中斷。這樣也友善了以後的擴充,比如現在機器裡又加了一片8259晶片,那麼這個晶片就可以使用空閑的中斷号,看哪一個空閑就使用哪一個,而不是必須要使用第0号,或第1号中斷号了。其實這相當于一種映射機制,把IRQ信号映射到不同的中斷号上,IRQ的排列或說編号是固定的,但通過改變映射機制,就可以讓IRQ映射到不同的中斷号,也可以說調用不同的中斷服務程式。
3、什麼是中斷服務程式?
在響應一個特定中斷的時候,核心會執行一個函數,該函數叫做中斷處理程式(interrupt handler)或中斷服務程式(interrupt service routine(ISR))。産生中斷的每個裝置都有相應的中斷處理程式。例如,由一個函數專門處理來自系統時鐘的中斷,而另外一個函數專門處理由鍵盤産生的中斷。
一般來說,中斷服務程式要負責與硬體進行互動,告訴該裝置中斷已被接收。此外,還需要完成其他相關工作。比如說網絡裝置的中斷服務程式除了要對硬體應答,還要把來自硬體的網絡資料包拷貝到記憶體,對其進行處理後再交給合适的協定棧或應用程式。每個中斷服務程式根據其要完成的任務,複雜程度各不相同。
一般來說,一個裝置的中斷服務程式是它的裝置驅動程式(device driver)的一部分——裝置驅動程式是用于對裝置進行管理的核心代碼。
4、隔離變化
不知道您有沒有意識到,中斷處理前面這部分的設計是何等的簡單優美。人是高度智能化的,能夠對遇到的各種意外情況做有針對性的處理,計算機相比就差距甚遠了,它隻能根據預定的程式進行操作。對于計算機來說,硬體支援的,隻能是中斷這種電信号傳播的方式和CPU對這種信号的接收方法,而具體如何處理這個中斷,必須得靠作業系統實作。作業系統支援所有事先能夠預料到的中斷信号,理論上都不存在太大的挑戰,但在作業系統安裝到計算機裝置上以後,肯定會時常有新的外圍裝置被加入系統,這可能會帶來安裝系統時根本無法預料的“意外”中斷。如何支援這種擴充,是整個系統必須面對的。
而硬體和軟體在這裡的協作,給我們帶來了完美的答案。當新的裝置引入新類型的中斷時,CPU和作業系統不用關注如何處理它。CPU隻負責接收中斷信号,并引用中斷服務程式;而作業系統提供預設的中斷服務——一般來說就是不理會這個信号,傳回就可以了——并負責提供接口,讓使用者通過該接口注冊根據裝置具體功能而編制的中斷服務程式。如果使用者注冊了對應于一個中斷的服務程式,那麼CPU就會在該中斷到來時調用使用者注冊的服務程式。這樣,在中斷來臨時系統需要如何操作硬體、如何實作硬體功能這部分工作就完全獨立于CPU架構和作業系統的設計了。
而當你需要加入新裝置的時候,隻需要告訴作業系統該裝置占用的中斷号、按照作業系統要求的接口格式撰寫中斷服務程式,用作業系統提供的函數注冊該服務程式,裝置的中斷就被系統支援了。
中斷和對中斷的處理被解除了耦合。這樣,無論是你在需要加入新的中斷時,還是在你需要改變現有中斷的服務程式時、又或是取消對某個中斷支援的時候,CPU架構和作業系統都無需作改變。
5、儲存目前工作“現場”
在中斷處理完畢後,計算機一般來說還要回頭處理原先手頭正做的工作。這給中斷的概念帶來些額外的“内涵”。注一“回頭”不是指從頭再來重新做,而是要接着剛才的進度繼續做。這就需要在進行中斷信号之前保留工作“現場”。“現場”這個詞比較晦澀,其實就是指一個資訊集,它能反映某個時間點上任務的狀态,并能保證按照這些資訊就能恢複任務到該狀态,繼續執行下去。再直白一點,現場不過就是一組寄存器值。而如何保護現場和恢複場景是中斷機制需要考慮的重點之一。
每個中斷處理都要經曆這個儲存和恢複過程,我們可以抽象出其中的步驟:
1. 儲存現場
2. 執行具體的中斷服務程式
3. 從中斷服務傳回
4. 恢複現場
上面說過了,“現場”看似在不斷變化,沒有哪個瞬間相同。但實際上組成現場的要素卻不會有任何改變。也就是說,隻要我們儲存了相關的寄存器狀态,現場就能儲存下來。而恢複“現場”就是重新載入這些寄存器。換句話說,對于任何一個中斷,保護現場和恢複現場所做的都是完全相同的操作。
既然操作相同,實作操作的過程和代碼就相同。減少代碼的備援是子產品化設計的基本準則,實在沒有道理讓所有的中斷服務程式都重複實作這樣的功能,應該将它作為一種基本的結構由底層的作業系統或硬體完成。而對中斷的處理過程需要迅速完成,是以,Intel CPU的控制器就承擔了這個任務,非但如此,上面的所有步驟次序都被固化下來,由控制器驅動完成。儲存現場和恢複現場都由硬體自動完成,大大減輕了作業系統和裝置驅動程式的負擔。
6、硬體對中斷支援的細節
下面的部分,本來應該介紹8259、中斷控制器程式設計、中斷描述符表等内容,可是我看到了潇寒寫的“保護模式下的8259A晶片程式設計及中斷處理探究”(見參考資料1),前人之述備矣,讀者直接讀它好了。
從外而内,Linux對中斷的支援
在Linux中,中斷處理程式看起來就是普普通通的C函數。隻不過這些函數必須按照特定的類型聲明,以便核心能夠以标準的方式傳遞處理程式的資訊,在其他方面,它們與一般的函數看起來别無二緻。中斷處理程式與其它核心函數的真正差別在于,中斷處理程式是被核心調用來響應中斷的,而它們運作于我們稱之為中斷上下文的特殊上下文中。關于中斷上下文,我們将在後面讨論。
中斷可能随時發生,是以中斷處理程式也就随時可能執行。是以必須保證中斷處理程式能夠快速執行,這樣才能保證盡可能快地恢複被中斷代碼的執行。是以,盡管對硬體而言,迅速對其中斷進行服務非常重要。但對系統的其它部分而言,讓中斷處理程式在盡可能短的時間内完成執行也同樣重要。
即使最精簡版的中斷服務程式,它也要與硬體進行互動,告訴該裝置中斷已被接收。但通常我們不能像這樣給中斷服務程式随意減負,相反,我們要靠它完成大量的其它工作。作為一個例子,我們可以考慮一下網絡裝置的中斷處理程式面臨的挑戰。該處理程式除了要對硬體應答,還要把來自硬體的網絡資料包拷貝到記憶體,對其進行處理後再交給合适的協定棧或應用程式。顯而易見,這種運動量不會太小。
現在我們來分析一下Linux作業系統為了支援中斷機制,具體都需要做些什麼工作。
首先,作業系統必須保證新的中斷能夠被支援。計算機系統硬體留給外設的是一個統一的中斷信号接口。它固化了中斷信号的接入和傳遞方法,拿PC機來說,中斷機制是靠兩塊8259和CPU協作實作的。外設要做的隻是把中斷信号發送到8259的某個特定引腳上,這樣8259就會為此中斷配置設定一個辨別——也就是通常所說的中斷向量,通過中斷向量,CPU就能夠在以中斷向量為索引的表——中斷向量表——裡找到中斷服務程式,由它決定具體如何進行中斷。(具體細節還請查閱參考資料1,對于為何采用這種機制,該資料有精彩描述)這是硬體規定的機制,軟體隻能無條件服從。
是以,作業系統對新中斷的支援,說簡單點,就是維護中斷向量表。新的外圍裝置加入系統,首先得明确自己的中斷向量号是多少,還得提供自身中斷的服務程式,然後利用Linux的核心調用界面,把〈中斷向量号、中斷服務程式〉這對資訊填寫到中斷向量表中去。這樣CPU在接收到中斷信号時就會自動調用中斷服務程式了。這種注冊操作一般是由裝置驅動程式完成的。
其次,作業系統必須提供給程式員簡單可靠的程式設計界面來支援中斷。中斷的基本流程前面已經講了,它會打斷目前正在進行的工作去執行中斷服務程式,然後再回到先前的任務繼續執行。這中間有大量需要解決問題:如何保護現場、嵌套中斷如何處理等等,作業系統要一一化解。程式員,即使是驅動程式的開發人員,在寫中斷服務程式的時候也很少需要對被打斷的程序心存憐憫。(當然,出于提高系統效率的考慮,編寫驅動程式要比編寫使用者級程式多一些條條框框,誰讓我們頂着系統程式員的光環呢?)
作業系統為我們屏蔽了這些與中斷相關硬體機制打交道的細節,提供了一套精簡的接口,讓我們用極為簡單的方式實作對實際中斷的支援,Linux是怎麼完美的做到這一點的呢?
CPU對中斷處理的流程:
我們首先必須了解CPU在接收到中斷信号時會做什麼。沒辦法,作業系統必須了解硬體的機制,不配合硬體就寸步難行。現在我們假定核心已被初始化,CPU在保護模式下運作。
CPU執行完一條指令後,下一條指令的邏輯位址存放在cs和eip這對寄存器中。在執行新指令前,控制單元會檢查在執行前一條指令的過程中是否有中斷或異常發生。如果有,控制單元就會抛下指令,進入下面的流程:
1.确定與中斷或異常關聯的向量i (0£i£255)。
2.籍由idtr寄存器從IDT表中讀取第i項(在下面的描述中,我們假定該IDT表項中包含的是一個中斷門或一個陷阱門)。
3.從gdtr寄存器獲得GDT的基位址,并在GDT表中查找,以讀取IDT表項中的選擇符所辨別的段描述符。這個描述符指定中斷或異常處理程式所在段的基位址。
4.确信中斷是由授權的(中斷)發生源發出的。首先将目前特權級CPL(存放在cs寄存器的低兩位)與段描述符(即DPL,存放在GDT中)的描述符特權級比較,如果CPL小于DPL,就産生一個“通用保護”異常,因為中斷處理程式的特權不能低于引起中斷的程式的特權。對于程式設計異常,則做進一步的安全檢查:比較CPL與處于IDT中的門描述符的DPL,如果DPL小于CPL,就産生一個“通用保護”異常。這最後一個檢查可以避免使用者應用程式通路特殊的陷阱門或中斷門。
5.檢查是否發生了特權級的變化,也就是說, CPL是否不同于所選擇的段描述符的DPL。如果是,控制單元必須開始使用與新的特權級相關的棧。通過執行以下步驟來做到這點:
a.讀tr寄存器,以通路運作程序的TSS段。
b.用與新特權級相關的棧段和棧指針的正确值裝載ss和esp寄存器。這些值可以在TSS中找到(參見第三章的“任務狀态段”一節)。
c.在新的棧中儲存ss和esp以前的值,這些值定義了與舊特權級相關的棧的邏輯位址。
6.如果故障已發生,用引起異常的指令位址裝載cs和eip寄存器,進而使得這條指令能再次被執行。
7.在棧中儲存eflag、cs及eip的内容。
8.如果異常産生了一個硬錯誤碼,則将它儲存在棧中。
9.裝載cs和eip寄存器,其值分别是IDT表中第i項門描述符的段選擇符和偏移量域。這些值給出了中斷或者異常處理程式的第一條指令的邏輯位址。
控制單元所執行的最後一步就是跳轉到中斷或者異常處理程式。換句話說,處理完中斷信号後,控制單元所執行的指令就是被選中的處理程式的第一條指令。
中斷或異常被處理完後,相應的處理程式必須産生一條iret指令,把控制權轉交給被中斷的程序,這将迫使控制單元:
1.用儲存在棧中的值裝載cs、eip、或eflag寄存器。如果一個硬錯誤碼曾被壓入棧中,并且在eip内容的上面,那麼,執行iret指令前必須先彈出這個硬錯誤碼。
2.檢查處理程式的CPL是否等于cs中最低兩位的值(這意味着被中斷的程序與處理程式運作在同一特權級)。如果是,iret終止執行;否則,轉入下一步。
3. 從棧中裝載ss和esp寄存器,是以,傳回到與舊特權級相關的棧。
4. 檢查ds、es、fs及gs段寄存器的内容,如果其中一個寄存器包含的選擇符是一個段描述符,并且其DPL值小于CPL,那麼,清相應的段寄存器。控制單元這麼做是為了禁止使用者态的程式(CPL=3)利用核心以前所用的段寄存器(DPL=0)。如果不清這些寄存器,懷有惡意的使用者程式就可能利用它們來通路核心位址空間。
再次,作業系統必須保證中斷資訊能夠高效可靠的傳遞
注一:那麼PowerOff(關機)算不算中斷呢?如果從字面上講,肯定符合漢語對中斷的定義,但是從信号格式、處理方法等方面來看,就很難符合我們的了解了。Intel怎麼說的呢?該中斷沒有采用通用的中斷處理機制。那麼到底是不是中斷呢?我也說不上來:(
注二:更詳細的内容和其它一些注意事項請參考核心源代碼包中Documentations/rtc.txt
注三:之是以這裡使用彙編而不是C來實作這些函數,是因為C編譯器會在函數的實作中推入額外的棧資訊。而CPU在中斷來臨時儲存和恢複現場都按照嚴格的格式進行,一個位元組的變化都不能有。
參考資料
1 “保護模式下的8259A晶片程式設計及中斷處理探究” 潇寒 哈工大純C論壇 http://purec.binghua.com/Article/ShowArticle.asp?ArticleID=91
2 “80x86 IBM PC及相容計算機(卷I和卷II):彙編語言、設計與接口技術” Muhammad Ali Mazidi等著 張波等譯 清華大學出版社
3 “編寫作業系統之鍵盤互動的實作” 潇寒 哈工大純C論壇 http://purec.binghua.com/Article/ShowArticle.asp?ArticleID=104