中斷和中斷處理常式
中斷隨時可能產生,打斷CPU的執行,CPU轉而處理中斷。 不同的裝置對應的中斷不同,每個中斷都通過一個唯一的數字標誌。
這些中斷值稱為
插斷要求(IRQ)線,每個irq線關聯一個數值。 中斷處理常式
響應中斷時,核心會執行一個函數,中斷處理常式/插斷服務常式ISR, 一個裝置的中斷處理常式是他的裝置驅動的一部分。
IO資源套件括 : 中斷,I/O連接埠,共用RAM,DMA。驅動程式需要管理註冊釋放這些資源。
上半部:接收到中斷就立即執行,只做有嚴格時限的工作,如對中斷應答或複位硬體。
下半部 : 能夠被允許稍後完成的工作延遲到下半部執行。 註冊中斷處理常式
Request_irq(uint irq, irq_handlet_t handler, ulong flasgs,void* dev)註冊中斷處理常式,啟用給定的中斷線;這個函數可能睡眠,不能在中斷上下文/其他不允許阻塞的代碼中執行。 卸載驅動程式時
登出相應中斷處理常式,釋放中斷線。 void free_irq(uint irq, void* dev); Linux的中斷處理常式是無需重入的,給定的中斷處理常式在執行時,相應中斷線在所有處理器上都會被屏蔽
中斷上下文:沒有後備進程,不可睡眠。中斷處理常式打斷了其他的代碼。 中斷處理機制的實現
裝置產生中斷,通過匯流排把電訊號發送給中斷控制器 如果中斷線是啟用的,中斷控制器發送中斷到處理器(處理器特定管腳) 如果處理器沒有禁止該中斷,處理器會停止正在做的事關閉中斷系統,調到預定義的中斷處理常式入口。 每條中斷線調到一個唯一位置。初始進入點儲存這個中斷線號,存放寄存器的值調用do_irq()
計算出中斷號,對中斷應答,禁止這條線上的中斷傳遞 確保這條中斷線上有一個有效處理常式,已經啟動,但沒有執行。 調用handle_IRQ_event()調用中斷線安裝的中斷處理常式。 將中斷禁止,返回到do_IRQ。 做清理工作,返回初始進入點,跳到ret_from_intr() 檢查是否調度掛起;恢複寄存器;核心恢複到中斷的點。 禁止當前處理器中斷和啟用中斷,
Local_irq_disable();local_irq_enable();
Unsigned long flags; local_irq_save(flags);local_irq_restore(flags); 禁止指定中斷
Disable_irq(int);禁止中斷向所有處理器的中斷。 中斷處理常式上、下半步處理邏輯分配原則: 上半部:
任務對時間非常敏感 任務和硬體相關 任務保證不被其他中斷打斷,不並發,不阻塞 下半部:
對時間不敏感 和硬體無關 可以被其他中斷打斷,可以睡眠,可以並發
Linux的上半部就是中斷處理常式,下半部有多種機制: 非強制中斷
非強制中斷是一組靜態定義的下半部介面,有32個,可以在所有處理器上同時執行,類型相同也可以;在編譯時間靜態註冊。 實現:
struct softirq_action{ //<linux/interrupt.h> 表示非強制中斷 void (*action)(struct softirq_action*);}
32個目前用了6個。
static struct softirq_action soft_irq_vec[NR_SOFTIRQS];//kernel/softirq.c非強制中斷數組
中斷處理常式:核心運行非強制中斷處理常式的時候,執行action函數。
一個非強制中斷不會搶佔另外一個非強制中斷。唯一可以搶佔非強制中斷的是中斷處理常式。其他的非強制中斷甚至同類型的可以在其他處理器上同時執行 執行軟體中斷:一個註冊的軟體中斷在標記後才會執行,這稱作觸發中斷。
中斷處理常式在返回前標記非強制中斷。
在:硬體中斷代碼返回時;在ksoftirq核心線程中;顯示檢查執行非強制中斷 處,待處理的非強制中斷會被檢查和執行
非強制中斷在do_softirq中執行
u32 pending;pending = local_sofqirq_pending();if(pending){ struct softirq_action* h; set_softirq_pending(0); h = softirq_vec; do{ if(pending & 1){ h->action(); } h++; pending >>=1; }while(pending);}
使用非強制中斷
非強制中斷留給對時間要求最嚴格及最重要的下半部使用。目前只有網路,scsi使用核心定時器和tasklet都建立在非強制中斷上。 通過枚舉類型靜態聲明非強制中斷,並分配索引 註冊處理常式
open_softirq(NET_TX_SOFTIRQ,net_tx_action);非強制中斷處裡程式執行時,允許響應中斷,但不能睡眠。由於只禁止當前處理器上的運行,其他處理器可以同時運行處理常式,需要加鎖保護。 rase_softirq(NET_TX_SOFTIRQ)將非強制中斷設定為掛起狀態,下次再調用do_softirq時執行。 Tasklet
基於軟體中斷實現的,靈活性強,動態建立的下半部實現機制。兩個不同類型的tasklet可以在不同處理器上運行,但相同的不可以。可以通過代碼動態註冊。 實現:基於非強制中斷
struct tasklet_struct { struct tasklet_struct *next; unsigned long sate;//(0/TASKLET_STATE_SCHED/TASKLET_STATE_RUN) atomic_t count;/*引用計數器,0允許執行,否則禁止*/ void (*func)(unsigned long);/*執行函數*/ unsigned long data;//func的參數};
調度:每個處理器有tasklet_vec和tasklet_hi_vec結構,分別為低、高優先順序tasklet_strucu鏈表 由tasklet_schedule()和tasklet_hi_schdule()進行調度 檢查tasklet是否為TASKLET_STATE_SCHED.如果是返回 調用_tasklet_schedule 儲存中斷狀態,禁止本地中斷 把需要調度的處理器加到tasklet_vec 或tasklet_hi_vec鏈表的頭上 喚起TASKLET_SOFTIRQ或TASKLET_HI_SOFTIRQ非強制中斷 恢複中斷狀態並返回 非強制中斷處理常式:
tasklet_action(),tasklet_hi_action()的操作 禁止中斷 將當前處理器置為null,清空鏈表 允許中斷 迴圈遍曆鏈表每一個待處理的tasklet
如果是多處理器系統,判斷是否TASKLET_STATE_SCHED,如果在運行,跳過。 如果未在執行,設定TASKLET_STATE_RUN。 檢查count==0,否則tasklet被禁止,跳過。 執行tasklet,清空TASKLET_STATE_RUN標誌 執行下一個tasklet 使用tasklet 聲明tasklet:
DECLARE_TASKLET(name,func,data)DECLARE_TASKLET_DISABLED(.)
tasklet_init(t, tasklet_handler, dev); 編寫處理常式:因為是依靠非強制中斷實現的,處理常式不能睡眠。Tasklet允許響應中斷。 調用tasklet_schedule(&my_tasklet);調度tasklet,實際上是標記/掛起,只要有機會,my_tasklet就會儘快執行。
tasklet_disable(&my_tasklet); tasklet_enable(&my_tasklet);禁止和啟用 折衷
頻繁中斷或tasklet頻繁發生的時候:儘快處理,使用者進程得不到響應;滯後執行,中斷處理也不快。
==》》使用低優先順序核心進程專門處理非強制中斷ksoftirqd/n
for(;;){ if(!softirq_pending(cpu)){ schedule(); } set_current_state(TASK_RUNNING); while(softirq_pending(cpu)){ do_softirq(); if(need_schdule())schedule(); }}
工作隊列:
將下半部功能交由核心線程執行,有著線程上下文環境,可以睡眠。
提供建立worker threads的介面,提供介面把需要推後執行的任務排到隊列裡,提供預設的工作者線程處理排到隊列裡的下半部工作。 實現: 資料結構 每種工作者線程有一個workqueue_struct結構 裡面有NR_CPUS個cpu_work_queue_strcut對應於每個處理器的一個工作者線程 工作用work_struct表示,含有一個執行函數fuc,每個cpu的背景工作執行緒都對應一個work_struct鏈表。
worker_thread()的核心,是一個死迴圈 線程將自己放置為休眠狀態 若鏈表為空白,休眠 不為空白,調用run_workqueue函數執行工作。
鏈表不為空白時,迴圈執行
選取下一個節點對象,擷取執行函數和參數 待處理標誌清0 調用函數 重複執行 使用: 建立推後的工作
DECLARE_WORK(name, void(func)(void), void*data);靜態
INIT_WORK(strut work_struct* task,…);動態 工作隊列處理函數
運行於進程上下文,允許響應中斷,不持有鎖,可以睡眠。不能訪問使用者空間 對工作進行調度
schedule_work(&work);提交給工作者進程
schedule_delay_work(&work,delay) 重新整理操作
flush_scheduled_work(),函數等待隊列中所有對象都被執行以後返回 建立新的工作隊列
如果預設的隊列不能滿足你的需要,你應該建立新的工作隊列和與之相對應的背景工作執行緒。 各種機制的比較
| 下半部 |
上下文 |
順序執行保障 |
| 非強制中斷 |
中斷 |
沒有 |
| Tasklet |
中斷 |
同類型不能同時執行 |
| 工作隊列 |
進程 |
沒有(和進程上下文一樣,被調度) |
如果任務需要推後到進程上下文完成,有休眠的需要 工作隊列
任務隊列介面簡單,同種類型不能同時執行 tasklet
非強制中斷提供的執行序列化的保障最少,必須格外小心採取一些步驟確保共用資料