中斷的漢語解釋是半中間發生阻隔、停頓或故障而斷開。那么,在計算機系統中,我們為什么需要“阻隔、停頓和斷開”呢?
舉個日常生活中的例子,比如說我正在廚房用煤氣燒一壺水,這樣就只能守在廚房里,苦苦等著水開——如果水溢出來澆滅了煤氣,有可能就要發生一場災難了。等啊等啊,外邊突然傳來了驚奇的叫聲“怎么不關水龍頭?”于是我慚愧的發現,剛才接水之后只顧著抱怨這份無聊的差事,居然忘了這事,于是慌慌張張的沖向水管,三下兩下關了龍頭,聲音又傳到耳邊,“怎么干什么都是這么馬虎?”。伸伸舌頭,這件小事就這么過去了,我落寞的眼神又落在了水壺上。
門外忽然又傳來了鏗鏘有力的歌聲,我最喜歡的古裝劇要開演了,真想奪門而出,然而,聽著水壺發出“咕嘟咕嘟”的聲音,我清楚:除非等到水開,否則沒有我享受人生的時候。
這個場景跟中斷有什么關系呢?
如果說我專心致志等待水開是一個過程的話,那么叫聲、電視里傳出的音樂不都讓這個過程“半中間發生阻隔、停頓或故障而斷開”了嗎?這不就是活生生的“中斷”嗎?
在這個場景中,我是唯一具有處理能力的主體,不管是燒水、關水龍頭還是看電視,同一個時間點上我只能干一件事情。但是,在我專心致志干一件事情時,總有許多或緊迫或不緊迫的事情突然出現在面前,都需要去關注,有些還需要我停下手頭的工作馬上去處理。只有在處理完之后,方能回頭完成先前的任務,“把一壺水徹底燒開!”
中斷機制不僅賦予了我處理意外情況的能力,如果我能充分發揮這個機制的妙用,就可以“同時”完成多個任務了。回到燒水的例子,實際上,無論我在不在廚房,煤氣灶總是會把水燒開的,我要做的,只不過是及時關掉煤氣灶而已,為了這么一個一秒鐘就能完成的動作,卻讓我死死地守候在廚房里,在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去看電視(有點難度,具體實現不在我們關心的范圍之內,留給讀者自行解決吧:>)。等鬧鐘響的時候再去廚房關爐子。
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
// 鬧鐘到時會執行此程序
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