Linux通過(guò)使用多種不同硬件來(lái)執(zhí)行許多不同任務(wù)。包括驅(qū)動(dòng)顯示器的視頻設(shè)備、驅(qū)動(dòng)硬盤的IDE設(shè)備等。 我們可以同步驅(qū)動(dòng)這些設(shè)備,即我們可以發(fā)送一個(gè)請(qǐng)求執(zhí)行一組操作(比如說(shuō)將一塊內(nèi)存數(shù)據(jù)寫入到磁盤)然后等待到執(zhí)行完畢。這種方式雖然可以工作,但是效率很低,因?yàn)椴僮飨到y(tǒng)必須等待每個(gè)操作的完成,所以操作系統(tǒng)將花費(fèi)大量時(shí)間在“忙等待”上。更為有效的方式是執(zhí)行請(qǐng)求,然后轉(zhuǎn)去執(zhí)行其它任務(wù)。當(dāng)設(shè)備完成請(qǐng)求時(shí)再通過(guò)中斷通知操作系統(tǒng)。這樣系統(tǒng)中可以同時(shí)存在多個(gè)未完成的任務(wù)。
不管CPU在作什么工作,為了讓設(shè)備產(chǎn)生中斷必須提供一些必要的硬件支持。幾乎所有的通用處理器如 Alpha AXP都使用近似的方法。CPU的一些物理引腳被設(shè)計(jì)成可以改變電壓(如從+5V變成-5V)從而引起CPU停止當(dāng)前工作并開始執(zhí)行處理中斷的特殊代碼:中斷處理程序。這些引腳之一被連接到一個(gè)周期性時(shí)鐘上并每隔千分之一秒就接收一次中斷,其它引腳則可連接到系統(tǒng)中其它設(shè)備如SCSI控制器上。
系統(tǒng)常使用中斷控制器來(lái)在向CPU中斷引腳發(fā)送信號(hào)之前將設(shè)備中斷進(jìn)行分組。這樣可以節(jié)省CPU上中斷引腳個(gè)數(shù),同時(shí)增加了系統(tǒng)設(shè)計(jì)的靈活性。此中斷控制器通過(guò)屏蔽與狀態(tài)寄存器來(lái)控制中斷。通過(guò)設(shè)置屏蔽寄存器中的某些位可以使能或者關(guān)閉中斷,讀取狀態(tài)寄存器可得到系統(tǒng)當(dāng)前處于活動(dòng)狀態(tài)的中斷。
系統(tǒng)中有些中斷是通過(guò)硬連線連接的,如實(shí)時(shí)時(shí)鐘的周期性定時(shí)器可能被固定連接到中斷控制器的引腳3上。而其它連接到控制器的引腳只能由插到特定ISA或PCI槽中的控制卡來(lái)決定。例如中斷控制器中的引腳4可能被連接到PCI槽號(hào)0,但可能某天此槽中插入一塊以太網(wǎng)卡而過(guò)幾天又會(huì)換成SCSI控制器??傊總€(gè)系統(tǒng)都有其自身的中斷路由機(jī)制,同時(shí)操作系統(tǒng)還應(yīng)該能靈活處理這些情況。
多數(shù)現(xiàn)代通用微處理器使用近似的方法來(lái)處理中斷。硬件中斷發(fā)生時(shí),CPU將停止執(zhí)行當(dāng)前指令并將跳轉(zhuǎn)到內(nèi)存中包含中斷處理代碼或中斷處理代碼指令分支的位置繼續(xù)執(zhí)行。這些代碼在一種特殊CPU模式: 中斷模式下執(zhí)行。通常在此模式下不會(huì)有其它中斷發(fā)生。但是也有例外;有些CPU將中斷的優(yōu)先級(jí)進(jìn)行分類,此時(shí)更高優(yōu)先級(jí)的中斷還可能發(fā)生。這樣意味著必須認(rèn)真編寫第一級(jí)中斷處理代碼,同時(shí)中斷處理過(guò)程應(yīng)該擁有其自身的堆棧,以便存儲(chǔ)轉(zhuǎn)到中斷處理過(guò)程前的CPU執(zhí)行狀態(tài)(所有CPU的普通寄存器和上下文)。一些CPU具有一組特殊的寄存器-它們僅存在于中斷模式中,在中斷模式下可以使用這些寄存器來(lái)保存執(zhí)行所需要的執(zhí)行上下文。
當(dāng)中斷處理完畢后CPU狀態(tài)將被重儲(chǔ),同時(shí)中斷也將被釋放。CPU將繼續(xù)做那些中斷發(fā)生前要做的工作。中斷處理代碼越精煉越好,這樣將減少操作系統(tǒng)阻塞在中斷上的時(shí)間與頻率。
系統(tǒng)設(shè)計(jì)者可以自由選擇中斷結(jié)構(gòu),一般的IBM PC兼容將使用Intel 82C59A-2 CMOS可編程中斷控制器或其派生者。這種控制器在PC誕生之前便已經(jīng)產(chǎn)生,它的可編程性體現(xiàn)在那些位于眾所周知ISA內(nèi)存位置中的寄存器上。非Intel系統(tǒng)如基于Alpha AXP的PC不受這些體系結(jié)構(gòu)限制,它們經(jīng)常使用各種不同的中斷控制器。
圖7.1給出了兩個(gè)級(jí)連的8位控制器,每個(gè)控制器都有一個(gè)屏蔽與中斷狀態(tài)寄存器:PIC1和PIC2。這兩個(gè)屏蔽寄存器分別位于ISA I/O空間0x21和0xA1處,狀態(tài)寄存器則位于0x20和0xA0。對(duì)此屏蔽寄存器某個(gè)特定位置位將使能某一中斷,寫入0則屏蔽它。但是不幸的是中斷屏蔽寄存器是只寫的,所以你無(wú)法讀取你寫入的值。這也意味著Linux必須保存一份對(duì)屏蔽寄存器寫入值的局部拷貝。一般在中斷使能和屏蔽例程中修改這些保存值,同時(shí)每次將這些全屏蔽碼寫入寄存器。
當(dāng)有中斷產(chǎn)生時(shí),中斷處理代碼將讀取這兩個(gè)中斷狀態(tài)寄存器(ISR)。它將0x20中的ISR看成一個(gè)16位中斷寄存器的低8位而將0xA0中的ISR看成其高8位。這樣0xA0中ISR第1位上的中斷將被視作系統(tǒng)中斷9。PIC1 上的第二位由于被用來(lái)級(jí)連PIC2所以不能作其它用處,PIC2上的任何中斷將導(dǎo)致PIC1的第二位被置位。
每個(gè)設(shè)備驅(qū)動(dòng)將調(diào)用這些過(guò)程來(lái)注冊(cè)其中斷處理例程地址。
有些中斷由于傳統(tǒng)的PC體系結(jié)構(gòu)被固定下來(lái),所以驅(qū)動(dòng)僅需要在其初始化時(shí)請(qǐng)求它的中斷。軟盤設(shè)備驅(qū)動(dòng)正是使用的這種方式;它的中斷號(hào)總為6。有時(shí)設(shè)備驅(qū)動(dòng)也可能不知道設(shè)備使用的中斷號(hào)。對(duì)PCI設(shè)備驅(qū)動(dòng)來(lái)說(shuō)這不是什么大問(wèn)題,它們總是可以知道其中斷號(hào)。但對(duì)于ISA設(shè)備驅(qū)動(dòng)則沒(méi)有取得中斷號(hào)的方便方式。Linux通過(guò)讓設(shè)備驅(qū)動(dòng)檢測(cè)它們的中斷號(hào)來(lái)解決這個(gè)問(wèn)題。
設(shè)備驅(qū)動(dòng)首先迫使設(shè)備引起一個(gè)中斷。系統(tǒng)中所有未被分配的中斷都被使能。此時(shí)設(shè)備引發(fā)的中斷可以通過(guò)可編程中斷控制器來(lái)發(fā)送出去。Linux再讀取中斷狀態(tài)寄存器并將其內(nèi)容返回給設(shè)備驅(qū)動(dòng)。非0結(jié)果則表示在此次檢測(cè)中有一個(gè)或多個(gè)中斷發(fā)生。設(shè)備驅(qū)動(dòng)然后將關(guān)閉檢測(cè)并將所有未分配中斷屏蔽掉。
如果ISA設(shè)備驅(qū)動(dòng)成功找到了設(shè)備的IRQ號(hào),就可以象平常一樣請(qǐng)求對(duì)設(shè)備的控制。
基于PCI系統(tǒng)比基于ISA系統(tǒng)有更多的動(dòng)態(tài)性。ISA設(shè)備使用的中斷引腳通常是通過(guò)硬件設(shè)備上的跳線來(lái)設(shè)置并固定在設(shè)備驅(qū)動(dòng)中。PCI設(shè)備在系統(tǒng)啟動(dòng)與初始化PCI時(shí)由PCI BIOS或PCI子系統(tǒng)來(lái)分配中斷。每個(gè)PCI設(shè)備可以使用A,B,C或D之中的任意中斷。這個(gè)中斷在設(shè)備建立時(shí)確定且通常多數(shù)設(shè)備的缺省中斷為 A。PCI槽中的PCI中斷連線A,B,C和D被正確路由到中斷控制器中。所以PCI槽4上的引腳A可能被路由到中斷控制器上的引腳6,PCI槽7上的引腳B被路由到中斷控制器上的引腳7等等。
如何路由PCI中斷完全取決于特定的系統(tǒng),一般設(shè)置代碼能理解PCI中斷路由拓?fù)洹T诨贗ntel的PC上由系統(tǒng)BIOS代碼在啟動(dòng)時(shí)作這些設(shè)置而在不帶BIOS(如Alpha AXP)系統(tǒng)中由Linux核心來(lái)完成這個(gè)任務(wù)。
PCI設(shè)置代碼將每個(gè)設(shè)備對(duì)應(yīng)的中斷控制器的引腳號(hào)寫入PCI配置頭中。通過(guò)得到PCI中斷路由拓?fù)浼霸O(shè)備的PCI槽號(hào)和PCI中斷引腳設(shè)置代碼可以確定其對(duì)應(yīng)的中斷引腳(或IRQ)號(hào)。設(shè)備使用的中斷引腳被保存在此設(shè)備的PCI配置頭中為此目的保留的中斷連線域中。當(dāng)運(yùn)行設(shè)備驅(qū)動(dòng)時(shí)這些信息被讀出并用來(lái)控制來(lái)自Linux核心的中斷請(qǐng)求。
系統(tǒng)中可能存在許多PCI中斷源,比如在使用PCI-PCI橋接器時(shí)。這些中斷源的個(gè)數(shù)可能將超出系統(tǒng)可編程中斷控制器的引腳數(shù)。此時(shí)PCI設(shè)備必須共享中斷號(hào)-中斷控制器上的一個(gè)引腳可能被多個(gè)PCI設(shè)備同時(shí)使用。Linux讓中斷的第一個(gè)請(qǐng)求者申明此中斷是否可以共享。中斷的共享將導(dǎo)致irq_action數(shù)組中的一個(gè)入口同時(shí)指向幾個(gè)irqaction數(shù)據(jù)結(jié)構(gòu)。當(dāng)共享中斷發(fā)生時(shí)Linux將調(diào)用對(duì)應(yīng)此中斷源的所有中斷處理過(guò)程。沒(méi)有中斷需要服務(wù)時(shí),任何共享此中斷(所有的PCI設(shè)備驅(qū)動(dòng))的設(shè)備驅(qū)動(dòng)都要準(zhǔn)備好其中斷處理過(guò)程的調(diào)用。
Linux中斷處理子系統(tǒng)的一個(gè)基本任務(wù)是將中斷正確路由到中斷處理代碼中的正確位置。這些代碼必須了解系統(tǒng)的中斷拓?fù)浣Y(jié)構(gòu)。例如在中斷控制器上引腳6上發(fā)生的軟盤控制器中斷必須被辨認(rèn)出的確來(lái)自軟盤并路由到系統(tǒng)的軟盤設(shè)備驅(qū)動(dòng)的中斷處理代碼中。Linux使用一組指針來(lái)指向包含處理系統(tǒng)中斷的例程的調(diào)用地址。這些例程屬于對(duì)應(yīng)于此設(shè)備的設(shè)備驅(qū)動(dòng),同時(shí)由它負(fù)責(zé)在設(shè)備初始化時(shí)為每個(gè)設(shè)備驅(qū)動(dòng)申請(qǐng)其請(qǐng)求的中斷。圖7.2給出了一個(gè)指向一組irqaction的irq_action指針。每個(gè)irqaction數(shù)據(jù)結(jié)構(gòu)中包含了對(duì)應(yīng)于此中斷處理的相關(guān)信息,包括中斷處理例程的地址。而中斷個(gè)數(shù)以及它們被如何處理則會(huì)根據(jù)體系結(jié)構(gòu)及系統(tǒng)的變化而變化。Linux中的中斷處理代碼就是和體系結(jié)構(gòu)相關(guān)的。這也意味著irq_action數(shù)組的大小隨于中斷源的個(gè)數(shù)而變化。
中斷發(fā)生時(shí)Linux首先讀取系統(tǒng)可編程中斷控制器中中斷狀態(tài)寄存器判斷出中斷源,將其轉(zhuǎn)換成irq_action數(shù)組中偏移值。例如中斷控制器引腳6來(lái)自軟盤控制器的中斷將被轉(zhuǎn)換成對(duì)應(yīng)于中斷處理過(guò)程數(shù)組中的第7個(gè)指針。如果此中斷沒(méi)有對(duì)應(yīng)的中斷處理過(guò)程則Linux核心將記錄這個(gè)錯(cuò)誤,不然它將調(diào)用對(duì)應(yīng)此中斷源的所有irqaction數(shù)據(jù)結(jié)構(gòu)中的中斷處理例程。
當(dāng)Linux核心調(diào)用設(shè)備驅(qū)動(dòng)的中斷處理過(guò)程時(shí)此過(guò)程必須找出中斷產(chǎn)生的原因以及相應(yīng)的解決辦法。為了找到設(shè)備驅(qū)動(dòng)的中斷原因,設(shè)備驅(qū)動(dòng)必須讀取發(fā)生中斷設(shè)備上的狀態(tài)寄存器。設(shè)備可能會(huì)報(bào)告一個(gè)錯(cuò)誤或者通知請(qǐng)求的處理已經(jīng)完成。如軟盤控制器可能將報(bào)告它已經(jīng)完成軟盤讀取磁頭對(duì)某個(gè)扇區(qū)的正確定位。一旦確定了中斷產(chǎn)生的原因,設(shè)備驅(qū)動(dòng)還要完成更多的工作。如果這樣Linux核心將推遲這些操作。以避免了CPU在中斷模式下花費(fèi)太多時(shí)間。在設(shè)備驅(qū)動(dòng)中斷中我們將作詳細(xì)討論。
聯(lián)系客服