




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、摘要:本章將向讀者依次解釋中斷概念,解析Linux中的中斷實(shí)現(xiàn)機(jī)理以及Linux下中斷如何被使用。作為實(shí)例我們第一將向i386體系結(jié)構(gòu)一章中打造的系統(tǒng)加入一個(gè)時(shí)鐘中斷;第二將為大家注解RTC中斷,希望通過(guò)這兩個(gè)實(shí)例可以幫助讀者掌握中斷相關(guān)的概念、實(shí)現(xiàn)和編程方法。中斷是什么中斷的漢語(yǔ)解釋是半中間發(fā)生阻隔、停頓或故障而斷開(kāi)。那么,在計(jì)算機(jī)系統(tǒng)中,我們?yōu)槭裁葱枰白韪簟⑼nD和斷開(kāi)”呢?舉個(gè)日常生活中的例子,比如說(shuō)我正在廚房用煤氣燒一壺水,這樣就只能守在廚房里,苦苦等著水開(kāi)如果水溢出來(lái)澆滅了煤氣,有可能就要發(fā)生一場(chǎng)災(zāi)難了。等啊等啊,外邊突然傳來(lái)了驚奇的叫聲“怎么不關(guān)水龍頭?”于是我慚愧的發(fā)現(xiàn),剛才接水
2、之后只顧著抱怨這份無(wú)聊的差事,居然忘了這事,于是慌慌張張的沖向水管,三下兩下關(guān)了龍頭,聲音又傳到耳邊,“怎么干什么都是這么馬虎?”。伸伸舌頭,這件小事就這么過(guò)去了,我落寞的眼神又落在了水壺上。門(mén)外忽然又傳來(lái)了鏗鏘有力的歌聲,我最喜歡的古裝劇要開(kāi)演了,真想奪門(mén)而出,然而,聽(tīng)著水壺發(fā)出“咕嘟咕嘟”的聲音,我清楚:除非等到水開(kāi),否則沒(méi)有我享受人生的時(shí)候。這個(gè)場(chǎng)景跟中斷有什么關(guān)系呢?如果說(shuō)我專心致志等待水開(kāi)是一個(gè)過(guò)程的話,那么叫聲、電視里傳出的音樂(lè)不都讓這個(gè)過(guò)程“半中間發(fā)生阻隔、停頓或故障而斷開(kāi)”了嗎?這不就是活生生的“中斷”嗎?在這個(gè)場(chǎng)景中,我是唯一具有處理能力的主體,不管是燒水、關(guān)龍頭還是看電視,
3、同一個(gè)時(shí)間點(diǎn)上我只能干一件事情。但是,在我專心致志干一件事情時(shí),總有許多或緊迫或不緊迫的事情突然出現(xiàn)在面前,都需要去關(guān)注,有些還需要我停下手頭的工作馬上去處理。只有在處理完之后,方能回頭完成先前的任務(wù), “把一壺水徹底燒開(kāi)!”中斷機(jī)制不僅賦予了我處理意外情況的能力,如果我能充分發(fā)揮這個(gè)機(jī)制的妙用,就可以“同時(shí)”完成多個(gè)任務(wù)了。回到燒水的例子,實(shí)際上,無(wú)論我在不在廚房,煤氣灶總是會(huì)把水燒開(kāi)的,我要做的,只不過(guò)是及時(shí)關(guān)掉煤氣灶而已,為了這么一個(gè)一秒鐘就能完成的動(dòng)作,卻讓我死死的守候在廚房里,在10分鐘的時(shí)間里不停的看壺嘴是不是冒蒸汽,怎么說(shuō)都不劃算。我決定安下心來(lái)看電視。當(dāng)然,在有生之年,我都不希
4、望讓廚房成為火海,于是我上了鬧鐘,10分鐘以后它會(huì)發(fā)出“尖叫”,提醒我爐子上的水燒開(kāi)了,那時(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)同樣的好處:如果在鍵盤(pán)按下的時(shí)候會(huì)得到一個(gè)中斷信號(hào),CPU就不必死守著等待鍵盤(pán)輸入了;如果硬盤(pán)讀寫(xiě)完成后發(fā)送一個(gè)中斷信號(hào),CPU就可以騰出手來(lái)集中精力“服務(wù)大眾”了無(wú)論是人類敲打鍵盤(pán)的指尖還是來(lái)回讀寫(xiě)介質(zhì)的磁頭,跟CPU的處理速度相比,都太慢了。沒(méi)有中斷機(jī)制,就像我們苦守
5、廚房一樣,計(jì)算機(jī)談不上有什么的并行處理能力。跟人相似,CPU也一樣要面對(duì)紛繁蕪雜的局面現(xiàn)實(shí)中的意外是無(wú)處不在的有可能是用戶等得不耐煩,猛敲鍵盤(pán);有可能是運(yùn)算中碰到了0除數(shù);還有可能網(wǎng)卡突然接收到了一個(gè)新的數(shù)據(jù)包。這些都需要CPU具體情況具體分析,要么馬上處理,要么暫緩響應(yīng),要么置之不理。無(wú)論如何應(yīng)對(duì),都需要 CPU暫停“手頭”的工作,拿出一種對(duì)策,只有在響應(yīng)之后,方能回頭完成先前的使命,“把一壺水徹底燒開(kāi)!”先讓我們感受一下中斷機(jī)制對(duì)并發(fā)處理帶來(lái)的幫助。讓我們用程序來(lái)探討一下燒水問(wèn)題,如果沒(méi)有“中斷”(注意,我們這里只是模仿中斷的場(chǎng)景,實(shí)際上是用異步事件消息處理機(jī)制來(lái)展示中斷產(chǎn)生的效果。畢竟,
6、在用戶空間沒(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 )
7、60; WaterIsBoiled = true;else WaterIsBoiled = false;/ 關(guān)煤氣爐printf(“Close gas oven.n”);/ 一切安定下來(lái),終于可以看電視了,10分鐘的寶貴時(shí)間啊,逝者如斯夫watching_tv(
8、);return;可以看出,整個(gè)流程如同我們前面描述的一樣,所有工作要順序執(zhí)行,沒(méi)有辦法完成并發(fā)任務(wù)。如果用“中斷”,在開(kāi)始燒水的時(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(
9、int signo) /關(guān)煤氣爐 printf(“Close gas oven.n”);void watching_tv() while(1) / 呵呵,優(yōu)哉游哉 int main()/ 點(diǎn)火后設(shè)置定時(shí)中斷 printf(“Start to
10、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ì)比,我們可以清楚地看到異
11、步的事件處理機(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)允許程序自行決定。程序員編寫(xiě)一個(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é)了)。上面的例子里我們提供
12、的函數(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ī)能夠接收的外部
13、信號(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)心鬧鐘
14、響時(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í)
15、現(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)入下面的流程:1. 確定與中斷或異常關(guān)聯(lián)的向量i (0£i£255)2. 尋找向量對(duì)應(yīng)的處理程序3. 保存當(dāng)前的“工作現(xiàn)場(chǎng)”,執(zhí)行中斷或異常的處理程序4
16、. 處理程序執(zhí)行完畢后,把控制權(quán)交還給控制單元5. 控制單元恢復(fù)現(xiàn)場(chǎng),返回繼續(xù)執(zhí)行原程序整個(gè)流程如下圖所示:讓我們深入這個(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í)際上,
17、異常往往被稱為同步中斷。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è)中斷向量這么麻煩的東西?為什么不直接用 IRQ0IRQ15就完了?比如就讓IRQ0為0,IRQ1為1,這不是要簡(jiǎn)單的多么?其實(shí)這里體現(xiàn)了模塊化設(shè)計(jì)規(guī)則,及節(jié)約規(guī)則。首先我們先談?wù)劰?jié)
18、約規(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根線不就行了嗎?(24=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īng)為對(duì)于CPU來(lái)說(shuō),中斷處理的過(guò)程都是一樣的:中斷現(xiàn)行程序,轉(zhuǎn)到中斷服務(wù)程序處執(zhí)行,回到被中斷的程序繼續(xù)執(zhí)行。CPU總共可以處理256種中斷,而并
19、不知道,也不應(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芯片,那么這
20、個(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ù)專門(mén)處理來(lái)自系統(tǒng)時(shí)鐘的中斷,而另外一個(gè)函數(shù)專門(mén)處理由鍵盤(pán)產(chǎn)
21、生的中斷。一般來(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ù)定的程序
22、進(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),返回就可
23、以了并負(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)要求的接口格式撰寫(xiě)中斷服務(wù)程序,用操作系統(tǒng)提供的函數(shù)注冊(cè)該服務(wù)程序,設(shè)備的中斷就被系統(tǒng)支持了。中斷和對(duì)中斷的處理被解除了耦合。這樣,無(wú)論是你在需要加入新的中斷時(shí),還是在你需要改變現(xiàn)有中斷的服務(wù)程序時(shí)、又或是取消對(duì)某個(gè)中斷支持的時(shí)候,CPU架構(gòu)和
24、操作系統(tǒng)都無(wú)需作改變。5、保存當(dāng)前工作“現(xiàn)場(chǎng)”在中斷處理完畢后,計(jì)算機(jī)一般來(lái)說(shuō)還要回頭處理原先手頭正做的工作。這給中斷的概念帶來(lái)些額外的“內(nèi)涵”1。“回頭”不是指從頭再來(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ò)程,我們可以抽象出其中的步驟:1. &
25、#160; 保存現(xiàn)場(chǎng)2. 執(zhí)行具體的中斷服務(wù)程序3. 從中斷服務(wù)返回4. 恢復(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)所作的都是完全相同的操作。既然操作相同,
26、實(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)容,可是我看到了瀟寒寫(xiě)的“保護(hù)模式下的8259A芯片編程及中斷處理探究”,前人之述備矣,讀者直接讀它好了。Linux
27、下的中斷在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í)
28、行也同樣重要。即使最精簡(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)用程序。顯而易見(jiàn),這種運(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ī)制是
29、靠?jī)蓧K8259和CPU協(xié)作實(shí)現(xiàn)的。外設(shè)要做的只是把中斷信號(hào)發(fā)送到8259的某個(gè)特定引腳上,這樣8259就會(huì)為此中斷分配一個(gè)標(biāo)識(shí) 也就是通常所說(shuō)的中斷向量,通過(guò)中斷向量,CPU就能夠在以中斷向量為索引的表中斷向量表里找到中斷服務(wù)程序,由它決定具體如何處理中斷。這是硬件規(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ì)信息填寫(xiě)到中斷向量表中去。這樣CPU在接收到中斷信號(hào)時(shí)就會(huì)自動(dòng)調(diào)用中斷服務(wù)程序了。這種注冊(cè)操作
30、一般是由設(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)程序的開(kāi)發(fā)人員,在寫(xiě)中斷服務(wù)程序的時(shí)候也很少需要對(duì)被打斷的進(jìn)程心存憐憫。(當(dāng)然,出于提高系統(tǒng)效率的考慮,編寫(xiě)驅(qū)動(dòng)程序要比編寫(xiě)用戶級(jí)程序多一些條條框框,誰(shuí)讓我們頂著系統(tǒng)程序員的光環(huán)呢?)操作系統(tǒng)為我們屏蔽了這些與中斷相關(guān)硬件機(jī)制打交道的細(xì)節(jié),提供了一套精簡(jiǎn)的接口,讓我們用極為簡(jiǎn)單的方式實(shí)現(xiàn)對(duì)實(shí)際中斷的支持,L
31、inux是怎么完美的做到這一點(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)入下面的流程:1.確定與中斷或異常關(guān)聯(lián)的向量i (0£i£255)。2.籍由idtr寄存器從IDT表中讀取第i項(xiàng)(在下面的描述中,我們假定該IDT表項(xiàng)中包含的是一個(gè)中斷門(mén)或一個(gè)陷
32、阱門(mén))。3.從gdtr寄存器獲得GDT的基地址,并在GDT表中查找,以讀取IDT表項(xiàng)中的選擇符所標(biāo)識(shí)的段描述符。這個(gè)描述符指定中斷或異常處理程序所在段的基地址。4.確信中斷是由授權(quán)的(中斷)發(fā)生源發(fā)出的。首先將當(dāng)前特權(quán)級(jí)CPL(存放在cs寄存器的低兩位)與段描述符(存放在GDT中)的描述符特權(quán)級(jí)DPL比較,如果CPL小于DPL,就產(chǎn)生一個(gè)“通用保護(hù)”異常,因?yàn)橹袛嗵幚沓绦虻奶貦?quán)不能低于引起中斷的程序的特權(quán)。對(duì)于編程異常,則做進(jìn)一步的安全檢查:比較CPL與處于IDT中的門(mén)描述符的DPL,如果DPL小于CPL,就產(chǎn)生一個(gè)“通用保護(hù)”異常。這最后一個(gè)檢查可以避免用戶應(yīng)用程序訪問(wèn)特殊的陷阱門(mén)或中斷門(mén)。
33、5.檢查是否發(fā)生了特權(quán)級(jí)的變化,也就是說(shuō), CPL是否不同于所選擇的段描述符的DPL。如果是,控制單元必須開(kāi)始使用與新的特權(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中找到(參見(jiàn)第三章的“任務(wù)狀態(tài)段”一節(jié))。c.在新的棧中保存ss和esp以前的值,這些值定義了與舊特權(quán)級(jí)相關(guān)的棧的邏輯地址。 6.如果故障已發(fā)生,用引起異常的指令地址裝載cs和eip寄存器,從而使得這條指令能再次被執(zhí)行。 7.在
34、棧中保存eflag、cs及eip的內(nèi)容。8.如果異常產(chǎn)生了一個(gè)硬錯(cuò)誤碼,則將它保存在棧中。9.裝載cs和eip寄存器,其值分別是IDT表中第i項(xiàng)門(mén)描述符的段選擇符和偏移量域。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址。控制單元所執(zhí)行的最后一步就是跳轉(zhuǎn)到中斷或者異常處理程序。換句話說(shuō),處理完中斷信號(hào)后, 控制單元所執(zhí)行的指令就是被選中處理程序的第一條指令。中斷或異常被處理完后,相應(yīng)的處理程序必須0x20/0x21/0xa0/0xa1產(chǎn)生一條iret指令,把控制權(quán)轉(zhuǎn)交給被中斷的進(jìn)程,這將迫使控制單元:1.用保存在棧中的值裝載cs、eip、或eflag寄存器。如果一個(gè)硬錯(cuò)誤碼曾被壓入棧中
35、,并且在eip內(nèi)容的上面,那么,執(zhí)行iret指令前必須先彈出這個(gè)硬錯(cuò)誤碼。2.檢查處理程序的CPL是否等于cs中最低兩位的值(這意味著被中斷的進(jìn)程與處理程序運(yùn)行在同一特權(quán)級(jí))。如果是,iret終止執(zhí)行;否則,轉(zhuǎn)入下一步。3. 從棧中裝載ss和esp寄存器,因此,返回到與舊特權(quán)級(jí)相關(guān)的棧。4. 檢查ds、es、fs及gs段寄存器的內(nèi)容,如果其中一個(gè)寄存器包含的選擇符是一個(gè)段描述符,并且其DPL值小于CPL,那么,清相應(yīng)的段寄存器。控制單元這么做是為了禁止用戶態(tài)的程序(CPL=3)利用內(nèi)核以前所用的段寄存器(DPL=0)。如果不清這些寄存器,懷有
36、惡意的用戶程序就可能利用它們來(lái)訪問(wèn)內(nèi)核地址空間。再次,操作系統(tǒng)必須保證中斷信息能夠高效可靠的傳遞實(shí)例一為自己的操作系統(tǒng)中加入中斷中斷機(jī)制的實(shí)現(xiàn)在這個(gè)部分,我將為大家詳細(xì)介紹SagaLinux_irq中是如何處理中斷的。為了更好的演示軟硬件交互實(shí)現(xiàn)中斷機(jī)制的過(guò)程,我將在前期實(shí)現(xiàn)的SagaLinux上加入對(duì)一個(gè)新中斷定時(shí)中斷的支持。首先,讓我介紹一下SagaLinux_irq中涉及中斷的各部分代碼。這些代碼主要包含在kernel目錄下,包括idt.c,irq.c,i8259.s,boot目錄下的setup.s也和中斷相關(guān),下面將對(duì)他們進(jìn)行討論。1、boot/setup.ssetup.s中相關(guān)于中斷
37、的部分主要集中在pic_init小結(jié),該部分完成了對(duì)中斷控制器的初始化。對(duì)8259A的編程是通過(guò)向其相應(yīng)的端口發(fā)送一系列的ICW(初始化命令字)完成的。總共需要發(fā)送四個(gè)ICW,它們都分別有自己獨(dú)特的格式,而且必須按次序發(fā)送,并且必須發(fā)送到相應(yīng)的端口,具體細(xì)節(jié)請(qǐng)查閱相關(guān)資料。pic_init: cli mov al, 0x11 initialize PICs; 給中斷寄存器編程; 發(fā)送ICW1:使用ICW4,級(jí)聯(lián)工作
38、 out 0x20, al 8259_MASTER out 0xA0, al 8259_SLAVE ; 發(fā)送 ICW2,中斷起始號(hào)從 0x20 開(kāi)始(第一片)及 0x28開(kāi)始(第二片) mov al, 0x20
39、; interrupt start 32 out 0x21, al mov al, 0x28 ; interrupt start 40 out 0xA1, al; 發(fā)送 ICW3 mov al, 0x04 ; IRQ 2 of 8259_MASTER out 0x2
40、1, al ; 發(fā)送 ICW4 mov al, 0x02 ; to 8259_SLAVE out 0xA1, al; 工作在80x86架構(gòu)下 mov al, 0x01 ; 8086 Mode out 0x21, al
41、; out 0xA1, al; 設(shè)置中斷屏蔽位 OCW1 ,屏蔽所有中斷請(qǐng)求 mov al, 0xFF ; mask all out 0x21, al out 0xA1, al sti 2、kernel/irq.c irq.c提供了三個(gè)函數(shù)enable_irq、disable_irq和request_irq,函數(shù)原型如下:void enabl
42、e_irq(int irq)void disable_irq(int irq)void request_irq(int irq, void (*handler)() enable_irq和disable_irq用來(lái)開(kāi)啟和關(guān)閉右參數(shù)irq指定的中斷,這兩個(gè)函數(shù)直接對(duì)8259的寄存器進(jìn)行操作,因此irq 對(duì)應(yīng)的是實(shí)實(shí)在在的中斷號(hào),比如說(shuō)X86下時(shí)鐘中斷一般為0號(hào)中斷,那么啟動(dòng)時(shí)鐘中斷就需要調(diào)用enable_irq(1),而鍵盤(pán)一般占用2號(hào)中斷,那么關(guān)閉鍵盤(pán)中斷就需要調(diào)用disable_irq(2)。irq對(duì)應(yīng)的不是中斷向量。equest_irq用來(lái)
43、將中斷號(hào)和中斷服務(wù)程序綁定起來(lái),綁定完成后,命令8259開(kāi)始接受中斷請(qǐng)求。下面是request_irq的實(shí)現(xiàn)代碼:void request_irq(int irq, void (*handler)() irq_handlerirq = handler; enable_irq(irq);其中irq_handler是一個(gè)擁有16個(gè)元素的數(shù)組,數(shù)組項(xiàng)是指向函數(shù)的指針,每個(gè)指針可以指向一個(gè)中斷服務(wù)程序。 irq_handlerirq = handler 就是一個(gè)給數(shù)組項(xiàng)賦值的過(guò)程,其中隱藏了中斷號(hào)向中斷向量映射的過(guò)程,在初始化IDT表的部分,我會(huì)介紹相關(guān)內(nèi)容
44、。3、kernel/i8259.s2i8259.c負(fù)責(zé)對(duì)外部中斷的支持。我們已經(jīng)討論過(guò)了,8259芯片負(fù)責(zé)接收外部設(shè)備如定時(shí)器、鍵盤(pán)、聲卡等的中斷,兩塊8259共支持16個(gè)中斷。我們也曾討論過(guò),在編寫(xiě)操作系統(tǒng)的時(shí)候,我們不可能知道每個(gè)中斷到底對(duì)應(yīng)的是哪個(gè)中斷服務(wù)程序。實(shí)際上,通常在這個(gè)時(shí)候,中斷服務(wù)程序壓根還沒(méi)有被編寫(xiě)出來(lái)。可是,X86體系規(guī)定,在初始化中斷向量表的時(shí)候,必須提供每個(gè)向量對(duì)應(yīng)的服務(wù)程序的偏移地址,以便CPU在接收到中斷時(shí)調(diào)用相應(yīng)的服務(wù)程序,這該如何是好呢?巧婦難為無(wú)米之炊,此時(shí)此刻,我們只有創(chuàng)造所有中斷對(duì)應(yīng)的服務(wù)程序,才能完成初始化IDT的工作,于是我們制造出16個(gè)函數(shù)_irq
45、0到 _irq15,在注冊(cè)中斷服務(wù)程序的時(shí)候,我們就把它們填寫(xiě)到IDT的描述符中去。(在SagaLinux中當(dāng)前的實(shí)現(xiàn)里,我并沒(méi)有填寫(xiě)完整的IDT 表,為了讓讀者看得較為清楚,我只加入了定時(shí)器和鍵盤(pán)對(duì)應(yīng)的_irq和_irq1。但這樣一來(lái)就帶來(lái)一個(gè)惡果,讀者會(huì)發(fā)現(xiàn)在加入新的中斷支持時(shí),需要改動(dòng)idt.c中的trap_init函數(shù),用set_int_gate對(duì)新中斷進(jìn)行支持。完全背離了我們強(qiáng)調(diào)的分隔變化的原則。實(shí)際上,只要我們?cè)谶@里填寫(xiě)完整,并提供一個(gè)缺省的中斷服務(wù)函數(shù)就可以解決這個(gè)問(wèn)題。我再?gòu)?qiáng)調(diào)一遍,這不是設(shè)計(jì)問(wèn)題,只是為了便于讀者觀察而做的簡(jiǎn)化。)可是,這16個(gè)函數(shù)怎么能對(duì)未知的中斷進(jìn)行有針對(duì)
46、性的個(gè)性化服務(wù)呢?當(dāng)然不能,這16個(gè)函數(shù)只是一個(gè)接口,我們可以在其中留下后門(mén),當(dāng)新的中斷需要被系統(tǒng)支持時(shí),它實(shí)際的中斷服務(wù)程序就能被這些函數(shù)調(diào)用。具體調(diào)用關(guān)系請(qǐng)參考圖2如圖2所示,_irq0到_irq15會(huì)被填充到IDT從32到47(之所以映射到這個(gè)區(qū)間是為了模仿Linux的做法,其實(shí)這部分的整個(gè)實(shí)現(xiàn)都是在模仿Linux)這16個(gè)條目的中斷描述符中去,這樣中斷到來(lái)的時(shí)候就會(huì)調(diào)用相應(yīng)的_irq函數(shù)。所有irq函數(shù)所作的工作基本相同,把中斷號(hào)壓入棧中,再調(diào)用do_irq函數(shù);它們之間唯一區(qū)別的地方就在于不同的irq函數(shù)壓入的中斷號(hào)不同。do_irq首先會(huì)從棧中取出中斷號(hào),然后根據(jù)中斷號(hào)計(jì)算該中斷對(duì)
47、應(yīng)的中斷服務(wù)程序在irq_handler數(shù)組中的位置,并跳到該位置上去執(zhí)行相應(yīng)的服務(wù)程序。還記得irq.c中介紹的request_irq函數(shù)嗎,該函數(shù)綁定中斷號(hào)和中斷服務(wù)程序的實(shí)現(xiàn),其實(shí)就是把指向中斷服務(wù)程序的指針填寫(xiě)到中斷號(hào)對(duì)應(yīng)的irq_handler數(shù)組中去。現(xiàn)在,你應(yīng)該明白我們是怎樣把一個(gè)中斷服務(wù)程序加入到SagaLinux中的了吧通過(guò)一個(gè)中間層,我們可以做任何事情。在上圖的實(shí)現(xiàn)中,IDT表格中墨綠色的部分外部中斷對(duì)應(yīng)的部分可以浮動(dòng),也就是說(shuō),我們可以任意選擇映射的起始位置,比如說(shuō),我們讓_irq0映射到IDT的第128項(xiàng),只要后續(xù)的映射保持連續(xù)就可以了。4、kernel/idt.cid
48、t.c當(dāng)然是用來(lái)初始化IDT表的了。在i8259.s中我們介紹了操作系統(tǒng)是如何支持中斷服務(wù)程序的添加的,但是,有兩個(gè)部分的內(nèi)容沒(méi)有涉及:一是如何把_irq函數(shù)填寫(xiě)到IDT表中,另外一個(gè)就是中斷支持了,那異常怎么支持呢?idt.c負(fù)責(zé)解決這兩方面的問(wèn)題。idt.c提供了trap_init函數(shù)來(lái)填充IDT表。 void trap_init() int i; idtr_t idtr; / 填入系統(tǒng)默認(rèn)的異常,共17個(gè)
49、0;set_trap_gate(0, (unsigned int)÷_error); set_trap_gate(1, (unsigned int)&debug); set_trap_gate(2, (unsigned int)&nmi); set_trap_gate(3, (unsigned int)&int3); set_trap_gate(4, (unsigned int)&overflow); set_trap_gate(5,
50、 (unsigned int)&bounds); set_trap_gate(6, (unsigned int)&invalid_op); set_trap_gate(7, (unsigned int)&device_not_available); set_trap_gate(8, (unsigned int)&double_fault); set_trap_gate(9, (unsigned int)&coprocessor_segment_overrun);
51、60; set_trap_gate(10,(unsigned int) &invalid_TSS); set_trap_gate(11, (unsigned int)&segment_not_present); set_trap_gate(12, (unsigned int)&stack_segment); set_trap_gate(13, (unsigned int)&general_protection); set_trap_gate(14, (unsigne
52、d int)&page_fault); set_trap_gate(15, (unsigned int)&coprocessor_error); set_trap_gate(16, (unsigned int)&alignment_check);/ 17到31這15個(gè)異常是intel保留的,最好不要占用 for (i = 17;i<32;i+) set_trap_gate(i, (unsigned int)&reserved); / 我們只在I
53、DT中填入定時(shí)器和鍵盤(pán)要用到的兩個(gè)中斷 set_int_gate(32, (unsigned int)&_irq0); set_int_gate(33, (unsigned int)&_irq1);/ 一共有34個(gè)中斷和異常需要支持 idtr.limit = 34*8; idtr.lowerbase = 0x0000; idtr.higherbase = 0x0000; cli();/ 載入IDT表,新的中斷可以用了 _as
54、m_ _volatile_ ("lidt (%0)" :"p" (&idtr); sti();首先我們來(lái)看看set_trap_gate和set_int_gate函數(shù),下面是它們兩個(gè)的實(shí)現(xiàn)void set_trap_gate(int vector, unsigned int handler_offset) trapgd_t* trapgd = (trapgd_t*) IDT_BASE + vector;&
55、#160; trapgd->loffset = handler_offset & 0x0000FFFF; trapgd->segment_s = CODESEGMENT; trapgd->reserved = 0x00; trapgd->options = 0x0F | PRESENT | KERNEL_LEVEL; trapgd->hoffset = (handler_offset & 0xFFFF0000) >>
56、16);void set_int_gate(int vector, unsigned int handler_offset) intgd_t* intgd = (intgd_t*) IDT_BASE + vector; intgd->loffset = handler_offset & 0x0000FFFF; intgd->segment_s = CODESEGMENT; intgd->reserved = 0x0;
57、;intgd->options = 0x0E | PRESENT | KERNEL_LEVEL; intgd->hoffset = (handler_offset & 0xFFFF0000) >> 16); 我們可以發(fā)現(xiàn),它們所作的工作就是根據(jù)中斷向量號(hào)計(jì)算出應(yīng)該把指向中斷或異常服務(wù)程序的指針?lè)旁谑裁碔DT表中的什么位置,然后把該指針和中斷描述符設(shè)置好就行了。同樣,中斷描述符的格式請(qǐng)查閱有關(guān)資料。現(xiàn)在,來(lái)關(guān)注一下set_trap_gate的參數(shù),又是指向函數(shù)的指針。在這里,我們看到每個(gè)這樣的指針指向一個(gè)異常處理函數(shù),如
58、divide_error、debug等:void divide_error(void) sleep("divide error");void debug(void) sleep("debug"); 每個(gè)函數(shù)都調(diào)用了sleep,那么sleep是有何作用?是不是像do_irq一樣調(diào)用具體異常的中斷服務(wù)函數(shù)呢?/ Nooooo . just sleep :)void sleep(char* message) printk("%s",message);
59、 while(1); 看樣子不是,這個(gè)函數(shù)就是休眠而已!實(shí)際上,我們這里進(jìn)行了簡(jiǎn)化,對(duì)于Intel定義好的前17個(gè)內(nèi)部異常,目前SagaLinux還不能做有針對(duì)性的處理,因此我們直接讓系統(tǒng)無(wú)限制地進(jìn)入休眠跟死機(jī)區(qū)別不大。因此,當(dāng)然也不用擔(dān)心恢復(fù)“現(xiàn)場(chǎng)”的問(wèn)題了,不用考慮棧的影響,所以直接用C函數(shù)實(shí)現(xiàn)。此外,由于這17個(gè)異常如何處理在這個(gè)時(shí)候我們已經(jīng)確定下來(lái)了sleep,既然沒(méi)有什么變化,我們也就不用耗盡心思的考慮去如何支持變化了,直接把函數(shù)硬編碼就可以了。Intel規(guī)定中斷描述符表的第17-31項(xiàng)保留,為硬件將來(lái)可能的擴(kuò)展用,因此我們這里將它閑置
60、起來(lái)。void reserved(void)sleep("reserved");下面的部分是對(duì)外部中斷的初始化,放在trap_init中是否有些名不正言不順呢?確實(shí)如此,這個(gè)版本暫時(shí)把它放在這里,以后重構(gòu)的時(shí)候再調(diào)整吧。注意,這個(gè)部分解釋了我們是如何把中斷服務(wù)程序放置到IDT中的。此外,可以看出,我們使用手工方式對(duì)中斷向量號(hào)進(jìn)行了映射,_irq0對(duì)應(yīng)32 號(hào)中斷,而_irq1對(duì)應(yīng)33號(hào)中斷。能不能映射成別的向量呢?當(dāng)然可以,可是別忘了修改setup.s中的pic_init部分,要知道,我們初始化 8259的時(shí)候定義好了外部中斷對(duì)應(yīng)的向量,如果你希望從8259發(fā)來(lái)的中斷信號(hào)能
61、正確的觸發(fā)相應(yīng)的中斷服務(wù)程序,當(dāng)然要把所有的接收處理鏈條上的每個(gè)映射關(guān)系都改過(guò)來(lái)。我們只填充了34個(gè)表項(xiàng),每個(gè)表項(xiàng)8字節(jié)長(zhǎng),因此我們把IDT表的長(zhǎng)度上限設(shè)為34x8,把IDT表放置在邏輯地址起始的地方(如果我們沒(méi)有啟用分頁(yè)機(jī)制,那么就是在線性空間起始的地方,也就是物理地址的0位置處)。最后,調(diào)用ldtr指令啟用新的中斷處理機(jī)制,SagaLinux的初步中斷支持機(jī)制就完成了。下面,我們以定時(shí)器(timer)設(shè)備為例,展示如何通過(guò)SagaLinux目前提供的中斷服務(wù)程序接口來(lái)支持設(shè)備的中斷。IBM PC兼容機(jī)包含了一種時(shí)間測(cè)量設(shè)備,叫做可編程間隔定時(shí)器(PIT)。PIT的作用類似于鬧鐘,在設(shè)定的時(shí)
62、間點(diǎn)到來(lái)的時(shí)候發(fā)出中斷信號(hào)。這種中斷叫做定時(shí)中斷(timer interrupt)。在Linux操作系統(tǒng)中,就是它來(lái)通知內(nèi)核又一個(gè)時(shí)間片斷過(guò)去了。與鬧鐘不同,PIT以某一固定的頻率(編程控制)不停地發(fā)出中斷。每個(gè)IBM PC兼容機(jī)至少都會(huì)包含一個(gè)PIT,一般來(lái)說(shuō),它就是一個(gè)使用0x400x43 I/O端口的8254CMOS芯片。 SagaLinux目前的版本還不支持進(jìn)程調(diào)度,因此定時(shí)中斷的作用還不明顯,不過(guò),作為一個(gè)做常見(jiàn)的中斷源,我們可以讓它每隔一定時(shí)間發(fā)送一個(gè)中斷信號(hào),而我們?cè)诙〞r(shí)中斷的中斷服務(wù)程序中計(jì)算流逝過(guò)去的時(shí)間數(shù),然后打印出結(jié)果,充分體現(xiàn)中斷的效果。我們?cè)?/p>
63、kernel目錄下編寫(xiě)了timer.c文件,也在include目錄下加入了相應(yīng)的timer.h,下面就是具體的實(shí)現(xiàn)。/ 流逝的時(shí)間static volatile ulong_t counter; / 中斷服務(wù)程序void timer_handler() / 中斷每10毫秒一次 counter += 10;/ 初始化硬件和技術(shù)器,啟用中斷void timer_init() ushort_t pit_counter = CLOCK_RATE * INTERVA
64、L / SECOND; counter = 0; outb (SEL_CNTR0|RW_LSB_MSB|MODE2|BINARY_STYLE, CONTROL_REG); outb (pit_counter & 0xFF, COUNTER0_REG); outb (pit_counter >> 8, COUNTER0_REG); / 申請(qǐng)0號(hào)中斷,TIMER定義為0 request_irq(TIMER, timer_handler);/ 返回流
65、逝過(guò)去的時(shí)間ulong_t uptime() return counter;timer_init函數(shù)是核心函數(shù),負(fù)責(zé)硬件的初始化和中斷的申請(qǐng),對(duì)8254的初始化就不多做糾纏了,請(qǐng)查閱有關(guān)資料。我們可以看到,申請(qǐng)中斷確實(shí)跟預(yù)想中的一樣容易,調(diào)用request_irq,一行語(yǔ)句就完成了中斷的注冊(cè)。而中斷服務(wù)程序非常簡(jiǎn)單,由于把8254設(shè)置為每10毫秒發(fā)送一次中斷,因此每次中斷到來(lái)時(shí)都在服務(wù)程序中對(duì)counter加10,所以counter表示的就是流逝的時(shí)間。在kernel.c中,我們調(diào)用timer_init進(jìn)行初始化,此時(shí)定時(shí)中斷就被激活了,如果我們的中斷機(jī)制運(yùn)轉(zhuǎn)順利,那么流
66、逝時(shí)間會(huì)不斷增加。為了顯示出這樣的結(jié)果,我們編寫(xiě)一個(gè)循環(huán)不斷的調(diào)uptime函數(shù),并把返回的結(jié)果打印在屏幕上。如果打印出的數(shù)值越來(lái)越大,那就說(shuō)明我們的中斷機(jī)制確確實(shí)實(shí)發(fā)揮了作用,定時(shí)中斷被驅(qū)動(dòng)起來(lái)了。 在kernel.c中: / 初始化 int i = 0;
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 知識(shí)產(chǎn)權(quán)收益權(quán)質(zhì)押合作框架協(xié)議書(shū)
- 帶車位地下室自用住宅產(chǎn)權(quán)交換合同
- 工程驗(yàn)收及工程驗(yàn)收?qǐng)?bào)告補(bǔ)充協(xié)議
- 基因檢測(cè)技術(shù)專利授權(quán)與商業(yè)化合作協(xié)議
- 針對(duì)虛擬礦場(chǎng)建設(shè)的PDU電源租賃與能源管理合同
- 心理健康教育教師c級(jí)上崗考試試題及答案
- 網(wǎng)紅燒烤品牌區(qū)域代理權(quán)及線上線下聯(lián)合運(yùn)營(yíng)合作協(xié)議
- 直播平臺(tái)與主播商品銷售額度分成合同
- 商業(yè)衛(wèi)星地面站維護(hù)工程師招聘合同
- 校招小學(xué)語(yǔ)文面試題目及答案
- MOOC 兒科學(xué)-四川大學(xué) 中國(guó)大學(xué)慕課答案
- GB/T 3098.6-2023緊固件機(jī)械性能不銹鋼螺栓、螺釘和螺柱
- 上海市材料工程學(xué)校教師招聘考試真題2022
- 【課件】Unit+3Reading+and+Thinking+課件高中英語(yǔ)人教版(2019)選擇性必修第四冊(cè)
- 《太上感應(yīng)篇》原文
- (計(jì)算機(jī)病毒論文)計(jì)算機(jī)網(wǎng)絡(luò)安全中病毒防護(hù)技術(shù)運(yùn)用
- GB/T 39866-2021建筑門(mén)窗附框技術(shù)要求
- OLT建設(shè)方案設(shè)計(jì)模版
- 11471勞動(dòng)爭(zhēng)議處理(第9章)
- 康復(fù)治療技術(shù)運(yùn)動(dòng)療法課件
- 自主探究式教學(xué)活動(dòng)模式結(jié)題報(bào)告
評(píng)論
0/150
提交評(píng)論