中斷服務程式一般都是在中斷請求關閉的條件下執行的,以避免嵌套而使中斷控制複雜化。但是,中斷是一個随機事件,它随時會到來,如果關中斷的時間太長,CPU就不能及時響應其他的中斷請求,進而造成中斷的丢失。是以,核心的目标就是盡可能快的處理完中斷請求,盡其所能把更多的處理向後推遲。例如,假設一個資料塊已經達到了網線,當中斷控制器接受到這個中斷請求信号時,Linux核心隻是簡單地标志資料到來了,然後讓處理器恢複到它以前運作的狀态,其餘的處理稍後再進行(如把資料移入一個緩沖區,接受資料的程序就可以在緩沖區找到資料)。是以,核心把中斷處理分為兩部分:上半部(top half)和下半部(bottom half),上半部(就是中斷服務程式)核心立即執行,而下半部(就是一些核心函數)留着稍後處理:
首先,一個快速的“上半部”來處理硬體發出的請求,它必須在一個新的中斷産生之前終止。通常,除了在裝置和一些記憶體緩沖區(如果你的裝置用到了DMA,就不止這些)之間移動或傳送資料,确定硬體是否處于健全的狀态之外,這一部分做的工作很少。
下半部運作時是允許中斷請求的,而上半部運作時是關中斷的,這是二者之間的主要差別。
但是,核心到底什時候執行下半部,以何種方式組織下半部?這就是我們要讨論的下半部實作機制,這種機制在核心的演變過程中不斷得到改進,在以前的核心中,這個機制叫做bottom half(簡稱bh),在2.4以後的版本中有了新的發展和改進,改進的目标使下半部可以在多處理機上并行執行,并有助于驅動程式的開發者進行驅動程式的開發。下面主要介紹常用的小任務(Tasklet)機制及2.6核心中的工作隊列機制。除此之外,還簡要介紹2.4以前核心中的下半部和任務隊列機制。
1 小任務機制
這裡的小任務是指對要推遲執行的函數進行組織的一種機制。其資料結構為tasklet_struct,每個結構代表一個獨立的小任務,其定義如下:
struct tasklet_struct {
struct tasklet_struct *next; /*指向連結清單中的下一個結構*/
unsigned long state; /* 小任務的狀态 */
atomic_t count; /* 引用計數器 */
void (*func) (unsigned long); /* 要調用的函數 */
unsigned long data; /* 傳遞給函數的參數 */
};
結構中的func域就是下半部中要推遲執行的函數 ,data是它唯一的參數。
State域的取值為TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任務已被排程,正準備投入運作,TASKLET_STATE_RUN表示小任務正在運作。TASKLET_STATE_RUN隻有在多處理器系統上才使用,單處理器系統什麼時候都清楚一個小任務是不是正在運作(它要麼就是目前正在執行的代碼,要麼不是)。
Count域是小任務的引用計數器。如果它不為0,則小任務被禁止,不允許執行;隻有當它為零,小任務才被激活,并且在被設定為挂起時,小任務才能夠執行。
2 聲明和使用小任務
大多數情況下,為了控制一個尋常的硬體裝置,小任務機制是實作下半部的最佳選擇。小任務可以動态建立,使用友善,執行起來也比較快。
我們既可以靜态地建立小任務,也可以動态地建立它。選擇那種方式取決于到底是想要對小任務進行直接引用還是一個間接引用。如果準備靜态地建立一個小任務(也就是對它直接引用),使用下面兩個宏中的一個:
DECLARE_TASKLET(name, func, data)
DECLARE_TASKLET_DISABLED(name, func, data)
這兩個宏都能根據給定的名字靜态地建立一個tasklet_struct結構。當該小任務被排程以後,給定的函數func會被執行,它的參數由data給出。這兩個宏之間的差別在于引用計數器的初始值設定不同。第一個宏把建立的小任務的引用計數器設定為0,是以,該小任務處于激活狀态。另一個把引用計數器設定為1,是以該小任務處于禁止狀态。例如:
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
這行代碼其實等價于
struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0),
tasklet_handler, dev};
這樣就建立了一個名為my_tasklet的小任務,其處理程式為tasklet_handler,并且已被激活。當處理程式被調用的時候,dev就會被傳遞給它。
3 編寫自己的小任務處理程式
小任務處理程式必須符合如下的函數類型:
void tasklet_handler(unsigned long data)
由于小任務不能睡眠,是以不能在小任務中使用信号量或者其它産生阻塞的函數。但是小任務運作時可以響應中斷。
4 排程自己的小任務
通過調用tasklet_schedule()函數并傳遞給它相應的tasklt_struct指針,該小任務就會被排程以便适當的時候執行:
tasklet_schedule(&my_tasklet); /*把 my_tasklet 标記為挂起 */
在小任務被排程以後,隻要有機會它就會盡可能早的運作。在它還沒有得到運作機會之前,如果一個相同的小任務又被排程了,那麼它仍然隻會運作一次。
可以調用tasklet_disable()函數來禁止某個指定的小任務。如果該小任務目前正在執行,這個函數會等到它執行完畢再傳回。調用tasklet_enable()函數可以激活一個小任務,如果希望把以DECLARE_TASKLET_DISABLED()建立的小任務激活,也得調用這個函數,如:
tasklet_disable(&my_tasklet); /* 小任務現在被禁止,這個小任務不能運作 */
tasklet_enable(&my_tasklet); /* 小任務現在被激活 */
也可以調用tasklet_kill()函數從挂起的隊列中去掉一個小任務。該函數的參數是一個指向某個小任務的tasklet_struct的長指針。在小任務重新排程它自身的時候,從挂起的隊列中移去已排程的小任務會很有用。這個函數首先等待該小任務執行完畢,然後再将它移去。
5 tasklet的簡單用法
下面是tasklet的一個簡單應用, 以子產品的形成加載。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
static struct tasklet_struct my_tasklet;
static void tasklet_handler (unsigned long data)
{
printk(KERN_ALERT "tasklet_handler is running.\n");
}
static int __init test_init(void)
tasklet_init(&my_tasklet, tasklet_handler, 0);
tasklet_schedule(&my_tasklet);
return 0;
static void __exit test_exit(void)
tasklet_kill(&my_tasklet);
printk(KERN_ALERT "test_exit running.\n");
}
MODULE_LICENSE("GPL");
module_init
(test_init);
module_exit(test_exit);
從這個例子可以看出,所謂的小任務機制是為下半部函數的執行提供了一種執行機制,也就是說,推遲處理的事情是由tasklet_handler實作,何時執行,經由小任務機制封裝後交給核心去處理。
softirq和tasklet都屬于軟中斷,tasklet是softirq的特殊實作;
workqueue是普通的工作隊列。
1、softirq
軟中斷支援SMP,同一個softirq可以在不同的CPU上同時運作,softirq必須是可重入的。軟中斷是在編譯期間靜态配置設定的,它不像tasklet那樣能被動态的注冊或去除。kernel/softirq.c中定義了一個包含32個softirq_action結構體的數組。每個被注冊的軟中斷都占據該數組的一項。是以最多可能有32個軟中斷。2.6版本的核心中定義了六個軟中斷:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ。
一般情況下,在硬體中斷處理程式後都會試圖調用do_softirq()函數,每個CPU都是通過執行這個函數來執行軟中斷服務的。由于軟中斷不能進入硬中斷部分,且同一個CPU上軟中斷的執行是串行的,即不允許嵌套,是以,do_softirq()函數一開始就檢查目前CPU是否已經正出在中斷服務中,如果是則 do_softirq()函數立即傳回。這是由do_softirq()函數中的 if (in_interrupt()) return; 保證的。
2、tasklet
引入tasklet,最主要的是考慮支援SMP,提高SMP多個cpu的使用率;不同的tasklet可以在不同的cpu上運作。tasklet可以了解為softirq的派生,是以它的排程時機和軟中斷一樣。對于核心中需要延遲執行的多數任務都可以用tasklet來完成,由于同類tasklet本身已經進行了同步保護,是以使用tasklet比軟中斷要簡單的多,而且效率也不錯。tasklet把任務延遲到安全時間執行的一種方式,在中斷期間運作,即使被排程多次,tasklet也隻運作一次,不過tasklet可以在SMP系統上和其他不同的tasklet并行運作。在SMP系統上,tasklet還被確定在第一個排程它的CPU上運作,因為這樣可以提供更好的高速緩存行為,進而提高性能。
與一般的軟中斷不同,某一段tasklet代碼在某個時刻隻能在一個CPU上運作,但不同的tasklet代碼在同一時刻可以在多個CPU上并發地執行。Kernel/softirq.c中用tasklet_trylock()宏試圖對目前要執行的tasklet(由指針t所指向)進行加鎖,如果加鎖成功(目前沒有任何其他CPU正在執行這個tasklet),則用原子讀函數atomic_read()進一步判斷count成員的值。如果count為0,說明這個tasklet是允許執行的。如果tasklet_trylock()宏加鎖不成功,或者因為目前tasklet的count值非0而不允許執行時,我們必須将這個tasklet重新放回到目前CPU的tasklet隊列中,以留待這個CPU下次服務軟中斷向量TASKLET_SOFTIRQ時再執行。為此進行這樣幾步操作:(1)先關 CPU中斷,以保證下面操作的原子性。(2)把這個tasklet重新放回到目前CPU的tasklet隊列的首部;(3)調用__cpu_raise_softirq()函數在目前CPU上再觸發一次軟中斷請求TASKLET_SOFTIRQ;(4)開中斷。
軟中斷和tasklet都是運作在中斷上下文中,它們與任一程序無關,沒有支援的程序完成重新排程。是以軟中斷和tasklet不能睡眠、不能阻塞,它們的代碼中不能含有導緻睡眠的動作,如減少信号量、從使用者空間拷貝資料或手工配置設定記憶體等。也正是由于它們運作在中斷上下文中,是以它們在同一個CPU上的執行是串行的,這樣就不利于實時多媒體任務的優先處理。
本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/8043190.html,如需轉載請自行聯系原作者