第 七 章 中 斷 與 中 斷 處 理


第七章 中斷與中斷處理 本章講述Linux核心如何處理中斷。 雖然通常作業系統都提供一些通用的機制和介面來處理中斷,大多數中 斷處理的細節是與具體的設備體系結構有關的。
圖7.1
Linux支援許多不同的硬體。視訊設備驅動顯示器,IDE設備驅動磁碟等 等。你可以同步地驅動這些設備:發出一個操作請求然後等待操作的完 成(比如將一塊記憶體的內容寫進磁碟)。這種方法雖然在邏輯和實踐上 都行的通,但是效率很差。在等待你請求的操作返回的時候,作業系統 什麼也不能作(busydoingnothing),浪費了許多CPU時間(譯者:週邊的速 度比CPU通常慢很多)。一個更好的,更有效的方法是:作業系統發出 週邊操作請求,然後去做別的事情。當週邊請求完成並返回時,中斷操 作系統。通過這種方案,系統裡可以同時支援許多設備請求,而不是 嚴格的同步。 不管系統採用什麼CPU,我們必須用一些硬體來提支援設備中斷CPU。 大多數處理器(比如AlphaAXP)採用類似的方法:CPU接腳(Pin)中的一些 上的電壓的變化(例如從+5伏到-5伏)可以導致CPU停止它正在處理的 事情,而轉到一段特殊的程式碼過程上去處理中斷。在那些CPU接腳中, 有一個接腳連接在一個內部定時器上,從而可以每秒接收1000次的中斷 。其他的負責中斷的接腳會連在相應的其他設備上,比如SCSI磁碟機。 在傳遞中斷信號到一個CPU中斷接腳前,系統常採用一個中斷控制器並 用它將眾多的設備中斷組合起來。這樣就節省了CPU的中斷接腳並且給 系統設計帶來了靈活性。中斷控制器常用其的Mask暫存器和狀態暫存器 來控制來自設備的中斷信號。通過設置Mask暫存器的位操作可以允許 或屏蔽一些週邊的中斷。狀態暫存器可以用來查詢目前系統中已啟動的 待處理的中斷。 系統中有一些中斷接腳是固定連接的,比如,實時時鐘的定時器可能被 永久地連在中斷控制器的接腳3上。當然,這些接腳具體連接的是什麼 設備取決於插在ISA或PCI插槽上的是設備控制器。比如,中斷控制器 的接腳4可能對應於PCI插槽0,而在此插槽上有可能今天是一塊 Ethernet網路卡,明天是一個SCSI控制器。對於這樣每一種設備都提供不 同的,為特定設備而寫的中斷處理過程,作業系統必須提供足夠的靈活 性來處理。 大多數一般微處理器採用同樣的方式處理中斷:當一個硬體中斷發生時 ,CPU停止它正在處理的指令,跳轉到記憶體中的一個位址。在那個位址 處,含有中斷處理過程或一條可以指向中斷處理過程的指令。這段程式 碼一般運行在CPU的一種特殊模式---中斷模式。一般而言,在這種模式 下,其他的中斷不會被接受。當然也存在例外的情況。一些CPU將中斷 按優先級劃分,從而在處理低優先級的中斷時,更高階的中斷可以被 處理。換句話說,最低階的那斷中斷處理過程必須非常細心的編寫。例 如,需要一個自己的堆疊空間以用來保存CPU的執行狀態(所有的CPU暫 存器和上下文)在CPU被剝奪並處理更高一級的中斷之前。(譯者注:1 。上下文:context.這裡的上下文講的是用戶程序切換到核心模式時的上 下文。2。中斷處理是一個過程,通常依附在一個程序的上下文中。3 。這裡講的堆疊通常是指一個程序在核心模式下的核心堆疊。所有核心模式下的 “過程”調用包括中斷處理過程都在這個堆疊上處理。)。有些CPU提供 一套特殊的暫存器集。這套暫存器集只存在於中斷模式下。從而中斷過 程處理程式碼可以利用它們來保存大多數的,需要保存的上下文。(譯者 注:現代作業系統排程中,一個很大的代價發生在程序上下文切換中。 感興趣的讀者可以存取UCBerkeley的NOW項目中的co-schedule部份。提 供一套暫存器有利於性能最佳化。) 當中斷處理完畢後,CPU的狀態被恢復﹔CPU將繼續從斷點處執行(譯 者住:這個斷點有可能是會到使用者模式,也有可能仍然在核心模式,比如, 繼續完成系統調用或處理低一級的中斷請求。)。所以,中斷處理程式 要盡可能的高效以防止堵塞其他的中斷。 7.1可程式化中斷控制器 系統設計師可以隨意選擇他們希望的中斷控制器硬體。IBM-PC系列用的 是INTEL的82C59A-2CMOS可程式化中斷控制器系列。這種控制器自從有了 PC就存在了,提供了一套可程式化的,在ISA位址空間裡,位址是周知 (Well-Known)的暫存器。任何一個現代的邏輯chipsets為這些暫存器保留著 同樣的ISA記憶體位址。非INTEL處理器系統,例如基於AlphaAXP的PC就 不受上述限制。它們採用不同的中斷控制器。 圖7.1所示是兩個8位的控制器連在一起,PIC1和PIC2。每一個控制器 有一個Mask暫存器和一個中斷狀態暫存器,Mask與中斷狀態暫存器的 位址分別在ox21,oxA1和ox20,oxA0。對一個Mask的某一位置1將允許一個 中斷﹔位置0將屏蔽一個中斷。例如,位3置1將致能(Enable)中斷3。反之 將屏蔽中斷3。遺憾的是,中斷Mask暫存器是只能寫,不可讀,你不能 讀回剛剛寫進去的位值。這意味著Linux必須在核心中保存一份目前Mask 暫存器的備份。每次核心先改寫這個“最近的”備份然後一次性的刷 新Mask暫存器。 當一個中斷到來時,中斷處理過程讀圖中的兩個中斷狀態暫存器(ISR)。 系統把在0x20的ISR當做這個16位中斷狀態暫存器的低8位,在位址0xA0 上的ISR為高8位。所以如果在oxA0上的ISR的第一位被置一的話,系統 認為來了一個中斷9。PIC1的第2位被用來連接PIC2。所以任何PIC2的中 斷都會導致PIC1的第2位被置1。 7.2中斷處理資料結構的初始化 核心的中斷處理資料結構的設置由設備驅動程式(DeviceDriver)來負責,因 為是它們需要控制中斷。設備驅動程式利用Linux核心中的一些服務例 程(譯者注:核心中一些預先編好的功能函數。)來請求,使能或屏蔽一 個中斷。 這些各自不相同的設備驅動程式通過調用上述例程來等級它們的中斷處 理過程位址。 對於PC體系結構,一些中斷的中斷號是固定的,約定好的。初始化時 ,磁碟機只要申請這個中斷就可以。比如軟碟機將固定使用IRQ6。 有時候設備驅動程式不知道設備將使用那個中斷。對於PCI設備驅動程 序而言,這不是個問題因為設備占用的中斷號可以被知道。但對於ISA 設備驅動程式就不是那麼容易知道。Linux通過允許設備驅動程式探測中 斷號來解決上述問題。 首先,這個設備驅動程式通過一些操作使得這個設備發出中斷。然後致 能所有的,系統中還沒有分配出去的中斷號。這意味著這個設備發出 的中斷通過中斷控制器會被系統接收。然後Linux讀取ISR的內容並將當 前值傳遞給上述的設備驅動程式。一個非0值將意味著一個或多個中斷 已經發生。這時,設備驅動程式重新屏蔽所有未分配的中斷口。 ISA設備驅動程式在知道它的設備占用的中斷號後,就可以像正常一樣 去註冊它的中斷處理過程了。 基於PCI的系統比起基於ISA的系統有更多的靈活性。ISA設備一般通過 設置硬體板上的跳線(Jumpers)來設置中斷。跳線設置後,在系統初始化 後,核心程式中這個中斷號是已經固定的分配給這個設備了(譯者注: 如果沒有中斷衝突的話)。然而,PCI設備的中斷是在系統啟動時,通過 PCIBIOS或PCI子系統在初始化時來分配的。每一個PCI設備卡有四個中 斷接腳,A,B,C和D。通常設備預設使用接腳A。每一個PCI插槽的A,B,C和 D中斷接腳都被引向中斷控制器。所以PCI插槽4的接腳A可能映射在中 斷控制器的接腳6上,接腳B可能映射在中斷控制器的接腳7上。 PCI中斷的如何映射跟不同的系統有關。任何一個系統都要提供一些程式 碼用來解釋PCI中斷映射拓樸。基於INTEL的PC通過BIOS程式碼。對於沒有 BIOS的系統(基於AlphaAXP的系統),Linux核心將會負責處理上述任務。 上述PCI設置程式碼將每塊PCI設備相對應的IRQ號寫入一個PCI配置頭 (ConfigurationHeader)資料結構。IRQ號的獲得是通過PCI中斷映射拓樸, PCI插槽和哪一個PCI中斷接腳正被使用而推導出來的。對每塊PCI設備 ,它用的IRQ號將被固定下來並寫入其相應的PCI配置頭資料結構的值域 中--"interruptline"。當這個設備運行時,它讀取這個信息然後向Linux核心 要求占有這個中斷的處理權。 在一個系統中,有可能同時存在許多PCI中斷源。例如當PCI bridge 的情況下 。所以就有可能中斷源的數目超過系統提供的可程式化中斷控制器的接腳 數目。這種情況下,PCI設備之間可能要共用一些中斷口,中斷控制器 一個接腳將接收來自多個PCI設備的中斷。Linux允許第一個申請占有一 個特定中斷口的中斷源願不願意將這個中斷口被其他設備共享。共享的 中斷口信息都存放在一些叫做irqaction的資料結構中。irqaction結構的位址 可在一個向量irq_action中找到。當一個共享的中斷發生時,Linux將調用 掛在這個中斷上的,所有的設備的,中斷處理程式。因此任何一個可以 支援共享中斷的設備驅動程式(所有的PCI驅動程式)都必須能夠支援其 中斷處理過程被調用雖然在那個時刻這個設備沒有中斷發生。 7.3中斷處理
圖7.2
Linux中斷處理子系統的一個首要任務是當處理中斷時,將指令控制指向 正確的中斷處理程式碼過程。完成上述任務的程式碼必須了解系統的中斷分 部情況。例如,如果軟碟驅動控制器用的中斷口是中斷控制器的接腳6 ,那麼當接收到一個中斷信號6時,系統必須將CPU執行位址轉到軟碟 設備驅動程式程式碼處。Linux使用一系列指標指向含有中斷處理例程的資 料結構。這些例程分別屬於系統中不同的設備驅動程式。每一個設備驅 動程式在初始化時負責申請它所需要的中斷號。如圖7.2所示,irq_action 是一個指標向量指向irqaction資料結構。每一個irqaction資料結構含有為這 個中斷口(譯者注:irq_action向量的下標加1)服務的處理程式的信息(包含 中斷處理程式的入口位址)。至於系統支援的中斷數目和中斷如何被處 理,對於不同的(硬體)體系結構和作業系統,方法不一樣。Linux的中斷 處理程式碼是與體系結構有關的。irq_action向量的大小依賴於系統中中斷 源的數目。 當一個中斷發生時,Linux首先必須通過讀取目前的ISR(中斷狀態暫存器 )來決定中斷的來源。然後核心把這個中斷源映射到irq_action向量一個偏 移量上。例如,一個來自軟碟驅動的中斷6將被映射到向量的第7個入口。 如果對於一個發生了的中斷沒有一個中斷處理handler相對應,Linux核心將 記載一個錯誤。否則,核心將通過查詢所有的”掛“這個中斷口上 irqaction結構並調用相應的中斷處理例程。(譯者注:如果是線性的鏈結串列 查詢的話,可以在這裡做一些演算法上的最佳化。如將最近常發生中斷的那 個中斷源的資料結構移到鏈結串列的前面。很多現成的演算法可以用上來。) 當一個設備驅動程式的中斷處理例程被Linux核心調用以後,它必須迅速 地解決為什麼來了中斷並做出反應。為了找出中斷的原因,設備驅動程 序會讀取這個中斷設備的狀態暫存器。這個設備有可能正在回報一個 錯誤或一個請求的操作完成。比如,一個軟碟控制器回報對一個指定的 磁碟sector的磁頭定位已經結束。一旦中斷的原因被查明,設備驅動程 序有可能需要採取更多的工作去響應這個中斷。如果是這樣,Linux核心 提供機制允許設備驅動程式推延其操作。從而可以避免CPU花費太多的 時間在中斷模式下。有關這方面的細節請參閱設備驅動程式章節。