中斷是一種特殊的電信號,由硬件發向處理器,處理器接收到中斷時,會馬上箱操作系統反映,由操作系統進行處理。中斷隨時可以產生,因此,內核隨時可能因為新到來的中斷而被打斷。
不同的設備對應的中斷不同,每個中斷通過一個唯一的數字標識,這些中斷值通常被稱為中斷請求(IRQ)線。
中斷處理程序又成為中斷處理例程(ISR),是內核在響應中斷時執行的一個函數
一個中斷處理程序對應一個中斷,一個設備可能發出多種中斷
對於外部設備,中斷處理程序是設備驅動程序的一部分
在 Linux 中,中斷處理程序和 C 函數區別不大,但有自己的規范,主要是運行時需要在中斷上下文中
驅動程序可以通過request_irq()
函數注冊一個中斷處理程序(linux/interrupt.h)
int request_irq(unsigned int irq,
irqhandler_t handler,
unsigined long falgs,
const char *name,
void *dev)
irq:
表示要分配的中斷號
handler:
一個指針,指向處理這個中斷的實際中斷處理函數
typedef irqhandler_t(*irq_handler_t)(int, void*);
卸載驅動程序時,需要注銷響應中斷處理程序,並釋放中斷線。
void free_irq(unsigned int irq, void *dev);
如果指定的中斷線不是共享的,那麼該函數刪除處理程序的同時將禁用這條中斷線;中斷線是共享的,則僅僅刪除 dev 對應的處理程序,而這條中斷線本身只有在刪除了最後一個處理程序時才會被禁用
local_irq_disable();
local_irq_enable();
又想中斷處理程序運行的快,又想中斷處理程序完成的工作多,這兩個目的顯然有所抵觸,所以把中斷處理分為兩個部分:
中斷處理程序是上半部,接收到一個中斷,它就立刻開始執行,但只做有嚴格時限的工作,例如一些只有在中斷被禁止的狀態下才能完成的工作
能夠被允許稍後完成的工作會推遲2到下半部去,此後,在合適的時機,下半部會被開中斷執行
中斷程序以異步方式執行,可能打斷重要操作的執行,越快越好中斷處理程序會屏蔽其他同級中斷,所以執行越快越好中斷處理程序往往需要對硬件操作,通常有很高的時限要求中斷處理程序不在進程的上下文中運行,所以不能阻塞Q1:為什麼要分上半部和下半部? {% endcq %}
如果一個任務對時間非常敏感,將其放到上半部;如果一個任務和硬件相關,將其放到上半部;如果一個任務要保證不被其它中斷打斷,將其放到上半部;其他的所有任務考慮放到下半部Q2:上半部和下半部如何分開? {% endcq %}
下半部的任務就是執行與終端處理密切相關但中斷處理程序本身不執行的工作。我們期望中斷處理程序將盡量多的工作放到下半部執行,以快速從中斷返回。
此處的軟中斷和系統調用使用的 int 80H 不同,是操作系統支持的一種,在編譯期間靜態分配
linux/interrupt.h
中:
struct softirq_action{
void (*action)(struct sfotirq_action*); //待執行的函數
void *data; //傳遞的參數
}
最多可能32個軟中斷,定義於 kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS];
void softirq_handler(struct softirq_action*); //傳遞整個結構體
一個注冊的軟中斷必須在被標記後才會執行下列地方,待處理的軟中斷會被檢查和執行:
在 ksoftirqd 內核線程中在那些顯式檢查和和執行待處理的軟中斷的代碼中(如網絡子系統)不管執行的時機,軟中斷都要在do_softirq
中執行
分配索引: 通過 linux/interrupt.h
中的一個枚舉類型中聲明一個新的軟中斷
注冊處理程序:在運行時通過調用open_softirq()
注冊軟中斷處理程序,有兩個參數,軟中斷和處理函數
觸發軟中斷:raise_softirq()
函數可以將一個軟中斷設置為掛起狀態,讓它在下次調用do_softirq()
函數時投入運行
基於軟中斷的實現,但它的接口更簡單,鎖保護要求更低
struct tasklet_struct{
struct tasklet_struct *next; //鏈表
unsigned long state; //tasklet 狀態
atomic_t count; //引用計數器
void (*funx)(unsigned long); //taklet 處理函數
unsigned long data; //給處理器函數傳遞的參數
}
被觸發的軟中斷存放在2個數據結構:tasklet_vec
,task_hi_vec
,這兩個數據結構都是由task_struct
構成的鏈表,由tasklet_schedule()
和task_hi_schedule()
進行調度,調度步驟如下:
(1)檢查 tasklet 狀態,如果為TASK_STATE_SCHED
則返回
(2)調用_tasklet_schedule()
(3)保存中斷狀態,禁止本地中斷
(4)把需要調度的 tasklet 加到 tasklet_vec
或tasklet_hi_vec
鏈表頭
(5)喚起TASKLET_SOFTIRQ
或HI_SOFTIRQ
軟中斷,下一次調用do_softirq()
時會執行該 tasklet
(6)恢復中斷
tasklet_action()
或task_hi_action()
[tasklet 處理的核心]:TASKLET_STATE_RUN
判斷這個 tasklet 是否在其他處理器上運行,如果是,跳到笑一個 tasklet (6)如果否,設置TASKLET_STATE_RUN
(7)檢查 count 是否為0,確保 tasklet 沒有被禁止;如果被禁止,跳到下一個 tasklet (8)執行 tasklet 處理程序(9)執行完畢,清除TASKLET_STATE_RUN
(10)重復執行下一個 tasklet
DECLARE_TASKLET(name,func,data);
DECLARE_TASKLET_DIASBLED(name,func,data);
動態:通過一個指針賦給一個動態創建的 tasklet_struct:
tasklet_init(t, takslet_handler, dev);
void tasklet_handler(unsigned long data)
注意:不能再 tasklet 中使用信號量或者其他阻塞式函數
tasklet_schedule(&my_tasklet);
tasklet_enable(&my_tasklet);
tasklet_disable(&my_tasklet);
ksoftirqd 是內核線程,每個處理器都有一個,用於在空閒處理器處理軟中斷
for(;;){
if(!softirq_pending(cpu))
schedule();
set_current_state(TASK_RUNNING);
while(softirq_pending(cpu)){
do_softirq();
if(need_resched())
schedule();
}
set_current_sdtate(TASK_INTERRUPTIBLE);
}
只要有待處理的軟中斷,該線程就會處理
工作隊列機制將下半部功能交給內核縣城去執行,有線程上下文,可以睡眠
schedule()
,休眠如果不為空,將自己設為TASK_RUNNING
調用run_workqueue()
執行被推後的工作
該函數循環遍歷鏈表上每個待處理的工作:
當鏈表非空,選取下一個節點對象獲取要執行的函數和參數從鏈表上解下該節點,將 pending 位清零調用函數重復執行靜態:
DECLARE_WORK(name, void(*func)(void*), void *data);
動態:
INIT_WORK(struct work_struct *work, void(*func)(void*), void *data);
工作隊列處理函數
void work_handler(void *data)
對工作的調度
schedule_work(&work);
schedule_delayed_work(&work, delay);
刷新操作
void flush_scheduled_work(void);
Q:我們要選擇哪種機制? {% endcq %}
如果有休眠的要求,選擇工作隊列;否則,最好使用 tasklet;要是必須專注性能的提高,選擇軟中斷
如果進程上下文和一個下半部共享數據,在訪問這些數據之前,你需要禁止下半部的處理並得到鎖的使用權
如果中斷上下文和一個下半部共享數據,在訪問數據之前,需要禁止中斷並得到鎖的使用權
臨界區就是訪問和操作共享資源的代碼段,必須保證原子地執行才能保證安全
保證在臨界區執行的縣城只有一個
中斷
軟中斷和 tasklet
內核搶占
睡眠及用戶空間的同步
對稱多處理
要有一個或多個執行線程和一個或多個資源
每一個線程都在等待其中一個資源
所有的資源都被占用
所有縣城都在互相等待,但他們永遠不會釋放已經占有的資源
atomic_dec_and_test(atmoic_t, *v)
原子位操作(asm/bitops.h)
set_bit(0, &word)
自旋鎖只能被一個可執行進程持有
若爭用一個被占用的鎖則進程忙等(旋轉)
自旋鎖不能長期被占用,否則效率低