中斷和中斷處理常式
1.中斷
中斷本質上是一種特殊的電訊號,由硬體裝置發向處理器。處理器接收到中斷後,會馬上向作業系統反映此訊號的到來,然後由OS負責處理這些新到來的資料。硬體裝置產生中斷的時候並不考慮與處理器的時鐘同步,核心隨時可能因為新到來的中斷而被打斷。不同的裝置對應的中斷不同,都通過一個唯一的數位識別碼,稱之為插斷要求(IRQ)線。
在作業系統中,討論中斷就不得不提及異常。異常與中斷不同,它在產生時必須考慮與處理器時鐘同步。實際上,異常也常常稱為同步中斷。在處理器執行到由於編程失誤而導致的錯誤指令的時候,或者是在執行期間出現特殊情況,必須靠核心來處理的時候,處理器就會產生一個異常。因為許多處理器體繫結構處理異常與處理中斷的方式類似,因此,核心對它們的處理也很類似。
2.中斷處理常式
在響應一個特定中斷的時候,核心會執行一個函數,該函數叫做中斷處理常式(interrupt handler)或插斷服務常式(interrupt service routine,ISR)。中斷處理常式通常不是和特定裝置關聯,而是和特定中斷關聯,也就是說,如果一個裝置可以產生多種不同的中斷,那麼該裝置就可以對應多個中斷處理常式,相應的,該裝置的驅動程式也就需要準備多個這樣的函數。
中斷處理常式與其他核心功能的真正區別在於:中斷處理常式是被核心調用來響應中斷的,而它們運行於稱之為中斷內容相關的特殊上下文中。
中斷處理常式一方面需要迅速和儘可能短的時間內完成中斷處理,另一方面,處理常式內的工作量也不小。所以把中斷處理切為兩個部分。中斷處理常式是上半部(top half)——接收到一個中斷,它就立即開始執行,但只做有嚴格時限的工作,例如對接收的中斷進行應答或複位硬體,這些工作都是在所有中斷被禁止的情況下完成的,能夠被允許稍後完成的工作延遲到下半部(bottom half)。
3.共用的中斷處理常式
共用與非共用的處理常式差異有以下三處:
1)request_irq()的參數flags必須設定SA_SHIRQ標誌,但只有在中斷棧當前未被註冊,或者在該棧上的所有登入處理常式都指定了SA_SHIRQ的情況下才會成功。
2)對每個註冊的中斷處理常式來說,dev_id參數必須唯一。
3)需要硬體的支援。
核心接收一個中斷後,會依次調用在該中斷線上註冊的每一個處理常式。因此,一個處理常式必須知道它是否應該為這個中斷負責。如果與它相關的裝置並沒有產生中斷,那麼處理常式應該立即退出。
4.中斷上下文
當執行一個中斷處理常式或下半部時,核心處於中斷上下文(interrupt context)中,中斷上下文具有較為嚴格的時間限制,因為它打斷了其他代碼(甚至打斷了其他中斷線上的另一個中斷處理常式),它應迅速簡潔,盡量不要使用迴圈去處理繁重的工作。另一方面,中斷處理常式擁有自己的棧,每個處理器一個,大小為一頁(32位為4KB,64位為8KB),稱之為中斷棧。中斷處理常式的編寫要注意這兩個方面。
5.中斷控制
Linux核心提供了一組介面用於操作機器上的中斷狀態。這些介面為我們提供了能夠禁止當前處理順的中斷系統,或屏蔽掉整個機器的一條中斷線的能力。一般來說,控制中斷系統的原因歸根結底是需要提供同步。通過禁止中斷,可以確保某個中斷處理常式不會搶佔當前的代碼。然而,不管是禁止中斷還是禁止核心搶佔,都沒有提供任何保護機制來防止來自其他處理器的並發訪問。Linux支援多處理器,因此,核心代碼一般都需要擷取某種鎖,防止來自其他處理器對共用資料的並發訪問。擷取這些鎖的同時也伴隨禁止本地中斷。
用于禁止或啟用當前處理器上的本地中斷的語句為:
local_irq_disable() 和 local_irq_enable()
直接使用這兩個語句禁止或啟用中斷都存在潛在危險,因為它們將無條件地禁止或啟用中斷,而不管中斷在開始時的狀態。因此,在禁止中斷之前儲存中斷系統的狀態會更加安全一些。相反,在準備啟用中斷時,只要把中斷恢複到它們原來的狀態。
在某些情況下,只禁止整個系統中一條特定的中斷線就夠了,Linux提供了四個介面:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
前兩個函數禁止中斷控制器上指定的中斷線,即禁止給定中斷向系統中所有處理器的傳遞。另外,函數只有在當前正在執行的所有處理常式完成後,disable_irq()才能返回。函數disable_irq_nosync()不會等待當前中斷處理常式執行完畢。
函數synchronize_irq()等待一個特定的中斷處理常式的退出。如果該處理常式正在執行,那麼該函數必須退出後才能返回。
這些函數調用可以嵌套,但要記住在一條指定的中斷線上,對disable_irq()或disable_irq_nosync()的每次調用,都需要相應地調用一次enable_irq()。只有在對enable_irq()完成最後一次調用後,才真正啟用了中斷線。
Linux提供兩個宏,用來瞭解中斷系統的狀態:
in_interrupt() 和 in_irq()
in_interrupt():如果核心處於中斷上下文中,它返回非0,說明核心此刻正在執行中斷處理常式,或者正在執行下半部處理常式。in_irq()只有在核心確實正在執行中斷處理常式時才返回非0。