目錄
1、中斷簡介
1.1 作用
1.2 物理實現
1.3 中斷請求線IRQ
1.4 異常
2、中斷處理程序
2.1 作用
2.2 上半部和下半部
2.3 中斷上下文
3、中斷系統
3.1 中斷機制的實現
3.2 中斷控制
4、下半部和軟中斷
4.1 下半部簡介
4.2 軟中斷
4.3 tasklet
4.4 工作隊列
1、中斷簡介
1.1 作用
中斷機制,是操作系統用來實現處理器和外部設備協同工作的方案,讓硬件在需要的時間主動向內核發出信號。
1.2 物理實現
中斷是一種特殊的電信號,由硬件生成。中斷生成和處理的主要流程:
硬件生成中斷電信號,並直接送入中斷控制器的輸入引腳;
中斷控制器接收到一個中斷信號后,向處理器發送一個電信號;
處理器檢測到這個電信號后,會立即中斷自己當前的工作,並處理這個中斷(向操作系統反映此信號的到來);
操作系統處理這個中斷請求
硬件設備在產生中斷的時候,並不會考慮與處理器的時鍾同步問題,也就是說,中斷會隨時產生,內核隨時可能因為新到來的中斷而被打斷。
1.3 中斷請求線IRQ
中斷請求線指的是中斷的唯一數字標志。不同設備對應的中斷不同,操作系統通過IRQ,來區分中斷的來源是什么硬件設備,以提供相應的中斷處理程序。
IRQ的值是和硬件相關的。在經典的PC機上,IRQ 0是時鍾中斷,IRQ 1是鍵盤中斷;而在PCI總線上的設備,中斷的IRQ則是動態分配的。其他非PC的體系結構里,也有動態分配可用動態的特性。
1.4 異常
異常也被稱為同步中斷,因為異常產生的時候,必須考慮和處理器時鍾同步的問題。
在處理器執行到編程失誤導致的錯誤之類(如被0除),或者執行期間出現特殊情況(如缺頁),必須靠內核來處理的時候,處理器就會產生一個異常。所以,很多處理器體系結構處理異常和處理中斷的方式非常類似。
2、中斷處理程序
2.1 作用
在響應一個特定中斷的時候,內核會執行一個函數:中斷處理程序,或者叫中斷服務例程(ISR,Interrupt Service Routine)。中斷處理程序,是一個設備的驅動的一部分,一方面負責通知硬件設備中斷已經被接收,另外還有其他大量和設備相關的工作。例如對網卡的中斷,還需要把來自網絡的數據包拷貝到內存,並處理然后再交給合適的協議棧和應用程序。
2.2 上半部和下半部
由於中斷隨時可能發生,所以必須保證中斷處理程序可以快速執行;但是中斷處理程序可能又會處理大量的任務,兩者之間存在矛盾,所以一般會把中斷處理的過程分成兩部分:上半部和下半部。
上半部也叫硬中斷,是通常意義上的中斷處理程序,用來接收中斷,和簡單的、有時限的處理工作,例如對中斷接收后進行應答或者復位硬件等需要在所有中斷被禁止的情況下完成的工作。而其他的允許稍后完成的工作,則會推遲到下半部在合適的時間完成。Linux有多種機制來實現下半部,其中一種就是軟中斷。
對於網卡的中斷處理,上半部會執行 通知硬件、拷貝網絡數據報到內存並繼續讀取新數據包,這些重要、緊急且與硬件相關的工作,因為網卡接收的網絡數據包的緩存大小通常是固定的、有限的,一旦被延遲可能造成緩存溢出。而數據包的處理等操作,則由下半部來完成。
2.3 中斷上下文
中斷處理程序運行在中斷上下文中。中斷上下文也被稱為原子上下文,因為該上下文中執行的代碼是不可阻塞的。
中斷上下文不可以睡眠,所以中斷上下文中的代碼中,不允許使用睡眠函數。
中斷上下文的執行,是打斷了其他代碼,甚至是其他中斷線上的中斷處理程序的執行,所以是有嚴格的時間限制的,所以中斷上下文中的代碼必須簡潔迅速。盡量把工作分離出來放到下半部執行,因為下半部可以在更合適的實現來執行。
在Linux 2.6版本中,內核為每個中斷處理程序分部增加了一個中斷棧,大小為1頁(32位系統是4KB,64位系統是8KB)。
3、中斷系統
3.1 中斷機制的實現
設備產生中斷,通過總線將電信號發送給中斷控制器;
如果中斷線是激活的(中斷線允許被屏蔽),中斷控制器將中斷信號發送給處理器。在大多數體系結構里,這一步是通過電信號給處理器的特定管腳發送一個信號;
處理器如果沒有禁止這個中斷,則立即停止正在進行的事情,關閉中斷系統,並跳轉到內存中預定義的位置,執行那里的代碼。這個預定義的位置,是內核設置的,中斷程序的入口。對於每條中斷線,內核都會跳轉到一個唯一的位置,內核以此獲取中斷的IRQ號;
內核首先在棧中保存中斷的IRQ號,並保存寄存器的值,這些值是被中斷任務的;
內核調用do_IRQ()方法響應中斷:
對接收的中斷進行應答,並禁止這條線的中斷傳遞;
do_IRQ()檢查這條中斷線是否有一個有效的處理程序,而且這條程序已經啟動,但是當前沒有執行;
如果符合條件,do_IRQ()調用handle_IRQ_event()運行這條中斷線的中斷處理程序;
如果不符合條件,或者handle_IRQ_event()執行完成后,do_IRQ()會調用ret_from_intr()方法,結束中斷過程,返回中斷前執行的用戶地址空間。
3.2 中斷控制
Linux內核提供了一套用來操作中斷的系統調用,可以用來屏蔽處理器的中斷,或者屏蔽掉一部分、甚至某一條中斷線的中斷。這些可以在<asm/system.h>和<asm/irq.h>中找到。
Linux之所以要實現中斷控制系統,主要是因為處理器需要同步。通過禁止中斷,可以防止某個或者某些中斷處理程序不會搶占當前內核執行的代碼;此外,禁止中斷還可以禁止內核搶占。但是禁止中斷和禁止內核搶占,並不能防止其他處理器並發訪問。想要防止其他處理器的並發訪問,需要Linux的鎖保護機制來實現。
1、禁止和激活全部中斷
linux內核提供了接口,用於禁止或激活全部中斷線的中斷請求:
local_irq_disable():禁止全部中斷線的中斷請求;
local_irq_enable():激活全部中斷線的中斷請求。
這兩個接口會有一些問題:它們會無條件的禁止和激活所有的中斷線。如果有部分中斷線本來就是被禁止的,local_irq_enable()也會將它們恢復。在很多場景激活所有的中斷線可能會帶來一些問題。
2、禁止中斷和恢復
由於禁止和激活全部中斷會帶來一些問題,Linux內核提供了禁止中斷並恢復禁止前狀態的調用:
unsigned long flags:值傳遞的一些參數
local_irq_save(flags):保存本地中斷的當前狀態,然后禁止這些中斷線的中斷請求;
local_irq_restore(flags):恢復本地中斷控制狀態到保存的狀態。
3、禁止指定中斷線的中斷
一些情況下,操作系統只需要禁止某條中斷線的中斷,即屏蔽這條中斷線。例如在執行某條中斷線的中斷處理程序時,一般會屏蔽這條中斷線。
Linux提供了相應的系統調用:
disable_irq(unsigned long irq):在當前中斷線的中斷處理程序執行完成后,屏蔽這條中斷線;
disable_irq_nosync(unsigned long irq):立即屏蔽這條中斷線;
enable_irq(unsigned long irq):激活某條中斷線;
synchronize_irq(unsigned long irq):等待某條中斷線的中斷處理程序退出,如果該處理程序正在執行,則處理程序退出后方法才會返回。
4、下半部和軟中斷
根據上面的內容可知:
中斷處理過程的上半部就是硬中斷,主要負責處理中斷過程中和硬件相關的、對時間敏感的操作;
下半部主要用來處理一些比較耗時的操作,linux有很多中實現方式,包括tasklet、軟中斷、工作隊列。即軟中斷是下半部的一種實現方式。
4.1 下半部簡介
1、為什么要使用下半部
如果所有任務都放到上半部執行,會導致處理器較長時間不能處理一部分甚至全部中斷:當一個中斷正在處理的時候,這個中斷線在所有處理器上都會被屏蔽;甚至如果一個處理程序是IRQF_DISABLED類型的,它在執行的過程中會屏蔽所有中斷
中斷處理器不在進程上下文中執行,所以它們不能被阻塞,這在很多場景會有限制;
中斷處理程序是異步執行的。
所以應該盡量縮短中斷處理程序的執行時間。這樣,我們會將和硬件不相干的、對時間不太敏感的操作,盡量放到下半部執行
2、什么任務會放到下半部
Linux操作系統目前沒有辦法強制要求哪些任務放到下半部,目前完全取決於開發者自己去判斷。但是有一些規則提供借鑒:
如果一個任務,對時間非常敏感,則應該放到上半部;
如果一個任務,和硬件相關,則應該放到上半部;
如果一個任務,要求不被其他任務打斷,尤其是不被同類型的中斷打斷,則應該放到上半部;
其他的任務都應該放到下半部。
3、下半部的實現方式
對於現在的Linux 2.6來說,主要有三種方式實現下半部:
軟中斷:有些地方會混淆軟中斷和下半部的概念,實際上軟中斷是下半部的實現方式之一。
tasklet:tasklet實際上是基於軟中斷實現的,是在性能和易用性之間尋求平衡的產物。對於大部分軟中斷,使用tasklet即可。
工作隊列:和上面兩種不同,工作隊列可以把任務退后,交給內核線程去執行。工作隊列是工作在進程上下文中的,用於進程的各種特性,比如睡眠等。
4.2 軟中斷
軟中斷是編譯期間動態分配的,不像tasklet一樣可以動態的注冊和銷毀。軟中斷最多只有32個軟中斷,大部分下半部都是通過tasklet來實現的,目前只用了9個左右。
軟中斷運行在軟中斷上下文中,軟中斷的執行會搶占進程上下文。軟中斷執行過程中,不允許重新調度和睡眠。
1、資源搶占
軟中斷不會被另一個軟中斷打斷,只有硬件的中斷處理程序才能打斷軟中斷;
另一個軟中斷可以在其他處理器上執行;
同一種軟中斷,也可以在另一個處理器上執行
2、什么時候應該使用軟中斷實現下半部
軟中斷應該保留給系統中對時間要求最嚴格的下半部使用。目前只有兩個子系統直接使用軟中斷:網絡和SCSI。此外,內核定時器和tasklet也是基於軟中斷實現的。由於同一個軟中斷可以在不同處理器上同時執行,所以軟中斷要求對共享資源的處理要非常嚴格,鎖保護的要求很高。
3、軟中斷的觸發和執行
一個注冊的軟中斷被標記后才會被執行,這被稱為軟中斷的觸發。通常中斷處理程序會在返回前標記相應的軟中斷,使其稍后會被執行。
待處理的軟中斷,被檢查和執行的時機包括:
從一個硬件中斷代碼處返回;
在ksoftirqd內核線程中;
在顯式檢查和執行待處理的軟中斷的代碼中,如網絡子系統中。
4.3 tasklet
tasklet是基於軟中斷實現的一種下半部機制,所以也是運行在軟中斷上下文的。
相比於直接使用軟中斷,tasklet的接口更簡單,鎖保護的要求也更低。tasklet可以靜態定義,也可以動態初始化。
tasklet由兩類軟中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。這兩者唯一的區別在於,HI_SOFTIRQ類型的軟中斷先於TASKLET_SOFTIRQ類型的軟中斷執行。
1、資源搶占
tasklet不會被其他tasklet打斷,只有硬件的中斷處理程序才能打斷tasklet;
如果一個tasklet正在一個處理器上執行,則這個tasklet不允許在本處理器和其他處理器上再次觸發;
不同的tasklet可以同時在不同的處理器上執行。
4.4 工作隊列
工作隊列可以把任務推后,交由一個內核線程來完成。工作隊列的任務是運行在進程上下文中的,這一點和軟中斷、tasklet不同,工作隊列可以使用進程的特性,最重要的是可以重新調度和睡眠。
一般來說,如果任務需要睡眠或者重新調度,就需要使用工作隊列;但是如果不需要,一般使用tasklet來實現。使用工作隊列的場景一般包括:需要獲取大量內存的場景、需要獲取信號量或者鎖的場景、需要阻塞式的執行IO的場景等。
理論上可以用創建內核線程的方式來代替工作隊列,但是由於隨便創建內核線程會帶來其他問題,所以實際上並不建議直接創建內核線程,而應使用工作隊列的形式。
原文鏈接:https://blog.csdn.net/weixin_38569499/article/details/113919471