




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、摘要:本章將向讀者依次解釋中斷概念,解析Linux中的中斷實現(xiàn)機理以及Linux下中斷如何被使用。作為實例我們第一將向i386體系結構一章中打造的系統(tǒng)加入一個時鐘中斷;第二將為大家注解RTC中斷,希望通過這兩個實例可以幫助讀者掌握中斷相關的概念、實現(xiàn)和編程方法。中斷是什么中斷的漢語解釋是半中間發(fā)生阻隔、停頓或故障而斷開。那么,在計算機系統(tǒng)中,我們?yōu)槭裁葱枰白韪?、停頓和斷開”呢?舉個日常生活中的例子,比如說我正在廚房用煤氣燒一壺水,這樣就只能守在廚房里,苦苦等著水開如果水溢出來澆滅了煤氣,有可能就要發(fā)生一場災難了。等啊等啊,外邊突然傳來了驚奇的叫聲“怎么不關水龍頭?”于是我慚愧的發(fā)現(xiàn),剛才接水
2、之后只顧著抱怨這份無聊的差事,居然忘了這事,于是慌慌張張的沖向水管,三下兩下關了龍頭,聲音又傳到耳邊,“怎么干什么都是這么馬虎?”。伸伸舌頭,這件小事就這么過去了,我落寞的眼神又落在了水壺上。門外忽然又傳來了鏗鏘有力的歌聲,我最喜歡的古裝劇要開演了,真想奪門而出,然而,聽著水壺發(fā)出“咕嘟咕嘟”的聲音,我清楚:除非等到水開,否則沒有我享受人生的時候。這個場景跟中斷有什么關系呢?如果說我專心致志等待水開是一個過程的話,那么叫聲、電視里傳出的音樂不都讓這個過程“半中間發(fā)生阻隔、停頓或故障而斷開”了嗎?這不就是活生生的“中斷”嗎?在這個場景中,我是唯一具有處理能力的主體,不管是燒水、關龍頭還是看電視,
3、同一個時間點上我只能干一件事情。但是,在我專心致志干一件事情時,總有許多或緊迫或不緊迫的事情突然出現(xiàn)在面前,都需要去關注,有些還需要我停下手頭的工作馬上去處理。只有在處理完之后,方能回頭完成先前的任務, “把一壺水徹底燒開!”中斷機制不僅賦予了我處理意外情況的能力,如果我能充分發(fā)揮這個機制的妙用,就可以“同時”完成多個任務了。回到燒水的例子,實際上,無論我在不在廚房,煤氣灶總是會把水燒開的,我要做的,只不過是及時關掉煤氣灶而已,為了這么一個一秒鐘就能完成的動作,卻讓我死死的守候在廚房里,在10分鐘的時間里不停的看壺嘴是不是冒蒸汽,怎么說都不劃算。我決定安下心來看電視。當然,在有生之年,我都不希
4、望讓廚房成為火海,于是我上了鬧鐘,10分鐘以后它會發(fā)出“尖叫”,提醒我爐子上的水燒開了,那時我再去關煤氣也完全來得及。我用一個中斷信號鬧鈴換來了10分鐘的歡樂時光,心里不禁由衷的感嘆:中斷機制真是個好東西。正是由于中斷機制,我才能有條不紊的“同時”完成多個任務,中斷機制實質上幫助我提高了并發(fā)“處理”能力。它也能給計算機系統(tǒng)帶來同樣的好處:如果在鍵盤按下的時候會得到一個中斷信號,CPU就不必死守著等待鍵盤輸入了;如果硬盤讀寫完成后發(fā)送一個中斷信號,CPU就可以騰出手來集中精力“服務大眾”了無論是人類敲打鍵盤的指尖還是來回讀寫介質的磁頭,跟CPU的處理速度相比,都太慢了。沒有中斷機制,就像我們苦守
5、廚房一樣,計算機談不上有什么的并行處理能力。跟人相似,CPU也一樣要面對紛繁蕪雜的局面現(xiàn)實中的意外是無處不在的有可能是用戶等得不耐煩,猛敲鍵盤;有可能是運算中碰到了0除數(shù);還有可能網卡突然接收到了一個新的數(shù)據(jù)包。這些都需要CPU具體情況具體分析,要么馬上處理,要么暫緩響應,要么置之不理。無論如何應對,都需要 CPU暫?!笆诸^”的工作,拿出一種對策,只有在響應之后,方能回頭完成先前的使命,“把一壺水徹底燒開!”先讓我們感受一下中斷機制對并發(fā)處理帶來的幫助。讓我們用程序來探討一下燒水問題,如果沒有“中斷”(注意,我們這里只是模仿中斷的場景,實際上是用異步事件消息處理機制來展示中斷產生的效果。畢竟,
6、在用戶空間沒有辦法與實際中斷產生直接聯(lián)系,不過操作系統(tǒng)為用戶空間提供的異步事件機制,可以看作是模仿中斷的產物),設計如下:void StayInKitchen()bool WaterIsBoiled = false;while ( WaterIsBoiled != true ) bool VaporGavenOff = false; if (VaporGavenOff )
7、60; WaterIsBoiled = true;else WaterIsBoiled = false;/ 關煤氣爐printf(“Close gas oven.n”);/ 一切安定下來,終于可以看電視了,10分鐘的寶貴時間啊,逝者如斯夫watching_tv(
8、);return;可以看出,整個流程如同我們前面描述的一樣,所有工作要順序執(zhí)行,沒有辦法完成并發(fā)任務。如果用“中斷”,在開始燒水的時候設定一個10分鐘的“鬧鈴”,然后讓CPU去看電視(有點難度,具體實現(xiàn)不在我們關心的范圍之內,留給讀者自行解決吧:>)。等鬧鐘響的時候再去廚房關爐子。#include <sys/types.h>#include <unistd.h>#include <sys/stat.h>#include <signal.h>#include <stdio.h>/ 鬧鐘到時會執(zhí)行此程序void sig_alarm(
9、int signo) /關煤氣爐 printf(“Close gas oven.n”);void watching_tv() while(1) / 呵呵,優(yōu)哉游哉 int main()/ 點火后設置定時中斷 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í)行。第二段程序跟中斷也沒有太大的關系,實際上它只用了信號機制而已。但是,通過這兩個程序的對比,我們可以清楚地看到異
11、步的事件處理機制是如何提升并發(fā)處理能力的。Alarm定時器:alarm相當于系統(tǒng)中的一個定時器,如果我們調用 alarm(5),那么5秒鐘后就會“響起一個鬧鈴”(實際上靠信號機制實現(xiàn)的,我們這里不想深入細節(jié),如果你對此很感興趣,請參考Richard Stevens不朽著作Unix環(huán)境高級編程)。在鬧鈴響起的時候會發(fā)生什么呢?系統(tǒng)會執(zhí)行一個函數(shù),至于到底是什么函數(shù),系統(tǒng)允許程序自行決定。程序員編寫一個函數(shù),并調用signal對該函數(shù)進行注冊,這樣一旦定時到來,系統(tǒng)就會調用程序員提供的函數(shù)(CallBack函數(shù)?沒錯,不過在這里如何實現(xiàn)并不關鍵,我們就不引入新的概念和細節(jié)了)。上面的例子里我們提供
12、的函數(shù)是sig_alarm,所做的工作很簡單,打印“關閉煤氣灶”消息。上面的兩個例子很簡單,但很能說明問題,首先,它證明采用異步的消息處理機制可以提高系統(tǒng)的并發(fā)處理能力。更重要的是,它揭示了這種處理機制的模式。用戶根據(jù)需要設計處理程序,并可以將該程序和特定的外部事件綁定起來,在外部事件發(fā)生時系統(tǒng)自動調用處理程序,完成相關的工作。這種模式給系統(tǒng)帶來了統(tǒng)一的管理方法,也帶來無盡的功能擴展空間。計算機系統(tǒng)實現(xiàn)中斷機制是非常復雜的一件工作,再怎么說人都是高度智能化的生物,而計算機作為一個鐵疙瘩,沒有程序的教導就一事無成。而處理一個中斷過程,它受到的限制和需要學習的東西太多了。首先,計算機能夠接收的外部
13、信號形式非常有限。中斷是由外部的輸入引起的,可以說是一種刺激。在燒水的場景中,這些輸入是叫聲和電視的音樂,我們這里只以聲音為例。其實現(xiàn)實世界中能輸入人類CPU大腦的信號很多,圖像、氣味一樣能被我們接受,人的信息接口很完善。而計算機則不然,接受外部信號的途徑越多,設計實現(xiàn)就越復雜,代價就越高。因此個人計算機(PC)給所有的外部刺激只留了一種輸入方式特定格式的電信號,并對這種信號的格式、接入方法、響應方法、處理步驟都做了規(guī)約(具體內容本文后面部分會繼續(xù)詳解),這種信號就是中斷或中斷信號,而這一整套機制就是中斷機制。其次,計算機不懂得如何應對信號。人類的大腦可以自行處理外部輸入,我從來不用去擔心鬧鐘
14、響時會手足無措走進廚房關煤氣,這簡直是天經地義的事情,還用大腦想啊,小腿肚子都知道可惜計算機不行,沒有程序,它就紋絲不動。因此,必須有機制保證外部中斷信號到來后,有正確的程序在正確的時候被執(zhí)行。還有,計算機不懂得如何保持工作的持續(xù)性。我在看電視的時候如果去廚房關了煤氣,回來以后能繼續(xù)將電視進行到底,不受太大的影響。而計算機則不然,如果放下手頭的工作直接去處理“意外”的中斷,那么它就再也沒有辦法想起來曾經作過什么,做到什么程度了。自然也就沒有什么“重操舊業(yè)”的機會了。這樣的處理方式就不是并發(fā)執(zhí)行,而是東一榔頭,西一棒槌了。那么,通用的計算機系統(tǒng)是如何解決這些問題的呢?它是靠硬件和軟件配合來協(xié)同實
15、現(xiàn)中斷處理的全過程的。我們將通過Intel X86架構的實現(xiàn)來介紹這一過程。中斷流程處理CPU執(zhí)行完一條指令后,下一條指令的邏輯地址存放在cs和eip這對寄存器中。在執(zhí)行新指令前,控制單元會檢查在執(zhí)行前一條指令的過程中是否有中斷或異常發(fā)生。如果有,控制單元就會拋下指令,進入下面的流程:1. 確定與中斷或異常關聯(lián)的向量i (0£i£255)2. 尋找向量對應的處理程序3. 保存當前的“工作現(xiàn)場”,執(zhí)行中斷或異常的處理程序4
16、. 處理程序執(zhí)行完畢后,把控制權交還給控制單元5. 控制單元恢復現(xiàn)場,返回繼續(xù)執(zhí)行原程序整個流程如下圖所示:讓我們深入這個流程,看看都有什么問題需要面對。1、異常是什么概念?在處理器執(zhí)行到由于編程失誤而導致的錯誤指令(例如除數(shù)是0)的時候,或者在執(zhí)行期間出現(xiàn)特殊情況(例如缺頁),需要靠操作系統(tǒng)來處理的時候,處理器就會產生一個異常。對大部分處理器體系結構來說,處理異常和處理中斷的方式基本是相同的,x86架構的CPU也是如此。異常與中斷還是有些區(qū)別,異常的產生必須考慮與處理器時鐘的同步。實際上,
17、異常往往被稱為同步中斷。2、中斷向量是什么?中斷向量代表的是中斷源從某種程度上講,可以看作是中斷或異常的類型。中斷和異常的種類很多,比如說被0除是一種異常,缺頁又是一種異常,網卡會產生中斷,聲卡也會產生中斷,CPU如何區(qū)分它們呢?中斷向量的概念就是由此引出的,其實它就是一個被送通往CPU數(shù)據(jù)線的一個整數(shù)。CPU給每個 IRQ分配了一個類型號,通過這個整數(shù)CPU來識別不同類型的中斷。這里可能很多朋友會尋問為什么還要弄個中斷向量這么麻煩的東西?為什么不直接用 IRQ0IRQ15就完了?比如就讓IRQ0為0,IRQ1為1,這不是要簡單的多么?其實這里體現(xiàn)了模塊化設計規(guī)則,及節(jié)約規(guī)則。首先我們先談談節(jié)
18、約規(guī)則,所謂節(jié)約規(guī)則就是所使用的信號線數(shù)越少越好,這樣如果每個IRQ都獨立使用一根數(shù)據(jù)線,如IRQ0用0號線,IRQ1 用1號線這樣,16個IRQ就會用16根線,這顯然是一種浪費。那么也許馬上就有朋友會說:那么只用4根線不就行了嗎?(24=16)。這個問題,體現(xiàn)了模塊設計規(guī)則。我們在前面就說過中斷有很多類,可能是外部硬件觸發(fā),也可能是由軟件觸發(fā),然而對于CPU來說中斷就是中斷,只有一種,CPU不用管它到底是由外部硬件觸發(fā)的還是由運行的軟件本身觸發(fā)的,應為對于CPU來說,中斷處理的過程都是一樣的:中斷現(xiàn)行程序,轉到中斷服務程序處執(zhí)行,回到被中斷的程序繼續(xù)執(zhí)行。CPU總共可以處理256種中斷,而并
19、不知道,也不應當讓CPU知道這是硬件來的中斷還是軟件來的中斷,這樣,就可以使CPU的設計獨立于中斷控制器的設計,這樣CPU所需完成的工作就很單純了。CPU對于其它的模塊只提供了一種接口,這就是256個中斷處理向量,也稱為中斷號。由這些中斷控制器自行去使用這256個中斷號中的一個與CPU進行交互,比如,硬件中斷可以使用前128個號,軟件中斷使用后128個號,也可以軟件中斷使用前128個號,硬件中斷使用后128個號,這與CPU完全無關了,當你需要處理的時候,只需告訴CPU你用的是哪個中斷號就行,而不需告訴CPU你是來自哪兒的中斷。這樣也方便了以后的擴充,比如現(xiàn)在機器里又加了一片8259芯片,那么這
20、個芯片就可以使用空閑的中斷號,看哪一個空閑就使用哪一個,而不是必須要使用第0號,或第1號中斷號了。其實這相當于一種映射機制,把IRQ信號映射到不同的中斷號上,IRQ的排列或說編號是固定的,但通過改變映射機制,就可以讓IRQ映射到不同的中斷號,也可以說調用不同的中斷服務程序。3、什么是中斷服務程序?在響應一個特定中斷的時候,內核會執(zhí)行一個函數(shù),該函數(shù)叫做中斷處理程序(interrupt handler)或中斷服務程序(interrupt service routine(ISR))。產生中斷的每個設備都有相應的中斷處理程序。例如,由一個函數(shù)專門處理來自系統(tǒng)時鐘的中斷,而另外一個函數(shù)專門處理由鍵盤產
21、生的中斷。一般來說,中斷服務程序要負責與硬件進行交互,告訴該設備中斷已被接收。此外,還需要完成其他相關工作。比如說網絡設備的中斷服務程序除了要對硬件應答,還要把來自硬件的網絡數(shù)據(jù)包拷貝到內存,對其進行處理后再交給合適的協(xié)議?;驊贸绦?。每個中斷服務程序根據(jù)其要完成的任務,復雜程度各不相同。一般來說,一個設備的中斷服務程序是它設備驅動程序(device driver)的一部分設備驅動程序是用于對設備進行管理的內核代碼。4、隔離變化不知道您有沒有意識到,中斷處理前面這部分的設計是何等的簡單優(yōu)美。人是高度智能化的,能夠對遇到的各種意外情況做有針對性的處理,計算機相比就差距甚遠了,它只能根據(jù)預定的程序
22、進行操作。對于計算機來說,硬件支持的,只能是中斷這種電信號傳播的方式和CPU對這種信號的接收方法,而具體如何處理這個中斷,必須得靠操作系統(tǒng)實現(xiàn)。操作系統(tǒng)支持所有事先能夠預料到的中斷信號,理論上都不存在太大的挑戰(zhàn),但在操作系統(tǒng)安裝到計算機設備上以后,肯定會時常有新的外圍設備被加入系統(tǒng),這可能會帶來安裝系統(tǒng)時根本無法預料的“意外”中斷。如何支持這種擴展,是整個系統(tǒng)必須面對的。而硬件和軟件在這里的協(xié)作,給我們帶來了完美的答案。當新的設備引入新類型的中斷時,CPU和操作系統(tǒng)不用關注如何處理它。CPU只負責接收中斷信號,并引用中斷服務程序;而操作系統(tǒng)提供默認的中斷服務一般來說就是不理會這個信號,返回就可
23、以了并負責提供接口,讓用戶通過該接口注冊根據(jù)設備具體功能而編制的中斷服務程序。如果用戶注冊了對應于一個中斷的服務程序,那么CPU就會在該中斷到來時調用用戶注冊的服務程序。這樣,在中斷來臨時系統(tǒng)需要如何操作硬件、如何實現(xiàn)硬件功能這部分工作就完全獨立于CPU架構和操作系統(tǒng)的設計了。而當你需要加入新設備的時候,只需要告訴操作系統(tǒng)該設備占用的中斷號、按照操作系統(tǒng)要求的接口格式撰寫中斷服務程序,用操作系統(tǒng)提供的函數(shù)注冊該服務程序,設備的中斷就被系統(tǒng)支持了。中斷和對中斷的處理被解除了耦合。這樣,無論是你在需要加入新的中斷時,還是在你需要改變現(xiàn)有中斷的服務程序時、又或是取消對某個中斷支持的時候,CPU架構和
24、操作系統(tǒng)都無需作改變。5、保存當前工作“現(xiàn)場”在中斷處理完畢后,計算機一般來說還要回頭處理原先手頭正做的工作。這給中斷的概念帶來些額外的“內涵”1?!盎仡^”不是指從頭再來重新做,而是要接著剛才的進度繼續(xù)做。這就需要在處理中斷信號之前保留工作“現(xiàn)場”。“現(xiàn)場”這個詞比較晦澀,其實就是指一個信息集,它能反映某個時間點上任務的狀態(tài),并能保證按照這些信息就能恢復任務到該狀態(tài),繼續(xù)執(zhí)行下去。再直白一點,現(xiàn)場不過就是一組寄存器值。而如何保護現(xiàn)場和恢復場景是中斷機制需要考慮的重點之一。每個中斷處理都要經歷這個保存和恢復過程,我們可以抽象出其中的步驟:1. &
25、#160; 保存現(xiàn)場2. 執(zhí)行具體的中斷服務程序3. 從中斷服務返回4. 恢復現(xiàn)場上面說過了,“現(xiàn)場”看似在不斷變化,沒有哪個瞬間相同。但實際上組成現(xiàn)場的要素卻不會有任何改變。也就是說,這要我們保存了相關的寄存器狀態(tài),現(xiàn)場就能保存下來。而恢復“現(xiàn)場”就是重新載入這些寄存器。換句話說,對于任何一個中斷,保護現(xiàn)場和恢復現(xiàn)場所作的都是完全相同的操作。既然操作相同,
26、實現(xiàn)操作的過程和代碼就相同。減少代碼的冗余是模塊化設計的基本準則,實在沒有道理讓所有的中斷服務程序都重復的實現(xiàn)這樣的功能,應該將它作為一種基本的結構由底層的操作系統(tǒng)或硬件完成。而對中斷的處理過程需要迅速完成,因此,Intel CPU的控制器就承擔了這個任務,非但如此,上面的所有步驟次序都被固化下來,由控制器驅動完成。保存現(xiàn)場和恢復現(xiàn)場都由硬件自動完成,大大減輕了操作系統(tǒng)和設備驅動程序的負擔。6、硬件對中斷支持的細節(jié)下面的部分,本來應該介紹8259、中斷控制器編程、中斷描述符表等內容,可是我看到了瀟寒寫的“保護模式下的8259A芯片編程及中斷處理探究”,前人之述備矣,讀者直接讀它好了。Linux
27、下的中斷在Linux中,中斷處理程序看起來就是普普通通的C函數(shù)。只不過這些函數(shù)必須按照特定的類型聲明,以便內核能夠以標準的方式傳遞處理程序的信息,在其他方面,它們與一般的函數(shù)看起來別無二致。中斷處理程序與其它內核函數(shù)的真正區(qū)別在于,中斷處理程序是被內核調用來響應中斷的,而它們運行于我們稱之為中斷上下文的特殊上下文中。關于中斷上下文,我們將在后面討論。中斷可能隨時發(fā)生,因此中斷處理程序也就隨時可能執(zhí)行。所以必須保證中斷處理程序能夠快速執(zhí)行,這樣才能保證盡可能快地恢復被中斷代碼的執(zhí)行。因此,盡管對硬件而言,迅速對其中斷進行服務非常重要。但對系統(tǒng)的其它部分而言,讓中斷處理程序在盡可能短的時間內完成執(zhí)
28、行也同樣重要。即使最精簡版的中斷服務程序,它也要與硬件進行交互,告訴該設備中斷已被接收。但通常我們不能像這樣給中斷服務程序隨意減負,相反,我們要靠它完成大量的其它工作。作為一個例子,我們可以考慮一下網絡設備的中斷處理程序面臨的挑戰(zhàn)。該處理程序除了要對硬件應答,還要把來自硬件的網絡數(shù)據(jù)包拷貝到內存,對其進行處理后再交給合適的協(xié)議?;驊贸绦颉o@而易見,這種運動量不會太小?,F(xiàn)在我們來分析一下Linux操作系統(tǒng)為了支持中斷機制,具體都需要做些什么工作。首先,操作系統(tǒng)必須保證新的中斷能夠被支持。計算機系統(tǒng)硬件留給外設的是一個統(tǒng)一的中斷信號接口。它固化了中斷信號的接入和傳遞方法,拿PC機來說,中斷機制是
29、靠兩塊8259和CPU協(xié)作實現(xiàn)的。外設要做的只是把中斷信號發(fā)送到8259的某個特定引腳上,這樣8259就會為此中斷分配一個標識 也就是通常所說的中斷向量,通過中斷向量,CPU就能夠在以中斷向量為索引的表中斷向量表里找到中斷服務程序,由它決定具體如何處理中斷。這是硬件規(guī)定的機制,軟件只能無條件服從。因此,操作系統(tǒng)對新中斷的支持,說簡單點,就是維護中斷向量表。新的外圍設備加入系統(tǒng),首先得明確自己的中斷向量號是多少,還得提供自身中斷的服務程序,然后利用Linux的內核調用界面,把中斷向量號、中斷服務程序這對信息填寫到中斷向量表中去。這樣CPU在接收到中斷信號時就會自動調用中斷服務程序了。這種注冊操作
30、一般是由設備驅動程序完成的。其次,操作系統(tǒng)必須提供給程序員簡單可靠的編程界面來支持中斷。中斷的基本流程前面已經講了,它會打斷當前正在進行的工作去執(zhí)行中斷服務程序,然后再回到先前的任務繼續(xù)執(zhí)行。這中間有大量需要解決問題:如何保護現(xiàn)場、嵌套中斷如何處理等等,操作系統(tǒng)要一一化解。程序員,即使是驅動程序的開發(fā)人員,在寫中斷服務程序的時候也很少需要對被打斷的進程心存憐憫。(當然,出于提高系統(tǒng)效率的考慮,編寫驅動程序要比編寫用戶級程序多一些條條框框,誰讓我們頂著系統(tǒng)程序員的光環(huán)呢?)操作系統(tǒng)為我們屏蔽了這些與中斷相關硬件機制打交道的細節(jié),提供了一套精簡的接口,讓我們用極為簡單的方式實現(xiàn)對實際中斷的支持,L
31、inux是怎么完美的做到這一點的呢?CPU對中斷處理的流程我們首先必須了解CPU在接收到中斷信號時會做什么。沒辦法,操作系統(tǒng)必須了解硬件的機制,不配合硬件就寸步難行?,F(xiàn)在我們假定內核已被初始化,CPU在保護模式下運行。CPU執(zhí)行完一條指令后,下一條指令的邏輯地址存放在cs和eip這對寄存器中。在執(zhí)行新指令前,控制單元會檢查在執(zhí)行前一條指令的過程中是否有中斷或異常發(fā)生。如果有,控制單元就會拋下指令,進入下面的流程:1.確定與中斷或異常關聯(lián)的向量i (0£i£255)。2.籍由idtr寄存器從IDT表中讀取第i項(在下面的描述中,我們假定該IDT表項中包含的是一個中斷門或一個陷
32、阱門)。3.從gdtr寄存器獲得GDT的基地址,并在GDT表中查找,以讀取IDT表項中的選擇符所標識的段描述符。這個描述符指定中斷或異常處理程序所在段的基地址。4.確信中斷是由授權的(中斷)發(fā)生源發(fā)出的。首先將當前特權級CPL(存放在cs寄存器的低兩位)與段描述符(存放在GDT中)的描述符特權級DPL比較,如果CPL小于DPL,就產生一個“通用保護”異常,因為中斷處理程序的特權不能低于引起中斷的程序的特權。對于編程異常,則做進一步的安全檢查:比較CPL與處于IDT中的門描述符的DPL,如果DPL小于CPL,就產生一個“通用保護”異常。這最后一個檢查可以避免用戶應用程序訪問特殊的陷阱門或中斷門。
33、5.檢查是否發(fā)生了特權級的變化,也就是說, CPL是否不同于所選擇的段描述符的DPL。如果是,控制單元必須開始使用與新的特權級相關的棧。通過執(zhí)行以下步驟來做到這點: a.讀tr寄存器,以訪問運行進程的TSS段。b.用與新特權級相關的棧段和棧指針的正確值裝載ss和esp寄存器。這些值可以在TSS中找到(參見第三章的“任務狀態(tài)段”一節(jié))。c.在新的棧中保存ss和esp以前的值,這些值定義了與舊特權級相關的棧的邏輯地址。 6.如果故障已發(fā)生,用引起異常的指令地址裝載cs和eip寄存器,從而使得這條指令能再次被執(zhí)行。 7.在
34、棧中保存eflag、cs及eip的內容。8.如果異常產生了一個硬錯誤碼,則將它保存在棧中。9.裝載cs和eip寄存器,其值分別是IDT表中第i項門描述符的段選擇符和偏移量域。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址??刂茊卧鶊?zhí)行的最后一步就是跳轉到中斷或者異常處理程序。換句話說,處理完中斷信號后, 控制單元所執(zhí)行的指令就是被選中處理程序的第一條指令。中斷或異常被處理完后,相應的處理程序必須0x20/0x21/0xa0/0xa1產生一條iret指令,把控制權轉交給被中斷的進程,這將迫使控制單元:1.用保存在棧中的值裝載cs、eip、或eflag寄存器。如果一個硬錯誤碼曾被壓入棧中
35、,并且在eip內容的上面,那么,執(zhí)行iret指令前必須先彈出這個硬錯誤碼。2.檢查處理程序的CPL是否等于cs中最低兩位的值(這意味著被中斷的進程與處理程序運行在同一特權級)。如果是,iret終止執(zhí)行;否則,轉入下一步。3. 從棧中裝載ss和esp寄存器,因此,返回到與舊特權級相關的棧。4. 檢查ds、es、fs及gs段寄存器的內容,如果其中一個寄存器包含的選擇符是一個段描述符,并且其DPL值小于CPL,那么,清相應的段寄存器??刂茊卧@么做是為了禁止用戶態(tài)的程序(CPL=3)利用內核以前所用的段寄存器(DPL=0)。如果不清這些寄存器,懷有
36、惡意的用戶程序就可能利用它們來訪問內核地址空間。再次,操作系統(tǒng)必須保證中斷信息能夠高效可靠的傳遞實例一為自己的操作系統(tǒng)中加入中斷中斷機制的實現(xiàn)在這個部分,我將為大家詳細介紹SagaLinux_irq中是如何處理中斷的。為了更好的演示軟硬件交互實現(xiàn)中斷機制的過程,我將在前期實現(xiàn)的SagaLinux上加入對一個新中斷定時中斷的支持。首先,讓我介紹一下SagaLinux_irq中涉及中斷的各部分代碼。這些代碼主要包含在kernel目錄下,包括idt.c,irq.c,i8259.s,boot目錄下的setup.s也和中斷相關,下面將對他們進行討論。1、boot/setup.ssetup.s中相關于中斷
37、的部分主要集中在pic_init小結,該部分完成了對中斷控制器的初始化。對8259A的編程是通過向其相應的端口發(fā)送一系列的ICW(初始化命令字)完成的。總共需要發(fā)送四個ICW,它們都分別有自己獨特的格式,而且必須按次序發(fā)送,并且必須發(fā)送到相應的端口,具體細節(jié)請查閱相關資料。pic_init: cli mov al, 0x11 initialize PICs; 給中斷寄存器編程; 發(fā)送ICW1:使用ICW4,級聯(lián)工作
38、 out 0x20, al 8259_MASTER out 0xA0, al 8259_SLAVE ; 發(fā)送 ICW2,中斷起始號從 0x20 開始(第一片)及 0x28開始(第二片) 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架構下 mov al, 0x01 ; 8086 Mode out 0x21, al
41、; out 0xA1, al; 設置中斷屏蔽位 OCW1 ,屏蔽所有中斷請求 mov al, 0xFF ; mask all out 0x21, al out 0xA1, al sti 2、kernel/irq.c irq.c提供了三個函數(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用來開啟和關閉右參數(shù)irq指定的中斷,這兩個函數(shù)直接對8259的寄存器進行操作,因此irq 對應的是實實在在的中斷號,比如說X86下時鐘中斷一般為0號中斷,那么啟動時鐘中斷就需要調用enable_irq(1),而鍵盤一般占用2號中斷,那么關閉鍵盤中斷就需要調用disable_irq(2)。irq對應的不是中斷向量。equest_irq用來
43、將中斷號和中斷服務程序綁定起來,綁定完成后,命令8259開始接受中斷請求。下面是request_irq的實現(xiàn)代碼:void request_irq(int irq, void (*handler)() irq_handlerirq = handler; enable_irq(irq);其中irq_handler是一個擁有16個元素的數(shù)組,數(shù)組項是指向函數(shù)的指針,每個指針可以指向一個中斷服務程序。 irq_handlerirq = handler 就是一個給數(shù)組項賦值的過程,其中隱藏了中斷號向中斷向量映射的過程,在初始化IDT表的部分,我會介紹相關內容
44、。3、kernel/i8259.s2i8259.c負責對外部中斷的支持。我們已經討論過了,8259芯片負責接收外部設備如定時器、鍵盤、聲卡等的中斷,兩塊8259共支持16個中斷。我們也曾討論過,在編寫操作系統(tǒng)的時候,我們不可能知道每個中斷到底對應的是哪個中斷服務程序。實際上,通常在這個時候,中斷服務程序壓根還沒有被編寫出來??墒?,X86體系規(guī)定,在初始化中斷向量表的時候,必須提供每個向量對應的服務程序的偏移地址,以便CPU在接收到中斷時調用相應的服務程序,這該如何是好呢?巧婦難為無米之炊,此時此刻,我們只有創(chuàng)造所有中斷對應的服務程序,才能完成初始化IDT的工作,于是我們制造出16個函數(shù)_irq
45、0到 _irq15,在注冊中斷服務程序的時候,我們就把它們填寫到IDT的描述符中去。(在SagaLinux中當前的實現(xiàn)里,我并沒有填寫完整的IDT 表,為了讓讀者看得較為清楚,我只加入了定時器和鍵盤對應的_irq和_irq1。但這樣一來就帶來一個惡果,讀者會發(fā)現(xiàn)在加入新的中斷支持時,需要改動idt.c中的trap_init函數(shù),用set_int_gate對新中斷進行支持。完全背離了我們強調的分隔變化的原則。實際上,只要我們在這里填寫完整,并提供一個缺省的中斷服務函數(shù)就可以解決這個問題。我再強調一遍,這不是設計問題,只是為了便于讀者觀察而做的簡化。)可是,這16個函數(shù)怎么能對未知的中斷進行有針對
46、性的個性化服務呢?當然不能,這16個函數(shù)只是一個接口,我們可以在其中留下后門,當新的中斷需要被系統(tǒng)支持時,它實際的中斷服務程序就能被這些函數(shù)調用。具體調用關系請參考圖2如圖2所示,_irq0到_irq15會被填充到IDT從32到47(之所以映射到這個區(qū)間是為了模仿Linux的做法,其實這部分的整個實現(xiàn)都是在模仿Linux)這16個條目的中斷描述符中去,這樣中斷到來的時候就會調用相應的_irq函數(shù)。所有irq函數(shù)所作的工作基本相同,把中斷號壓入棧中,再調用do_irq函數(shù);它們之間唯一區(qū)別的地方就在于不同的irq函數(shù)壓入的中斷號不同。do_irq首先會從棧中取出中斷號,然后根據(jù)中斷號計算該中斷對
47、應的中斷服務程序在irq_handler數(shù)組中的位置,并跳到該位置上去執(zhí)行相應的服務程序。還記得irq.c中介紹的request_irq函數(shù)嗎,該函數(shù)綁定中斷號和中斷服務程序的實現(xiàn),其實就是把指向中斷服務程序的指針填寫到中斷號對應的irq_handler數(shù)組中去?,F(xiàn)在,你應該明白我們是怎樣把一個中斷服務程序加入到SagaLinux中的了吧通過一個中間層,我們可以做任何事情。在上圖的實現(xiàn)中,IDT表格中墨綠色的部分外部中斷對應的部分可以浮動,也就是說,我們可以任意選擇映射的起始位置,比如說,我們讓_irq0映射到IDT的第128項,只要后續(xù)的映射保持連續(xù)就可以了。4、kernel/idt.cid
48、t.c當然是用來初始化IDT表的了。在i8259.s中我們介紹了操作系統(tǒng)是如何支持中斷服務程序的添加的,但是,有兩個部分的內容沒有涉及:一是如何把_irq函數(shù)填寫到IDT表中,另外一個就是中斷支持了,那異常怎么支持呢?idt.c負責解決這兩方面的問題。idt.c提供了trap_init函數(shù)來填充IDT表。 void trap_init() int i; idtr_t idtr; / 填入系統(tǒng)默認的異常,共17個
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個異常是intel保留的,最好不要占用 for (i = 17;i<32;i+) set_trap_gate(i, (unsigned int)&reserved); / 我們只在I
53、DT中填入定時器和鍵盤要用到的兩個中斷 set_int_gate(32, (unsigned int)&_irq0); set_int_gate(33, (unsigned int)&_irq1);/ 一共有34個中斷和異常需要支持 idtr.limit = 34*8; idtr.lowerbase = 0x0000; idtr.higherbase = 0x0000; cli();/ 載入IDT表,新的中斷可以用了 _as
54、m_ _volatile_ ("lidt (%0)" :"p" (&idtr); sti();首先我們來看看set_trap_gate和set_int_gate函數(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ù)中斷向量號計算出應該把指向中斷或異常服務程序的指針放在什么IDT表中的什么位置,然后把該指針和中斷描述符設置好就行了。同樣,中斷描述符的格式請查閱有關資料。現(xiàn)在,來關注一下set_trap_gate的參數(shù),又是指向函數(shù)的指針。在這里,我們看到每個這樣的指針指向一個異常處理函數(shù),如
58、divide_error、debug等:void divide_error(void) sleep("divide error");void debug(void) sleep("debug"); 每個函數(shù)都調用了sleep,那么sleep是有何作用?是不是像do_irq一樣調用具體異常的中斷服務函數(shù)呢?/ Nooooo . just sleep :)void sleep(char* message) printk("%s",message);
59、 while(1); 看樣子不是,這個函數(shù)就是休眠而已!實際上,我們這里進行了簡化,對于Intel定義好的前17個內部異常,目前SagaLinux還不能做有針對性的處理,因此我們直接讓系統(tǒng)無限制地進入休眠跟死機區(qū)別不大。因此,當然也不用擔心恢復“現(xiàn)場”的問題了,不用考慮棧的影響,所以直接用C函數(shù)實現(xiàn)。此外,由于這17個異常如何處理在這個時候我們已經確定下來了sleep,既然沒有什么變化,我們也就不用耗盡心思的考慮去如何支持變化了,直接把函數(shù)硬編碼就可以了。Intel規(guī)定中斷描述符表的第17-31項保留,為硬件將來可能的擴展用,因此我們這里將它閑置
60、起來。void reserved(void)sleep("reserved");下面的部分是對外部中斷的初始化,放在trap_init中是否有些名不正言不順呢?確實如此,這個版本暫時把它放在這里,以后重構的時候再調整吧。注意,這個部分解釋了我們是如何把中斷服務程序放置到IDT中的。此外,可以看出,我們使用手工方式對中斷向量號進行了映射,_irq0對應32 號中斷,而_irq1對應33號中斷。能不能映射成別的向量呢?當然可以,可是別忘了修改setup.s中的pic_init部分,要知道,我們初始化 8259的時候定義好了外部中斷對應的向量,如果你希望從8259發(fā)來的中斷信號能
61、正確的觸發(fā)相應的中斷服務程序,當然要把所有的接收處理鏈條上的每個映射關系都改過來。我們只填充了34個表項,每個表項8字節(jié)長,因此我們把IDT表的長度上限設為34x8,把IDT表放置在邏輯地址起始的地方(如果我們沒有啟用分頁機制,那么就是在線性空間起始的地方,也就是物理地址的0位置處)。最后,調用ldtr指令啟用新的中斷處理機制,SagaLinux的初步中斷支持機制就完成了。下面,我們以定時器(timer)設備為例,展示如何通過SagaLinux目前提供的中斷服務程序接口來支持設備的中斷。IBM PC兼容機包含了一種時間測量設備,叫做可編程間隔定時器(PIT)。PIT的作用類似于鬧鐘,在設定的時
62、間點到來的時候發(fā)出中斷信號。這種中斷叫做定時中斷(timer interrupt)。在Linux操作系統(tǒng)中,就是它來通知內核又一個時間片斷過去了。與鬧鐘不同,PIT以某一固定的頻率(編程控制)不停地發(fā)出中斷。每個IBM PC兼容機至少都會包含一個PIT,一般來說,它就是一個使用0x400x43 I/O端口的8254CMOS芯片。 SagaLinux目前的版本還不支持進程調度,因此定時中斷的作用還不明顯,不過,作為一個做常見的中斷源,我們可以讓它每隔一定時間發(fā)送一個中斷信號,而我們在定時中斷的中斷服務程序中計算流逝過去的時間數(shù),然后打印出結果,充分體現(xiàn)中斷的效果。我們在
63、kernel目錄下編寫了timer.c文件,也在include目錄下加入了相應的timer.h,下面就是具體的實現(xiàn)。/ 流逝的時間static volatile ulong_t counter; / 中斷服務程序void timer_handler() / 中斷每10毫秒一次 counter += 10;/ 初始化硬件和技術器,啟用中斷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); / 申請0號中斷,TIMER定義為0 request_irq(TIMER, timer_handler);/ 返回流
65、逝過去的時間ulong_t uptime() return counter;timer_init函數(shù)是核心函數(shù),負責硬件的初始化和中斷的申請,對8254的初始化就不多做糾纏了,請查閱有關資料。我們可以看到,申請中斷確實跟預想中的一樣容易,調用request_irq,一行語句就完成了中斷的注冊。而中斷服務程序非常簡單,由于把8254設置為每10毫秒發(fā)送一次中斷,因此每次中斷到來時都在服務程序中對counter加10,所以counter表示的就是流逝的時間。在kernel.c中,我們調用timer_init進行初始化,此時定時中斷就被激活了,如果我們的中斷機制運轉順利,那么流
66、逝時間會不斷增加。為了顯示出這樣的結果,我們編寫一個循環(huán)不斷的調uptime函數(shù),并把返回的結果打印在屏幕上。如果打印出的數(shù)值越來越大,那就說明我們的中斷機制確確實實發(fā)揮了作用,定時中斷被驅動起來了。 在kernel.c中: / 初始化 int i = 0;
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 差旅服務定制化解決方案合作協(xié)議
- 車輛抵押反擔保借款合同解除后的后續(xù)處理
- 建筑行業(yè)常年法律顧問專業(yè)服務協(xié)議
- 土地利用規(guī)劃保密及實施合同
- 倉儲空間轉讓與倉儲管理軟件應用合同
- 釣魚艇轉讓協(xié)議書范本
- 木房合同協(xié)議書范本
- 生物質發(fā)電項目安裝與運營管理合同
- 旅游企業(yè)特色旅游辦公用品采購及服務協(xié)議
- 城市綜合體停車場場地租賃及收費管理制度合同
- 2025年中小學暑假安全教育主題家長會 課件
- 顱內血腫護理查房
- 門診急救室管理制度
- 2025年沈陽水務集團有限公司-企業(yè)報告(代理機構版)
- 近視管理白皮書(2025)專家共識-
- 2024年深圳市深汕特別合作區(qū)農村工作者招聘真題
- 數(shù)字化藝術-終結性考核-國開(SC)-參考資料
- 2024年貴州省糧食儲備集團有限公司招聘考試真題
- 2025山西晉城市國有資本投資運營有限公司部分子公司招聘11人筆試參考題庫附帶答案詳解
- 2025盤錦市興隆臺區(qū)輔警考試試卷真題
- 壓縮空氣儲能系統(tǒng)透平膨脹機流動特性與損失優(yōu)化研究
評論
0/150
提交評論