1. 程式人生 > >Linux內核設計基礎(一)之中斷處理

Linux內核設計基礎(一)之中斷處理

family ng- 內存 irq strong 睡眠 sign 技術 struct

假設讓內核定期對設備進行輪詢。以便處理設備,那會做非常多無用功,假設能讓設備在須要內核時主動通知內核,會是一個聰明的方式,這便是中斷。

在響應一個特定中斷時,內核會運行一個函數——中斷處理程序。

中斷處理程序與其它內核函數的差別在於,中斷處理程序是被內核調用來響應中斷的,而它們運行於我們稱之為中斷上下文的特殊上下文中。

我們期望讓中斷處理程序運行得快。並想讓它完畢的工作量多,這兩個目標相互制約,怎樣解決——上下半部機制

我們把中斷處理切為兩半。我們用網卡來解釋一下這兩半。

當網卡接受到數據包時,通知內核,觸發中斷。所謂的上半部就是,及時讀取數據包到內存。防止由於延遲導致丟失,這是非常急迫的工作。讀到內存後,對這些數據的處理不再緊迫,此時內核能夠去運行中斷前運行的程序,而對網絡數據包的處理則交給下半部處理。

我們先來看一下上半部的處理過程。

中斷處理程序的註冊與註銷

設備驅動程序利用request_irq()註冊中斷處理程序。並激活給定的中斷線。

int request_irq(unsigned int irq,
                         irq_handler_t handler,
                         unsigned long flags,
                         const char *name,
                         void *dev)

irq表示中斷號,handler是指向中斷處理程序的指針。request_irq()成功運行返回0,當返回非0值時,表示有發生錯誤,中斷處理程序不會被註冊。

卸載設備驅動程序時,須要註銷對應的中斷處理程序,並釋放中斷線。這時須要調用free_irq——假設在給定的中斷線上沒有中斷處理程序,則註銷響應的處理程序。並禁用當中斷線。

中斷處理機制

技術分享圖片

下半部嚴格來說不屬於中斷處理程序(由於中斷返回後再運行下半部),它是中斷處理程序用來縮減自身工作的分擔者。

上下半部劃分原則

(1)假設一個任務對時間非常敏感。將其放在中斷處理程序中運行;

(2)假設一個任務和硬件有關,將其放在中斷處理程序中運行;

(3)假設一個任務要保證不被其它中斷打斷。將其放在中斷處理程序中運行;

(4)其它全部任務。考慮放置在下半部運行。

上下半部的意義

上半部簡單高速。運行時禁止一些或者全部中斷。下半部稍後運行,並且運行期間能夠響應全部的中斷。這樣的設計能夠使系統處於中斷屏蔽狀態的時間盡可能的短,以此來提高系統的響應能力。

下半部實現機制之軟中斷

在中斷處理程序中觸發軟中斷是最常見的形式。在這樣的情況下。中斷處理程序運行硬件設備的相關操作。然後觸發對應的軟中斷,最後退出。

內核在運行完中斷處理程序後,立即就會調用do_softirq()函數,於是軟中斷開始運行中斷處理程序留給它去完畢的剩余任務。

軟中斷註冊方式例如以下:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

前面的參數是軟中斷的索引號。後面的是處理函數。軟中斷處理程序運行時。同意響應中斷,但它自己不能休眠。

下半部實現機制之tasklet

tasklet是通過軟中斷實現的,所以它本身也是軟中斷。

首先聲明自己的tasklet,DECLARE_TASKLET(name, func, data),當該tasklet被調度後。給定的函數func會被運行。它的參數由data給出。接下來定義tasklet處理程序void tasklet_handler(unsigned long data),然後開始調度。tasklet由tasklet_schedule()和tasklet_hi_schedule()進行調度。

tasklet_schedule()的運行步驟:

(1)檢查tasklet的狀態是否為TASKLET_STATE_SCHED。假設是,說明tasklet已經被調度過了,函數立即返回。

(2)調用_tasklet_schedule()。

(3)保存中斷狀態,然後禁止本地中斷。在我們運行tasklet代碼時,這麽做能夠保證當tasklet_schedule()處理這些tasklet時,處理器上的數據不會弄亂。

(4)把須要調度的tasklet加到每一個處理器一個的tasklet_vec鏈表或tasklet_hi_vec鏈表的表頭上。

(5)喚醒TASKLET_SOFTIRQ或HI_SOFTIRQ軟中斷,這樣在下一次調用do_softirq()時就會運行該tasklet。

(6)恢復中斷到原狀態並返回。

下半部實現機制之工作隊列(work queue)

假設推後運行的任務須要睡眠。那麽就選擇工作隊列,假設不須要睡眠,那麽就選擇軟中斷或tasklet。

工作隊列能運行在進程上下文,它將工作委托給一個內核線程。我們用結構體workqueue_struct表示工作者線程,工作者線程是用內核線程實現的。而工作者線程是怎樣運行被推後的工作——有這樣一個鏈表。它由結構體work_struct組成,而這個work_struct則描寫敘述了一個工作,一旦這個工作被運行完。對應的work_struct對象就從鏈表上移去,當鏈表上不再有對象時,工作者線程就會繼續休眠。這些邏輯是通過函數worker_thread()實現的:

(1)線程將自己設置為休眠狀態。並把自己增加到等待隊列中。

(2)假設工作鏈表是空的。線程調用schedule()函數進入休眠狀態。

(3)假設鏈表中有對象,線程不會休眠。相反。它會脫離等待隊列。

(4)假設鏈表非空,調用run_workqueue()運行被推後的工作。

另外,cpu_workqueue_struct表示一個工作者線程。而workqueue_struct表示一類工作者線程。

創建工作者線程,DECLARE_WORK(name, void (*func) (void *), void *data)或INIT_WORK(struct work_struct *work, void (*func) (void *), void *data),前者是靜態創建,後者在運行時通過指針創建。

工作者線程創建了,接下來應該定義它要運行的函數work_handler。之後就是用schedule_work(&work)來調度工作線程的喚醒與休眠。

Linux內核設計基礎(一)之中斷處理