http://blog.csdn.net/maochengtao/article/details/30713459
本文寫的很形象,很容易理解费坊。
一贫贝、中斷是什么
中斷的漢語(yǔ)解釋是半中間發(fā)生阻隔热幔、停頓或故障而斷開。那么众雷,在計(jì)算機(jī)系統(tǒng)中灸拍,我們?yōu)槭裁葱枰白韪簟⑼nD和斷開”呢砾省?
舉個(gè)日常生活中的例子鸡岗,比如說(shuō)我正在廚房用煤氣燒一壺水,這樣就只能守在廚房里编兄,苦苦等著水開——如果水溢出來(lái)澆滅了煤氣轩性,有可能就要發(fā)生一場(chǎng)災(zāi)難了。等啊等啊翻诉,外邊突然傳來(lái)了驚奇的叫聲“怎么不關(guān)水龍頭炮姨?”于是我慚愧的發(fā)現(xiàn)捌刮,剛才接水之后只顧著抱怨這份無(wú)聊的差事,居然忘了這事舒岸,于是慌慌張張的沖向水管绅作,三下兩下關(guān)了龍頭,聲音又傳到耳邊蛾派,“怎么干什么都是這么馬虎俄认?”。伸伸舌頭洪乍,這件小事就這么過(guò)去了眯杏,我落寞的眼神又落在了水壺上。
門外忽然又傳來(lái)了鏗鏘有力的歌聲壳澳,我最喜歡的古裝劇要開演了岂贩,真想奪門而出,然而巷波,聽著水壺發(fā)出“咕嘟咕嘟”的聲音萎津,我清楚:除非等到水開,否則沒(méi)有我享受人生的時(shí)候抹镊。
這個(gè)場(chǎng)景跟中斷有什么關(guān)系呢锉屈?
如果說(shuō)我專心致志等待水開是一個(gè)過(guò)程的話,那么叫聲垮耳、電視里傳出的音樂(lè)不都讓這個(gè)過(guò)程“半中間發(fā)生阻隔颈渊、停頓或故障而斷開”了嗎?這不就是活生生的“中斷”嗎终佛?
在這個(gè)場(chǎng)景中俊嗽,我是唯一具有處理能力的主體,不管是燒水查蓉、關(guān)水龍頭還是看電視乌询,同一個(gè)時(shí)間點(diǎn)上我只能干一件事情榜贴。但是豌研,在我專心致志干一件事情時(shí),總有許多或緊迫或不緊迫的事情突然出現(xiàn)在面前唬党,都需要去關(guān)注鹃共,有些還需要我停下手頭的工作馬上去處理。只有在處理完之后驶拱,方能回頭完成先前的任務(wù)霜浴,“把一壺水徹底燒開!”
中斷機(jī)制不僅賦予了我處理意外情況的能力蓝纲,如果我能充分發(fā)揮這個(gè)機(jī)制的妙用阴孟,就可以“同時(shí)”完成多個(gè)任務(wù)了晌纫。回到燒水的例子永丝,實(shí)際上锹漱,無(wú)論我在不在廚房,煤氣灶總是會(huì)把水燒開的慕嚷,我要做的哥牍,只不過(guò)是及時(shí)關(guān)掉煤氣灶而已,為了這么一個(gè)一秒鐘就能完成的動(dòng)作喝检,卻讓我死死地守候在廚房里嗅辣,在10分鐘的時(shí)間里不停地看壺嘴是不是冒蒸氣,怎么說(shuō)都不劃算挠说。我決定安下心來(lái)看電視澡谭。當(dāng)然,在有生之年损俭,我都不希望讓廚房成為火海译暂,于是我上了鬧鐘,10分鐘以后它會(huì)發(fā)出“尖叫”撩炊,提醒我爐子上的水燒開了外永,那時(shí)我再去關(guān)煤氣也完全來(lái)得及。我用一個(gè)中斷信號(hào)——鬧鈴——換來(lái)了10分鐘的歡樂(lè)時(shí)光拧咳,心里不禁由衷地感嘆:中斷機(jī)制真是個(gè)好東西伯顶。
正是由于中斷機(jī)制,我才能有條不紊地“同時(shí)”完成多個(gè)任務(wù)骆膝,中斷機(jī)制實(shí)質(zhì)上幫助我提高了并發(fā)“處理”能力祭衩。它也能給計(jì)算機(jī)系統(tǒng)帶來(lái)同樣的好處:如果在鍵盤按下的時(shí)候會(huì)得到一個(gè)中斷信號(hào),CPU就不必死守著等待鍵盤輸入了阅签;如果硬盤讀寫完成后發(fā)送一個(gè)中斷信號(hào)掐暮,CPU就可以騰出手來(lái)集中精力“服務(wù)大眾”了——無(wú)論是人類敲打鍵盤的指尖還是來(lái)回讀寫介質(zhì)的磁頭,跟CPU的處理速度相比政钟,都太慢了路克。沒(méi)有中斷機(jī)制,就像我們苦守廚房一樣养交,計(jì)算機(jī)談不上有什么并行處理能力精算。
跟人相似,CPU也一樣要面對(duì)紛繁蕪雜的局面——現(xiàn)實(shí)中的意外是無(wú)處不在的——有可能是用戶等得不耐煩碎连,猛敲鍵盤灰羽;有可能是運(yùn)算中碰到了0除數(shù);還有可能網(wǎng)卡突然接收到了一個(gè)新的數(shù)據(jù)包。這些都需要CPU具體情況具體分析廉嚼,要么馬上處理玫镐,要么暫緩響應(yīng),要么置之不理怠噪。無(wú)論如何應(yīng)對(duì)摘悴,都需要CPU暫停“手頭”的工作舰绘,拿出一種對(duì)策蹂喻,只有在響應(yīng)之后,方能回頭完成先前的使命捂寿,“把一壺水徹底燒開口四!”
先讓我們感受一下中斷機(jī)制對(duì)并發(fā)處理帶來(lái)的幫助。
讓我們用程序來(lái)探討一下燒水問(wèn)題秦陋,如果沒(méi)有“中斷”(注意蔓彩,我們這里只是模仿中斷的場(chǎng)景,實(shí)際上是用異步事件——消息——處理機(jī)制來(lái)展示中斷產(chǎn)生的效果驳概。畢竟赤嚼,在用戶空間沒(méi)有辦法與實(shí)際中斷產(chǎn)生直接聯(lián)系,不過(guò)操作系統(tǒng)為用戶空間提供的異步事件機(jī)制顺又,可以看作是模仿中斷的產(chǎn)物)更卒,設(shè)計(jì)如下:
void StayInKitchen()
{
bool WaterIsBoiled = false;
while ( WaterIsBoiled != true )
{
bool VaporGavenOff = false;
if (VaporGavenOff )
WaterIsBoiled = true;
else
WaterIsBoiled = false;
}
// 關(guān)煤氣爐
printf(“Close gas oven.\n”);
// 一切安定下來(lái),終于可以看電視了稚照,10分鐘的寶貴時(shí)間啊蹂空,逝者如斯夫…
watching_tv();
return;
}
可以看出,整個(gè)流程如同我們前面描述的一樣果录,所有工作要順序執(zhí)行上枕,沒(méi)有辦法完成并發(fā)任務(wù)。
如果用“中斷”弱恒,在開始燒水的時(shí)候設(shè)定一個(gè)10分鐘的“鬧鈴”辨萍,然后讓CPU去看電視(有點(diǎn)難度,具體實(shí)現(xiàn)不在我們關(guān)心的范圍之內(nèi)返弹,留給讀者自行解決吧:>)锈玉。等鬧鐘響的時(shí)候再去廚房關(guān)爐子。
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
// 鬧鐘到時(shí)會(huì)執(zhí)行此程序
void sig_alarm(int signo)
{
//關(guān)煤氣爐
printf(“Close gas oven.\n”);
}
void watching_tv()
{
while(1)
{
// 呵呵琉苇,悠哉悠哉
}
}
int main()
{
// 點(diǎn)火后設(shè)置定時(shí)中斷
printf(“Start to boil water, set Alarm”);
if (signal( SIGALRM, sig_alrm ) == SIG_ERR)
{
perror("signal(SIGALRM) error");
return -1;
}
// 然后就可以欣賞電視節(jié)目了
printf(“Watching TV!\n”);
watching_tv();
return 0;
}
這兩段程序都在用戶空間執(zhí)行嘲玫。第二段程序跟中斷也沒(méi)有太大的關(guān)系,實(shí)際上它只用了信號(hào)機(jī)制而已并扇。但是,通過(guò)這兩個(gè)程序的對(duì)比抡诞,我們可以清楚地看到異步事件的處理機(jī)制是如何提升并發(fā)處理能力的穷蛹。
Alarm定時(shí)器:alarm相當(dāng)于系統(tǒng)中的一個(gè)定時(shí)器土陪,如果我們調(diào)用alarm(5),那么5秒鐘后就會(huì)“響起一個(gè)鬧鈴”(實(shí)際上靠信號(hào)機(jī)制實(shí)現(xiàn)的肴熏,我們這里不想深入細(xì)節(jié)鬼雀,如果你對(duì)此很感興趣,請(qǐng)參考Richard Stevens不朽著作《Unix環(huán)境高級(jí)編程》)蛙吏。在鬧鈴響起的時(shí)候會(huì)發(fā)生什么呢?系統(tǒng)會(huì)執(zhí)行一個(gè)函數(shù)源哩,至于到底是什么函數(shù),系統(tǒng)允許程序自行決定鸦做。程序員編寫一個(gè)函數(shù)励烦,并調(diào)用signal對(duì)該函數(shù)進(jìn)行注冊(cè),這樣一旦定時(shí)到來(lái)泼诱,系統(tǒng)就會(huì)調(diào)用程序員提供的函數(shù)(CallBack函數(shù)坛掠?沒(méi)錯(cuò),不過(guò)在這里如何實(shí)現(xiàn)并不關(guān)鍵治筒,我們就不引入新的概念和細(xì)節(jié)了)屉栓。上面的例子里我們提供的函數(shù)是sig_alarm,所做的工作很簡(jiǎn)單耸袜,打印“關(guān)閉煤氣灶”消息友多。
上面的兩個(gè)例子很簡(jiǎn)單,但很能說(shuō)明問(wèn)題堤框,首先夷陋,它證明采用異步的消息處理機(jī)制可以提高系統(tǒng)的并發(fā)處理能力。更重要的是胰锌,它揭示了這種處理機(jī)制的模式骗绕。用戶根據(jù)需要設(shè)計(jì)處理程序,并可以將該程序和特定的外部事件綁定起來(lái)资昧,在外部事件發(fā)生時(shí)系統(tǒng)自動(dòng)調(diào)用處理程序酬土,完成相關(guān)工作。這種模式給系統(tǒng)帶來(lái)了統(tǒng)一的管理方法格带,也帶來(lái)無(wú)盡的功能擴(kuò)展空間撤缴。
計(jì)算機(jī)系統(tǒng)實(shí)現(xiàn)中斷機(jī)制是非常復(fù)雜的一件工作,再怎么說(shuō)人都是高度智能化的生物叽唱,而計(jì)算機(jī)作為一個(gè)鐵疙瘩屈呕,沒(méi)有程序的教導(dǎo)就一事無(wú)成。而處理一個(gè)中斷過(guò)程棺亭,它受到的限制和需要學(xué)習(xí)的東西太多了虎眨。
首先,計(jì)算機(jī)能夠接收的外部信號(hào)形式非常有限。中斷是由外部的輸入引起的嗽桩,可以說(shuō)是一種刺激岳守。在燒水的場(chǎng)景中,這些輸入是叫聲和電視的音樂(lè)碌冶,我們這里只以聲音為例湿痢。其實(shí)現(xiàn)實(shí)世界中能輸入人類CPU——大腦的信號(hào)很多,圖像扑庞、氣味一樣能被我們接受譬重,人的信息接口很完善。而計(jì)算機(jī)則不然罐氨,接受外部信號(hào)的途徑越多臀规,設(shè)計(jì)實(shí)現(xiàn)就越復(fù)雜,代價(jià)就越高岂昭。因此個(gè)人計(jì)算機(jī)(PC)給所有的外部刺激只留了一種輸入方式——特定格式的電信號(hào)以现,并對(duì)這種信號(hào)的格式、接入方法约啊、響應(yīng)方法邑遏、處理步驟都做了規(guī)約(具體內(nèi)容本文后面部分會(huì)繼續(xù)詳解),這種信號(hào)就是中斷或中斷信號(hào)恰矩,而這一整套機(jī)制就是中斷機(jī)制记盒。
其次,計(jì)算機(jī)不懂得如何應(yīng)對(duì)信號(hào)外傅。人類的大腦可以自行處理外部輸入纪吮,我從來(lái)不用去擔(dān)心鬧鐘響時(shí)會(huì)手足無(wú)措——走進(jìn)廚房關(guān)煤氣,這簡(jiǎn)直是天經(jīng)地義的事情萎胰,還用大腦想啊碾盟,小腿肚子都知道——可惜計(jì)算機(jī)不行,沒(méi)有程序技竟,它就紋絲不動(dòng)冰肴。因此,必須有機(jī)制保證外部中斷信號(hào)到來(lái)后榔组,有正確的程序在正確的時(shí)候被執(zhí)行熙尉。
還有,計(jì)算機(jī)不懂得如何保持工作的持續(xù)性搓扯。我在看電視的時(shí)候如果去廚房關(guān)了煤氣检痰,回來(lái)以后能繼續(xù)將電視進(jìn)行到底,不受太大的影響锨推。而計(jì)算機(jī)則不然铅歼,如果放下手頭的工作直接去處理“意外”的中斷公壤,那么它就再也沒(méi)有辦法想起來(lái)曾經(jīng)作過(guò)什么,做到什么程度了谭贪。自然也就沒(méi)有什么“重操舊業(yè)”的機(jī)會(huì)了境钟。這樣的處理方式就不是并發(fā)執(zhí)行锦担,而是東一榔頭俭识,西一棒槌了。
那么洞渔,通用的計(jì)算機(jī)系統(tǒng)是如何解決這些問(wèn)題的呢套媚?它是靠硬件和軟件配合來(lái)協(xié)同實(shí)現(xiàn)中斷處理的全過(guò)程的。我們將通過(guò)Intel X86架構(gòu)的實(shí)現(xiàn)來(lái)介紹這一過(guò)程磁椒。
CPU執(zhí)行完一條指令后堤瘤,下一條指令的邏輯地址存放在cs和eip這對(duì)寄存器中。在執(zhí)行新指令前浆熔,控制單元會(huì)檢查在執(zhí)行前一條指令的過(guò)程中是否有中斷或異常發(fā)生本辐。如果有,控制單元就會(huì)拋下指令医增,進(jìn)入下面的流程:
確定與中斷或異常關(guān)聯(lián)的向量i (0£i£255)
尋找向量對(duì)應(yīng)的處理程序
保存當(dāng)前的“工作現(xiàn)場(chǎng)”慎皱,執(zhí)行中斷或異常的處理程序
處理程序執(zhí)行完畢后,把控制權(quán)交還給控制單元
控制單元恢復(fù)現(xiàn)場(chǎng)叶骨,返回繼續(xù)執(zhí)行原程序
讓我們深入這個(gè)流程茫多,看看都有什么問(wèn)題需要面對(duì)。
1忽刽、異常是什么概念天揖?
在處理器執(zhí)行到由于編程失誤而導(dǎo)致的錯(cuò)誤指令(例如除數(shù)是0)的時(shí)候,或者在執(zhí)行期間出現(xiàn)特殊情況(例如缺頁(yè))跪帝,需要靠操作系統(tǒng)來(lái)處理的時(shí)候今膊,處理器就會(huì)產(chǎn)生一個(gè)異常。對(duì)大部分處理器體系結(jié)構(gòu)來(lái)說(shuō)伞剑,處理異常和處理中斷的方式基本是相同的斑唬,x86架構(gòu)的CPU也是如此。異常與中斷還是有些區(qū)別纸泄,異常的產(chǎn)生必須考慮與處理器時(shí)鐘的同步赖钞。實(shí)際上,異常往往被稱為同步中斷聘裁。
2雪营、中斷向量是什么?
中斷向量代表的是中斷源——從某種程度上講衡便,可以看作是中斷或異常的類型献起。中斷和異常的種類很多洋访,比如說(shuō)被0除是一種異常,缺頁(yè)又是一種異常谴餐,網(wǎng)卡會(huì)產(chǎn)生中斷姻政,聲卡也會(huì)產(chǎn)生中斷,CPU如何區(qū)分它們呢岂嗓?中斷向量的概念就是由此引出的汁展,其實(shí)它就是一個(gè)被送通往CPU數(shù)據(jù)線的一個(gè)整數(shù)。CPU給每個(gè)IRQ分配了一個(gè)類型號(hào)厌殉,通過(guò)這個(gè)整數(shù)CPU來(lái)識(shí)別不同類型的中斷食绿。這里可能很多朋友會(huì)尋問(wèn)為什么還要弄個(gè)中斷向量這么麻煩的東東?為什么不直接用IRQ0~IRQ15就完了公罕?比如就讓IRQ0為0器紧,IRQ1為1……,這不是要簡(jiǎn)單得多么楼眷?其實(shí)這里體現(xiàn)了模塊化設(shè)計(jì)規(guī)則铲汪,及節(jié)約規(guī)則。
首先我們先談?wù)劰?jié)約規(guī)則罐柳,所謂節(jié)約規(guī)則就是所使用的信號(hào)線數(shù)越少越好掌腰,這樣如果每個(gè)IRQ都獨(dú)立使用一根數(shù)據(jù)線,如IRQ0用0號(hào)線硝清,IRQ1用1號(hào)線……這樣辅斟,16個(gè)IRQ就會(huì)用16根線,這顯然是一種浪費(fèi)芦拿。那么也許馬上就有朋友會(huì)說(shuō):那么只用4根線不就行了嗎士飒?(2^4=16)。
這個(gè)問(wèn)題蔗崎,體現(xiàn)了模塊設(shè)計(jì)規(guī)則酵幕。我們?cè)谇懊婢驼f(shuō)過(guò)中斷有很多類,可能是外部硬件觸發(fā)缓苛,也可能是由軟件觸發(fā)芳撒,然而對(duì)于CPU來(lái)說(shuō)中斷就是中斷,只有一種未桥,CPU不用管它到底是由外部硬件觸發(fā)的還是由運(yùn)行的軟件本身觸發(fā)的笔刹,因?yàn)閷?duì)于CPU來(lái)說(shuō),中斷處理的過(guò)程都是一樣的:中斷現(xiàn)行程序冬耿,轉(zhuǎn)到中斷服務(wù)程序處執(zhí)行舌菜,回到被中斷的程序繼續(xù)執(zhí)行。CPU總共可以處理256種中斷亦镶,而并不知道日月,也不應(yīng)當(dāng)讓CPU知道這是硬件來(lái)的中斷還是軟件來(lái)的中斷袱瓮,這樣,就可以使CPU的設(shè)計(jì)獨(dú)立于中斷控制器的設(shè)計(jì)爱咬,這樣CPU所需完成的工作就很單純了尺借。CPU對(duì)于其它的模塊只提供了一種接口,這就是256個(gè)中斷處理向量精拟,也稱為中斷號(hào)燎斩。由這些中斷控制器自行去使用這256個(gè)中斷號(hào)中的一個(gè)與CPU進(jìn)行交互,比如串前,硬件中斷可以使用前128個(gè)號(hào)瘫里,軟件中斷使用后128個(gè)號(hào)实蔽,也可以軟件中斷使用前128個(gè)號(hào)荡碾,硬件中斷使用后128個(gè)號(hào),這與CPU完全無(wú)關(guān)了局装,當(dāng)你需要處理的時(shí)候坛吁,只需告訴CPU你用的是哪個(gè)中斷號(hào)就行,而不需告訴CPU你是來(lái)自哪兒的中斷铐尚。這樣也方便了以后的擴(kuò)充拨脉,比如現(xiàn)在機(jī)器里又加了一片8259芯片,那么這個(gè)芯片就可以使用空閑的中斷號(hào)宣增,看哪一個(gè)空閑就使用哪一個(gè)玫膀,而不是必須要使用第0號(hào),或第1號(hào)中斷號(hào)了爹脾。其實(shí)這相當(dāng)于一種映射機(jī)制帖旨,把IRQ信號(hào)映射到不同的中斷號(hào)上,IRQ的排列或說(shuō)編號(hào)是固定的灵妨,但通過(guò)改變映射機(jī)制解阅,就可以讓IRQ映射到不同的中斷號(hào),也可以說(shuō)調(diào)用不同的中斷服務(wù)程序泌霍。
3货抄、什么是中斷服務(wù)程序?
在響應(yīng)一個(gè)特定中斷的時(shí)候朱转,內(nèi)核會(huì)執(zhí)行一個(gè)函數(shù)蟹地,該函數(shù)叫做中斷處理程序(interrupt handler)或中斷服務(wù)程序(interrupt service routine(ISR))。產(chǎn)生中斷的每個(gè)設(shè)備都有相應(yīng)的中斷處理程序藤为。例如怪与,由一個(gè)函數(shù)專門處理來(lái)自系統(tǒng)時(shí)鐘的中斷,而另外一個(gè)函數(shù)專門處理由鍵盤產(chǎn)生的中斷凉蜂。
一般來(lái)說(shuō)琼梆,中斷服務(wù)程序要負(fù)責(zé)與硬件進(jìn)行交互性誉,告訴該設(shè)備中斷已被接收。此外茎杂,還需要完成其他相關(guān)工作错览。比如說(shuō)網(wǎng)絡(luò)設(shè)備的中斷服務(wù)程序除了要對(duì)硬件應(yīng)答,還要把來(lái)自硬件的網(wǎng)絡(luò)數(shù)據(jù)包拷貝到內(nèi)存煌往,對(duì)其進(jìn)行處理后再交給合適的協(xié)議椙悴福或應(yīng)用程序。每個(gè)中斷服務(wù)程序根據(jù)其要完成的任務(wù)刽脖,復(fù)雜程度各不相同羞海。
一般來(lái)說(shuō),一個(gè)設(shè)備的中斷服務(wù)程序是它的設(shè)備驅(qū)動(dòng)程序(device driver)的一部分——設(shè)備驅(qū)動(dòng)程序是用于對(duì)設(shè)備進(jìn)行管理的內(nèi)核代碼曲管。
4却邓、隔離變化
不知道您有沒(méi)有意識(shí)到,中斷處理前面這部分的設(shè)計(jì)是何等的簡(jiǎn)單優(yōu)美院水。人是高度智能化的腊徙,能夠?qū)τ龅降母鞣N意外情況做有針對(duì)性的處理,計(jì)算機(jī)相比就差距甚遠(yuǎn)了檬某,它只能根據(jù)預(yù)定的程序進(jìn)行操作撬腾。對(duì)于計(jì)算機(jī)來(lái)說(shuō),硬件支持的恢恼,只能是中斷這種電信號(hào)傳播的方式和CPU對(duì)這種信號(hào)的接收方法民傻,而具體如何處理這個(gè)中斷,必須得靠操作系統(tǒng)實(shí)現(xiàn)场斑。操作系統(tǒng)支持所有事先能夠預(yù)料到的中斷信號(hào)漓踢,理論上都不存在太大的挑戰(zhàn),但在操作系統(tǒng)安裝到計(jì)算機(jī)設(shè)備上以后和簸,肯定會(huì)時(shí)常有新的外圍設(shè)備被加入系統(tǒng)彭雾,這可能會(huì)帶來(lái)安裝系統(tǒng)時(shí)根本無(wú)法預(yù)料的“意外”中斷。如何支持這種擴(kuò)展锁保,是整個(gè)系統(tǒng)必須面對(duì)的薯酝。
而硬件和軟件在這里的協(xié)作,給我們帶來(lái)了完美的答案爽柒。當(dāng)新的設(shè)備引入新類型的中斷時(shí)吴菠,CPU和操作系統(tǒng)不用關(guān)注如何處理它。CPU只負(fù)責(zé)接收中斷信號(hào)浩村,并引用中斷服務(wù)程序做葵;而操作系統(tǒng)提供默認(rèn)的中斷服務(wù)——一般來(lái)說(shuō)就是不理會(huì)這個(gè)信號(hào),返回就可以了——并負(fù)責(zé)提供接口心墅,讓用戶通過(guò)該接口注冊(cè)根據(jù)設(shè)備具體功能而編制的中斷服務(wù)程序酿矢。如果用戶注冊(cè)了對(duì)應(yīng)于一個(gè)中斷的服務(wù)程序榨乎,那么CPU就會(huì)在該中斷到來(lái)時(shí)調(diào)用用戶注冊(cè)的服務(wù)程序。這樣瘫筐,在中斷來(lái)臨時(shí)系統(tǒng)需要如何操作硬件蜜暑、如何實(shí)現(xiàn)硬件功能這部分工作就完全獨(dú)立于CPU架構(gòu)和操作系統(tǒng)的設(shè)計(jì)了。
而當(dāng)你需要加入新設(shè)備的時(shí)候策肝,只需要告訴操作系統(tǒng)該設(shè)備占用的中斷號(hào)肛捍、按照操作系統(tǒng)要求的接口格式撰寫中斷服務(wù)程序,用操作系統(tǒng)提供的函數(shù)注冊(cè)該服務(wù)程序之众,設(shè)備的中斷就被系統(tǒng)支持了拙毫。
中斷和對(duì)中斷的處理被解除了耦合。這樣棺禾,無(wú)論是你在需要加入新的中斷時(shí)缀蹄,還是在你需要改變現(xiàn)有中斷的服務(wù)程序時(shí)、又或是取消對(duì)某個(gè)中斷支持的時(shí)候帘睦,CPU架構(gòu)和操作系統(tǒng)都無(wú)需作改變袍患。
5、保存當(dāng)前工作“現(xiàn)場(chǎng)”
在中斷處理完畢后竣付,計(jì)算機(jī)一般來(lái)說(shuō)還要回頭處理原先手頭正做的工作。這給中斷的概念帶來(lái)些額外的“內(nèi)涵”滞欠。注一“回頭”不是指從頭再來(lái)重新做古胆,而是要接著剛才的進(jìn)度繼續(xù)做。這就需要在處理中斷信號(hào)之前保留工作“現(xiàn)場(chǎng)”筛璧∫菀铮“現(xiàn)場(chǎng)”這個(gè)詞比較晦澀,其實(shí)就是指一個(gè)信息集夭谤,它能反映某個(gè)時(shí)間點(diǎn)上任務(wù)的狀態(tài)棺牧,并能保證按照這些信息就能恢復(fù)任務(wù)到該狀態(tài),繼續(xù)執(zhí)行下去朗儒。再直白一點(diǎn)颊乘,現(xiàn)場(chǎng)不過(guò)就是一組寄存器值。而如何保護(hù)現(xiàn)場(chǎng)和恢復(fù)場(chǎng)景是中斷機(jī)制需要考慮的重點(diǎn)之一醉锄。
每個(gè)中斷處理都要經(jīng)歷這個(gè)保存和恢復(fù)過(guò)程乏悄,我們可以抽象出其中的步驟:
保存現(xiàn)場(chǎng)
執(zhí)行具體的中斷服務(wù)程序
從中斷服務(wù)返回
恢復(fù)現(xiàn)場(chǎng)
上面說(shuō)過(guò)了,“現(xiàn)場(chǎng)”看似在不斷變化恳不,沒(méi)有哪個(gè)瞬間相同檩小。但實(shí)際上組成現(xiàn)場(chǎng)的要素卻不會(huì)有任何改變。也就是說(shuō)烟勋,只要我們保存了相關(guān)的寄存器狀態(tài)规求,現(xiàn)場(chǎng)就能保存下來(lái)筐付。而恢復(fù)“現(xiàn)場(chǎng)”就是重新載入這些寄存器。換句話說(shuō)阻肿,對(duì)于任何一個(gè)中斷家妆,保護(hù)現(xiàn)場(chǎng)和恢復(fù)現(xiàn)場(chǎng)所做的都是完全相同的操作。
既然操作相同冕茅,實(shí)現(xiàn)操作的過(guò)程和代碼就相同伤极。減少代碼的冗余是模塊化設(shè)計(jì)的基本準(zhǔn)則,實(shí)在沒(méi)有道理讓所有的中斷服務(wù)程序都重復(fù)實(shí)現(xiàn)這樣的功能姨伤,應(yīng)該將它作為一種基本的結(jié)構(gòu)由底層的操作系統(tǒng)或硬件完成哨坪。而對(duì)中斷的處理過(guò)程需要迅速完成,因此乍楚,Intel CPU的控制器就承擔(dān)了這個(gè)任務(wù)当编,非但如此,上面的所有步驟次序都被固化下來(lái)徒溪,由控制器驅(qū)動(dòng)完成忿偷。保存現(xiàn)場(chǎng)和恢復(fù)現(xiàn)場(chǎng)都由硬件自動(dòng)完成,大大減輕了操作系統(tǒng)和設(shè)備驅(qū)動(dòng)程序的負(fù)擔(dān)臊泌。
6鲤桥、硬件對(duì)中斷支持的細(xì)節(jié)
下面的部分,本來(lái)應(yīng)該介紹8259渠概、中斷控制器編程茶凳、中斷描述符表等內(nèi)容,可是我看到了瀟寒寫的“保護(hù)模式下的8259A芯片編程及中斷處理探究”播揪,前人之述備矣贮喧,讀者直接讀它好了。
二猪狈、從外而內(nèi)箱沦,Linux對(duì)中斷的支持
在Linux中,中斷處理程序看起來(lái)就是普普通通的C函數(shù)雇庙。只不過(guò)這些函數(shù)必須按照特定的類型聲明谓形,以便內(nèi)核能夠以標(biāo)準(zhǔn)的方式傳遞處理程序的信息,在其他方面状共,它們與一般的函數(shù)看起來(lái)別無(wú)二致套耕。中斷處理程序與其它內(nèi)核函數(shù)的真正區(qū)別在于,中斷處理程序是被內(nèi)核調(diào)用來(lái)響應(yīng)中斷的峡继,而它們運(yùn)行于我們稱之為中斷上下文的特殊上下文中冯袍。關(guān)于中斷上下文,我們將在后面討論。
中斷可能隨時(shí)發(fā)生康愤,因此中斷處理程序也就隨時(shí)可能執(zhí)行儡循。所以必須保證中斷處理程序能夠快速執(zhí)行,這樣才能保證盡可能快地恢復(fù)被中斷代碼的執(zhí)行征冷。因此择膝,盡管對(duì)硬件而言,迅速對(duì)其中斷進(jìn)行服務(wù)非常重要检激。但對(duì)系統(tǒng)的其它部分而言肴捉,讓中斷處理程序在盡可能短的時(shí)間內(nèi)完成執(zhí)行也同樣重要。
即使最精簡(jiǎn)版的中斷服務(wù)程序叔收,它也要與硬件進(jìn)行交互齿穗,告訴該設(shè)備中斷已被接收。但通常我們不能像這樣給中斷服務(wù)程序隨意減負(fù)饺律,相反窃页,我們要靠它完成大量的其它工作。作為一個(gè)例子复濒,我們可以考慮一下網(wǎng)絡(luò)設(shè)備的中斷處理程序面臨的挑戰(zhàn)脖卖。該處理程序除了要對(duì)硬件應(yīng)答,還要把來(lái)自硬件的網(wǎng)絡(luò)數(shù)據(jù)包拷貝到內(nèi)存巧颈,對(duì)其進(jìn)行處理后再交給合適的協(xié)議椘枘荆或應(yīng)用程序。顯而易見洛二,這種運(yùn)動(dòng)量不會(huì)太小馋劈。
現(xiàn)在我們來(lái)分析一下Linux操作系統(tǒng)為了支持中斷機(jī)制,具體都需要做些什么工作晾嘶。
首先,操作系統(tǒng)必須保證新的中斷能夠被支持娶吞。計(jì)算機(jī)系統(tǒng)硬件留給外設(shè)的是一個(gè)統(tǒng)一的中斷信號(hào)接口垒迂。它固化了中斷信號(hào)的接入和傳遞方法监右,拿PC機(jī)來(lái)說(shuō)便斥,中斷機(jī)制是靠?jī)蓧K8259和CPU協(xié)作實(shí)現(xiàn)的肋殴。外設(shè)要做的只是把中斷信號(hào)發(fā)送到8259的某個(gè)特定引腳上暮芭,這樣8259就會(huì)為此中斷分配一個(gè)標(biāo)識(shí)——也就是通常所說(shuō)的中斷向量胞谈,通過(guò)中斷向量纽门,CPU就能夠在以中斷向量為索引的表——中斷向量表——里找到中斷服務(wù)程序没炒,由它決定具體如何處理中斷浓领。(具體細(xì)節(jié)還請(qǐng)查閱參考資料1陶耍,對(duì)于為何采用這種機(jī)制奋蔚,該資料有精彩描述)這是硬件規(guī)定的機(jī)制,軟件只能無(wú)條件服從。
因此泊碑,操作系統(tǒng)對(duì)新中斷的支持坤按,說(shuō)簡(jiǎn)單點(diǎn),就是維護(hù)中斷向量表馒过。新的外圍設(shè)備加入系統(tǒng)臭脓,首先得明確自己的中斷向量號(hào)是多少,還得提供自身中斷的服務(wù)程序腹忽,然后利用Linux的內(nèi)核調(diào)用界面来累,把〈中斷向量號(hào)、中斷服務(wù)程序〉這對(duì)信息填寫到中斷向量表中去窘奏。這樣CPU在接收到中斷信號(hào)時(shí)就會(huì)自動(dòng)調(diào)用中斷服務(wù)程序了嘹锁。這種注冊(cè)操作一般是由設(shè)備驅(qū)動(dòng)程序完成的。
其次蔼夜,操作系統(tǒng)必須提供給程序員簡(jiǎn)單可靠的編程界面來(lái)支持中斷兼耀。中斷的基本流程前面已經(jīng)講了,它會(huì)打斷當(dāng)前正在進(jìn)行的工作去執(zhí)行中斷服務(wù)程序求冷,然后再回到先前的任務(wù)繼續(xù)執(zhí)行瘤运。這中間有大量需要解決問(wèn)題:如何保護(hù)現(xiàn)場(chǎng)、嵌套中斷如何處理等等匠题,操作系統(tǒng)要一一化解拯坟。程序員,即使是驅(qū)動(dòng)程序的開發(fā)人員韭山,在寫中斷服務(wù)程序的時(shí)候也很少需要對(duì)被打斷的進(jìn)程心存憐憫郁季。(當(dāng)然,出于提高系統(tǒng)效率的考慮钱磅,編寫驅(qū)動(dòng)程序要比編寫用戶級(jí)程序多一些條條框框梦裂,誰(shuí)讓我們頂著系統(tǒng)程序員的光環(huán)呢?)
操作系統(tǒng)為我們屏蔽了這些與中斷相關(guān)硬件機(jī)制打交道的細(xì)節(jié)盖淡,提供了一套精簡(jiǎn)的接口年柠,讓我們用極為簡(jiǎn)單的方式實(shí)現(xiàn)對(duì)實(shí)際中斷的支持,Linux是怎么完美的做到這一點(diǎn)的呢褪迟?
CPU對(duì)中斷處理的流程:
我們首先必須了解CPU在接收到中斷信號(hào)時(shí)會(huì)做什么冗恨。沒(méi)辦法,操作系統(tǒng)必須了解硬件的機(jī)制味赃,不配合硬件就寸步難行∠颇ǎ現(xiàn)在我們假定內(nèi)核已被初始化,CPU在保護(hù)模式下運(yùn)行心俗。
CPU執(zhí)行完一條指令后傲武,下一條指令的邏輯地址存放在cs和eip這對(duì)寄存器中。在執(zhí)行新指令前,控制單元會(huì)檢查在執(zhí)行前一條指令的過(guò)程中是否有中斷或異常發(fā)生谱轨。如果有戒幔,控制單元就會(huì)拋下指令,進(jìn)入下面的流程:
- 確定與中斷或異常關(guān)聯(lián)的向量i (0£i£255)土童。
- 籍由idtr寄存器從IDT表中讀取第i項(xiàng)(在下面的描述中诗茎,我們假定該IDT表項(xiàng)中包含的是一個(gè)中斷門或一個(gè)陷阱門)。
- 從gdtr寄存器獲得GDT的基地址献汗,并在GDT表中查找敢订,以讀取IDT表項(xiàng)中的選擇符所標(biāo)識(shí)的段描述符。這個(gè)描述符指定中斷或異常處理程序所在段的基地址罢吃。
- 確信中斷是由授權(quán)的(中斷)發(fā)生源發(fā)出的楚午。首先將當(dāng)前特權(quán)級(jí)CPL(存放在cs寄存器的低兩位)與段描述符(即DPL,存放在GDT中)的描述符特權(quán)級(jí)比較尿招,如果CPL小于DPL矾柜,就產(chǎn)生一個(gè)“通用保護(hù)”異常,因?yàn)橹袛嗵幚沓绦虻奶貦?quán)不能低于引起中斷的程序的特權(quán)就谜。對(duì)于編程異常怪蔑,則做進(jìn)一步的安全檢查:比較CPL與處于IDT中的門描述符的DPL,如果DPL小于CPL丧荐,就產(chǎn)生一個(gè)“通用保護(hù)”異常缆瓣。這最后一個(gè)檢查可以避免用戶應(yīng)用程序訪問(wèn)特殊的陷阱門或中斷門。
- 檢查是否發(fā)生了特權(quán)級(jí)的變化虹统,也就是說(shuō)弓坞, CPL是否不同于所選擇的段描述符的DPL。如果是车荔,控制單元必須開始使用與新的特權(quán)級(jí)相關(guān)的棧渡冻。通過(guò)執(zhí)行以下步驟來(lái)做到這點(diǎn):
a.讀tr寄存器,以訪問(wèn)運(yùn)行進(jìn)程的TSS段忧便。
b.用與新特權(quán)級(jí)相關(guān)的棧段和棧指針的正確值裝載ss和esp寄存器菩帝。這些值可以在TSS中找到(參見第三章的“任務(wù)狀態(tài)段”一節(jié))。
c.在新的棧中保存ss和esp以前的值茬腿,這些值定義了與舊特權(quán)級(jí)相關(guān)的棧的邏輯地址。 - 如果故障已發(fā)生宜雀,用引起異常的指令地址裝載cs和eip寄存器切平,從而使得這條指令能再次被執(zhí)行。
- 在棧中保存eflag辐董、cs及eip的內(nèi)容悴品。
- 如果異常產(chǎn)生了一個(gè)硬錯(cuò)誤碼,則將它保存在棧中。
- 裝載cs和eip寄存器苔严,其值分別是IDT表中第i項(xiàng)門描述符的段選擇符和偏移量域定枷。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址。
控制單元所執(zhí)行的最后一步就是跳轉(zhuǎn)到中斷或者異常處理程序届氢。換句話說(shuō)欠窒,處理完中斷信號(hào)后,控制單元所執(zhí)行的指令就是被選中的處理程序的第一條指令退子。
中斷或異常被處理完后岖妄,相應(yīng)的處理程序必須產(chǎn)生一條iret指令,把控制權(quán)轉(zhuǎn)交給被中斷的進(jìn)程寂祥,這將迫使控制單元:
- 用保存在棧中的值裝載cs荐虐、eip、或eflag寄存器丸凭。如果一個(gè)硬錯(cuò)誤碼曾被壓入棧中福扬,并且在eip內(nèi)容的上面,那么惜犀,執(zhí)行iret指令前必須先彈出這個(gè)硬錯(cuò)誤碼铛碑。
- 檢查處理程序的CPL是否等于cs中最低兩位的值(這意味著被中斷的進(jìn)程與處理程序運(yùn)行在同一特權(quán)級(jí))。如果是向拆,iret終止執(zhí)行亚茬;否則,轉(zhuǎn)入下一步浓恳。
- 從棧中裝載ss和esp寄存器刹缝,因此,返回到與舊特權(quán)級(jí)相關(guān)的棧颈将。
- 檢查ds梢夯、es、fs及gs段寄存器的內(nèi)容晴圾,如果其中一個(gè)寄存器包含的選擇符是一個(gè)段描述符颂砸,并且其DPL值小于CPL,那么死姚,清相應(yīng)的段寄存器人乓。控制單元這么做是為了禁止用戶態(tài)的程序(CPL=3)利用內(nèi)核以前所用的段寄存器(DPL=0)都毒。如果不清這些寄存器色罚,懷有惡意的用戶程序就可能利用它們來(lái)訪問(wèn)內(nèi)核地址空間。
再次账劲,操作系統(tǒng)必須保證中斷信息能夠高效可靠的傳遞
注一:那么PowerOff(關(guān)機(jī))算不算中斷呢戳护?如果從字面上講金抡,肯定符合漢語(yǔ)對(duì)中斷的定義,但是從信號(hào)格式腌且、處理方法等方面來(lái)看梗肝,就很難符合我們的理解了。Intel怎么說(shuō)的呢铺董?該中斷沒(méi)有采用通用的中斷處理機(jī)制巫击。那么到底是不是中斷呢?我也說(shuō)不上來(lái):(
注二:更詳細(xì)的內(nèi)容和其它一些注意事項(xiàng)請(qǐng)參考內(nèi)核源代碼包中Documentations/rtc.txt
注三:之所以這里使用匯編而不是C來(lái)實(shí)現(xiàn)這些函數(shù)柄粹,是因?yàn)镃編譯器會(huì)在函數(shù)的實(shí)現(xiàn)中推入額外的棧信息喘鸟。而CPU在中斷來(lái)臨時(shí)保存和恢復(fù)現(xiàn)場(chǎng)都按照嚴(yán)格的格式進(jìn)行,一個(gè)字節(jié)的變化都不能有驻右。