Tasklet機(jī)制是一種較為特殊的軟中斷。Tasklet一詞的原意是“小片任務(wù)”的意思,這里是指一小段可執(zhí)行的代碼,且通常以函數(shù)的形式出現(xiàn)。軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet機(jī)制來(lái)實(shí)現(xiàn)的。
從某種程度上講,tasklet機(jī)制是Linux內(nèi)核對(duì)BH機(jī)制的一種擴(kuò)展。在2.4內(nèi)核引入了softirq機(jī)制后,原有的BH機(jī)制正是通過(guò)tasklet機(jī)制這個(gè)橋梁來(lái)納入softirq機(jī)制的整體框架中的。正是由于這種歷史的延伸關(guān)系,使得tasklet機(jī)制與一般意義上的軟中斷有所不同,而呈現(xiàn)出以下兩個(gè)顯著的特點(diǎn):
1. 與一般的軟中斷不同,某一段tasklet代碼在某個(gè)時(shí)刻只能在一個(gè)CPU上運(yùn)行,而不像一般的軟中斷服務(wù)函數(shù)(即softirq_action結(jié)構(gòu)中的action函數(shù)指針)那樣——在同一時(shí)刻可以被多個(gè)CPU并發(fā)地執(zhí)行。
2. 與BH機(jī)制不同,不同的tasklet代碼在同一時(shí)刻可以在多個(gè)CPU上并發(fā)地執(zhí)行,而不像BH機(jī)制那樣必須嚴(yán)格地串行化執(zhí)行(也即在同一時(shí)刻系統(tǒng)中只能有一個(gè)CPU執(zhí)行BH函數(shù))。
Linux用數(shù)據(jù)結(jié)構(gòu)tasklet_struct來(lái)描述一個(gè)tasklet。該數(shù)據(jù)結(jié)構(gòu)定義在include/linux/interrupt.h頭文件中。如下所示:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
各成員的含義如下:
(1)next指針:指向下一個(gè)tasklet的指針。
(2)state:定義了這個(gè)tasklet的當(dāng)前狀態(tài)。這一個(gè)32位的無(wú)符號(hào)長(zhǎng)整數(shù),當(dāng)前只使用了bit[1]和bit[0]兩個(gè)狀態(tài)位。其中,bit[1]=1表示這個(gè)tasklet當(dāng)前正在某個(gè)CPU上被執(zhí)行,它僅對(duì)SMP系統(tǒng)才有意義,其作用就是為了防止多個(gè)CPU同時(shí)執(zhí)行一個(gè)tasklet的情形出現(xiàn);bit[0]=1表示這個(gè)tasklet已經(jīng)被調(diào)度去等待執(zhí)行了。對(duì)這兩個(gè)狀態(tài)位的宏定義如下所示(interrupt.h):
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
(3)原子計(jì)數(shù)count:對(duì)這個(gè)tasklet的引用計(jì)數(shù)值。NOTE!只有當(dāng)count等于0時(shí),tasklet代碼段才能執(zhí)行,也即此時(shí)tasklet是被使能的;如果count非零,則這個(gè)tasklet是被禁止的。任何想要執(zhí)行一個(gè)tasklet代碼段的人都首先必須先檢查其count成員是否為0。
(4)函數(shù)指針func:指向以函數(shù)形式表現(xiàn)的可執(zhí)行tasklet代碼段。
(5)data:函數(shù)func的參數(shù)。這是一個(gè)32位的無(wú)符號(hào)整數(shù),其具體含義可供func函數(shù)自行解釋,比如將其解釋成一個(gè)指向某個(gè)用戶自定義數(shù)據(jù)結(jié)構(gòu)的地址值。
Linux在interrupt.h頭文件中又定義了兩個(gè)用來(lái)定義tasklet_struct結(jié)構(gòu)變量的輔助宏:
#define DECLARE_TASKLET(name, func, data)
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data)
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
顯然,從上述源代碼可以看出,用DECLARE_TASKLET宏定義的tasklet在初始化時(shí)是被使能的(enabled),因?yàn)槠鋍ount成員為0。而用DECLARE_TASKLET_DISABLED宏定義的tasklet在初始時(shí)是被禁止的(disabled),因?yàn)槠鋍ount等于1。
在這里,tasklet狀態(tài)指兩個(gè)方面:1. state成員所表示的運(yùn)行狀態(tài);2. count成員決定的使能/禁止?fàn)顟B(tài)。
(1)改變一個(gè)tasklet的運(yùn)行狀態(tài)state成員中的bit[0]表示一個(gè)tasklet是否已被調(diào)度去等待執(zhí)行,bit[1]表示一個(gè)tasklet是否正在某個(gè)CPU上執(zhí)行。對(duì)于state變量中某位的改變必須是一個(gè)原子操作,因此可以用定義在include/asm/bitops.h頭文件中的位操作來(lái)進(jìn)行。
由于bit[1]這一位(即TASKLET_STATE_RUN)僅僅對(duì)于SMP系統(tǒng)才有意義,因此Linux在Interrupt.h頭文件中顯示地定義了對(duì)TASKLET_STATE_RUN位的操作。如下所示:
#ifdef CONFIG_SMP
#define tasklet_trylock(t) (!test_and_set_bit(TASKLET_STATE_RUN, &(t)->state))
#define tasklet_unlock_wait(t) while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { /* NOTHING */ }
#define tasklet_unlock(t) clear_bit(TASKLET_STATE_RUN, &(t)->state)
#else
#define tasklet_trylock(t) 1
#define tasklet_unlock_wait(t) do { } while (0)
#define tasklet_unlock(t) do { } while (0)
#endif
顯然,在SMP系統(tǒng)同,tasklet_trylock()宏將把一個(gè)tasklet_struct結(jié)構(gòu)變量中的state成員中的bit[1]位設(shè)置成1,同時(shí)還返回bit[1]位的非。因此,如果bit[1]位原有值為1(表示另外一個(gè)CPU正在執(zhí)行這個(gè)tasklet代碼),那么tasklet_trylock()宏將返回值0,也就表示上鎖不成功。如果bit[1]位的原有值為0,那么tasklet_trylock()宏將返回值1,表示加鎖成功。而在單CPU系統(tǒng)中,tasklet_trylock()宏總是返回為1。
任何想要執(zhí)行某個(gè)tasklet代碼的程序都必須首先調(diào)用宏tasklet_trylock()來(lái)試圖對(duì)這個(gè)tasklet進(jìn)行上鎖(即設(shè)置TASKLET_STATE_RUN位),且只能在上鎖成功的情況下才能執(zhí)行這個(gè)tasklet。建議!即使你的程序只在CPU系統(tǒng)上運(yùn)行,你也要在執(zhí)行tasklet之前調(diào)用tasklet_trylock()宏,以便使你的代碼獲得良好可移植性。
在SMP系統(tǒng)中,tasklet_unlock_wait()宏將一直不停地測(cè)試TASKLET_STATE_RUN位的值,直到該位的值變?yōu)?(即一直等待到解鎖),假如:CPU0正在執(zhí)行tasklet A的代碼,在此期間,CPU1也想執(zhí)行tasklet A的代碼,但CPU1發(fā)現(xiàn)tasklet A的TASKLET_STATE_RUN位為1,于是它就可以通過(guò)tasklet_unlock_wait()宏等待tasklet A被解鎖(也即TASKLET_STATE_RUN位被清零)。在單CPU系統(tǒng)中,這是一個(gè)空操作。
宏tasklet_unlock()用來(lái)對(duì)一個(gè)tasklet進(jìn)行解鎖操作,也即將TASKLET_STATE_RUN位清零。在單CPU系統(tǒng)中,這是一個(gè)空操作。
(2)使能/禁止一個(gè)tasklet
使能與禁止操作往往總是成對(duì)地被調(diào)用的,tasklet_disable()函數(shù)如下
(interrupt.h):
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
}
函數(shù)tasklet_disable_nosync()也是一個(gè)靜態(tài)inline函數(shù),它簡(jiǎn)單地通過(guò)原子操作將count成員變量的值減1。如下所示(interrupt.h):
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
}
函數(shù)tasklet_enable()用于使能一個(gè)tasklet,如下所示(interrupt.h):
static inline void tasklet_enable(struct tasklet_struct *t)
{
atomic_dec(&t->count);
}
函數(shù)tasklet_init()用來(lái)初始化一個(gè)指定的tasklet描述符,其源碼如下所示(kernel/softirq.c):
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data)
{
t->func = func;
t->data = data;
t->state = 0;
atomic_set(&t->count, 0);
}
函數(shù)tasklet_kill()用來(lái)將一個(gè)已經(jīng)被調(diào)度了的tasklet殺死,即將其恢復(fù)到未調(diào)度的狀態(tài)。其源碼如下所示(kernel/softirq.c):
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interruptn");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
current->state = TASK_RUNNING;
do {
current->policy |= SCHED_YIELD;
schedule();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
多個(gè)tasklet可以通過(guò)tasklet描述符中的next成員指針鏈接成一個(gè)單向?qū)α?。為此,Linux專門在頭文件include/linux/interrupt.h中定義了數(shù)據(jù)結(jié)構(gòu)tasklet_head來(lái)描述一個(gè)tasklet對(duì)列的頭部指針。如下所示:
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
盡管tasklet機(jī)制是特定于軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一種實(shí)現(xiàn),但是tasklet機(jī)制仍然屬于softirq機(jī)制的整體框架范圍內(nèi)的,因此,它的設(shè)計(jì)與實(shí)現(xiàn)仍然必須堅(jiān)持“誰(shuí)觸發(fā),誰(shuí)執(zhí)行”的思想。為此,Linux為系統(tǒng)中的每一個(gè)CPU都定義了一個(gè)tasklet對(duì)列頭部,來(lái)表示應(yīng)該有各個(gè)CPU負(fù)責(zé)執(zhí)行的tasklet對(duì)列。如下所示(kernel/softirq.c):
struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
其中,tasklet_vec[]數(shù)組用于軟中斷向量TASKLET_SOFTIRQ,而tasklet_hi_vec[]數(shù)組則用于軟中斷向量HI_SOFTIRQ。也即,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量TASKLET_SOFTIRQ,那么對(duì)列tasklet_vec[i]中的每一個(gè)tasklet都將在CPUi服務(wù)于軟中斷向量TASKLET_SOFTIRQ時(shí)被CPUi所執(zhí)行。同樣地,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量HI_SOFTIRQ,那么隊(duì)列tasklet_vec[i]中的每一個(gè)tasklet都將CPUi在對(duì)軟中斷向量HI_SOFTIRQ進(jìn)行服務(wù)時(shí)被CPUi所執(zhí)行。
隊(duì)列tasklet_vec[I]和tasklet_hi_vec[I]中的各個(gè)tasklet是怎樣被所CPUi所執(zhí)行的呢?其關(guān)鍵就是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務(wù)程序——tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。下面我們就來(lái)分析這兩個(gè)函數(shù)。
Linux為軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ實(shí)現(xiàn)了專用的觸發(fā)函數(shù)和軟中斷服務(wù)函數(shù)。其中,tasklet_schedule()函數(shù)和tasklet_hi_schedule()函數(shù)分別用來(lái)在當(dāng)前CPU上觸發(fā)軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入當(dāng)前CPU所對(duì)應(yīng)的tasklet隊(duì)列中去等待執(zhí)行。而tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)則分別是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務(wù)函數(shù)。在初始化函數(shù)softirq_init()中,這兩個(gè)軟中斷向量對(duì)應(yīng)的描述符softirq_vec[0]和softirq_vec[3]中的action函數(shù)指針就被分別初始化成指向函數(shù)tasklet_hi_action()和函數(shù)tasklet_action()。
(1)軟中斷向量TASKLET_SOFTIRQ的觸發(fā)函數(shù)tasklet_schedule()
該函數(shù)實(shí)現(xiàn)在include/linux/interrupt.h頭文件中,是一個(gè)inline函數(shù)。其源碼如下所示:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}
該函數(shù)的參數(shù)t指向要在當(dāng)前CPU上被執(zhí)行的tasklet。對(duì)該函數(shù)的NOTE如下:
①調(diào)用test_and_set_bit()函數(shù)將待調(diào)度的tasklet的state成員變量的bit[0]位(也即TASKLET_STATE_SCHED位)設(shè)置為1,該函數(shù)同時(shí)還返回TASKLET_STATE_SCHED位的原有值。因此如果bit[0]為的原有值已經(jīng)為1,那就說(shuō)明這個(gè)tasklet已經(jīng)被調(diào)度到另一個(gè)CPU上去等待執(zhí)行了。由于一個(gè)tasklet在某一個(gè)時(shí)刻只能由一個(gè)CPU來(lái)執(zhí)行,因此tasklet_schedule()函數(shù)什么也不做就直接返回了。否則,就繼續(xù)下面的調(diào)度操作。
②首先,調(diào)用local_irq_save()函數(shù)來(lái)關(guān)閉當(dāng)前CPU的中斷,以保證下面的步驟在當(dāng)前CPU上原子地被執(zhí)行。
③然后,將待調(diào)度的tasklet添加到當(dāng)前CPU對(duì)應(yīng)的tasklet隊(duì)列的首部。
④接著,調(diào)用__cpu_raise_softirq()函數(shù)在當(dāng)前CPU上觸發(fā)軟中斷請(qǐng)求TASKLET_SOFTIRQ。
⑤最后,調(diào)用local_irq_restore()函數(shù)來(lái)開當(dāng)前CPU的中斷。
(2)軟中斷向量TASKLET_SOFTIRQ的服務(wù)程序tasklet_action()
函數(shù)tasklet_action()是tasklet機(jī)制與軟中斷向量TASKLET_SOFTIRQ的聯(lián)系紐帶。正是該函數(shù)將當(dāng)前CPU的tasklet隊(duì)列中的各個(gè)tasklet放到當(dāng)前CPU上來(lái)執(zhí)行的。該函數(shù)實(shí)現(xiàn)在kernel/softirq.c文件中,其源代碼如下:
static void tasklet_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = NULL;
local_irq_enable();
while (list != NULL) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (atomic_read(&t->count) == 0) {
clear_bit(TASKLET_STATE_SCHED, &t->state);
t->func(t->data);
/*
* talklet_trylock() uses test_and_set_bit that imply
* an mb when it returns zero, thus we need the explicit
* mb only here: while closing the critical section.
*/
#ifdef CONFIG_SMP
smp_mb__before_clear_bit();
#endif
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_enable();
}
}
注釋如下:
①首先,在當(dāng)前CPU關(guān)中斷的情況下,“原子”地讀取當(dāng)前CPU的tasklet隊(duì)列頭部指針,將其保存到局部變量list指針中,然后將當(dāng)前CPU的tasklet隊(duì)列頭部指針設(shè)置為NULL,以表示理論上當(dāng)前CPU將不再有tasklet需要執(zhí)行(但最后的實(shí)際結(jié)果卻并不一定如此,下面將會(huì)看到)。
②然后,用一個(gè)while{}循環(huán)來(lái)遍歷由list所指向的tasklet隊(duì)列,隊(duì)列中的各個(gè)元素就是將在當(dāng)前CPU上執(zhí)行的tasklet。循環(huán)體的執(zhí)行步驟如下:
用指針t來(lái)表示當(dāng)前隊(duì)列元素,即當(dāng)前需要執(zhí)行的tasklet。
更新list指針為list->next,使它指向下一個(gè)要執(zhí)行的tasklet。
用tasklet_trylock()宏試圖對(duì)當(dāng)前要執(zhí)行的tasklet(由指針t所指向)進(jìn)行加鎖,如果加鎖成功(當(dāng)前沒(méi)有任何其他CPU正在執(zhí)行這個(gè)tasklet),則用原子讀函數(shù)atomic_read()進(jìn)一步判斷count成員的值。如果count為0,說(shuō)明這個(gè)tasklet是允許執(zhí)行的,于是:(1)先清除TASKLET_STATE_SCHED位;(2)然后,調(diào)用這個(gè)tasklet的可執(zhí)行函數(shù)func;(3)執(zhí)行barrier()操作;(4)調(diào)用宏tasklet_unlock()來(lái)清除TASKLET_STATE_RUN位。(5)最后,執(zhí)行continue語(yǔ)句跳過(guò)下面的步驟,回到while循環(huán)繼續(xù)遍歷隊(duì)列中的下一個(gè)元素。如果count不為0,說(shuō)明這個(gè)tasklet是禁止運(yùn)行的,于是調(diào)用tasklet_unlock()清除前面用tasklet_trylock()設(shè)置的TASKLET_STATE_RUN位。
如果tasklet_trylock()加鎖不成功,或者因?yàn)楫?dāng)前tasklet的count值非0而不允許執(zhí)行時(shí),我們必須將這個(gè)tasklet重新放回到當(dāng)前CPU的tasklet隊(duì)列中,以留待這個(gè)CPU下次服務(wù)軟中斷向量TASKLET_SOFTIRQ時(shí)再執(zhí)行。為此進(jìn)行這樣幾步操作:(1)先關(guān)CPU中斷,以保證下面操作的原子性。(2)把這個(gè)tasklet重新放回到當(dāng)前CPU的tasklet隊(duì)列的首部;(3)調(diào)用__cpu_raise_softirq()函數(shù)在當(dāng)前CPU上再觸發(fā)一次軟中斷請(qǐng)求TASKLET_SOFTIRQ;(4)開中斷。
最后,回到while循環(huán)繼續(xù)遍歷隊(duì)列。
(3)軟中斷向量HI_SOFTIRQ的觸發(fā)函數(shù)tasklet_hi_schedule()
該函數(shù)與tasklet_schedule()幾乎相同,其源碼如下(include/linux/interrupt.h):
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}
}
(4)軟中斷向量HI_SOFTIRQ的服務(wù)函數(shù)tasklet_hi_action()
該函數(shù)與tasklet_action()函數(shù)幾乎相同,其源碼如下(kernel/softirq.c):
static void tasklet_hi_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = NULL;
local_irq_enable();
while (list != NULL) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (atomic_read(&t->count) == 0) {
clear_bit(TASKLET_STATE_SCHED, &t->state);
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();
}
}
Bottom Half機(jī)制在新的softirq機(jī)制中被保留下來(lái),并作為softirq框架的一部分。其實(shí)現(xiàn)也似乎更為復(fù)雜些,因?yàn)樗峭ㄟ^(guò)tasklet機(jī)制這個(gè)中介橋梁來(lái)納入softirq框架中的。實(shí)際上,軟中斷向量HI_SOFTIRQ是內(nèi)核專用于執(zhí)行BH函數(shù)的。原有的32個(gè)BH函數(shù)指針被保留,定義在kernel/softirq.c文件中:
static void (*bh_base[32])(void);
但是,每個(gè)BH函數(shù)都對(duì)應(yīng)有一個(gè)tasklet,并由tasklet的可執(zhí)行函數(shù)func來(lái)負(fù)責(zé)調(diào)用相應(yīng)的bh函數(shù)(func函數(shù)的參數(shù)指定調(diào)用哪一個(gè)BH函數(shù))。與32個(gè)BH函數(shù)指針相對(duì)應(yīng)的tasklet的定義如下所示(kernel/softirq.c):
struct tasklet_struct bh_task_vec[32];
上述tasklet數(shù)組使系統(tǒng)全局的,它對(duì)所有的CPU均可見。由于在某一個(gè)時(shí)刻只能有一個(gè)CPU在執(zhí)行BH函數(shù),因此定義一個(gè)全局的自旋鎖來(lái)保護(hù)BH函數(shù),如下所示(kernel/softirq.c):
spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED;
在softirq機(jī)制的初始化函數(shù)softirq_init()中將bh_task_vec[32]數(shù)組中的每一個(gè)tasklet中的func函數(shù)指針都設(shè)置為指向同一個(gè)函數(shù)bh_action,而data成員(也即func函數(shù)的調(diào)用參數(shù))則被設(shè)置成該tasklet在數(shù)組中的索引值,如下所示:
void __init softirq_init()
{
……
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
……
}
因此,bh_action()函數(shù)將負(fù)責(zé)相應(yīng)地調(diào)用參數(shù)所指定的bh函數(shù)。該函數(shù)是連接tasklet機(jī)制與Bottom Half機(jī)制的關(guān)鍵所在。
該函數(shù)的源碼如下(kernel/softirq.c):
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
對(duì)該函數(shù)的注釋如下:
①首先,調(diào)用spin_trylock()函數(shù)試圖對(duì)自旋鎖global_bh_lock進(jìn)行加鎖,同時(shí)該函數(shù)還將返回自旋鎖global_bh_lock的原有值的非。因此,如果global_bh_lock已被某個(gè)CPU上鎖而為非0值(那個(gè)CPU肯定在執(zhí)行某個(gè)BH函數(shù)),那么spin_trylock()將返回為0表示上鎖失敗,在這種情況下,當(dāng)前CPU是不能執(zhí)行BH函數(shù)的,因?yàn)榱硪粋€(gè)CPU正在執(zhí)行BH函數(shù),于是執(zhí)行g(shù)oto語(yǔ)句跳轉(zhuǎn)到resched程序段,以便在當(dāng)前CPU上再一次調(diào)度該BH函數(shù)。
②調(diào)用hardirq_trylock()函數(shù)鎖定當(dāng)前CPU,確保當(dāng)前CPU不是處于硬件中斷請(qǐng)求服務(wù)中,如果鎖定失敗,跳轉(zhuǎn)到resched_unlock程序段,以便先對(duì)global_bh_lock解鎖,在重新調(diào)度一次該BH函數(shù)。
③此時(shí),我們已經(jīng)可以放心地在當(dāng)前CPU上執(zhí)行BH函數(shù)了。當(dāng)然,對(duì)應(yīng)的BH函數(shù)指針bh_base[nr]必須有效才行。
④從BH函數(shù)返回后,先調(diào)用hardirq_endlock()函數(shù)(實(shí)際上它什么也不干,調(diào)用它只是為了保此加、解鎖的成對(duì)關(guān)系),然后解除自旋鎖global_bh_lock,最后函數(shù)就可以返回了。
⑤resched_unlock程序段:先解除自旋鎖global_bh_lock,然后執(zhí)行reched程序段。
⑥r(nóng)esched程序段:當(dāng)某個(gè)CPU正在執(zhí)行BH函數(shù)時(shí),當(dāng)前CPU就不能通過(guò)bh_action()函數(shù)來(lái)調(diào)用執(zhí)行任何BH函數(shù),所以就通過(guò)調(diào)用mark_bh()函數(shù)在當(dāng)前CPU上再重新調(diào)度一次,以便將這個(gè)BH函數(shù)留待下次軟中斷服務(wù)時(shí)執(zhí)行。
(1)init_bh()函數(shù)
該函數(shù)用來(lái)在bh_base[]數(shù)組登記一個(gè)指定的bh函數(shù),如下所示(kernel/softirq.c):
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
(2)remove_bh()函數(shù)
該函數(shù)用來(lái)在bh_base[]數(shù)組中注銷指定的函數(shù)指針,同時(shí)將相對(duì)應(yīng)的tasklet殺掉。
如下所示(kernel/softirq.c):
void remove_bh(int nr)
{
tasklet_kill(bh_task_vec+nr);
bh_base[nr] = NULL;
}
(3)mark_bh()函數(shù)
該函數(shù)用來(lái)向當(dāng)前CPU標(biāo)記由一個(gè)BH函數(shù)等待去執(zhí)行。它實(shí)際上通過(guò)調(diào)用tasklet_hi_schedule()函數(shù)將相應(yīng)的tasklet加入到當(dāng)前CPU的tasklet隊(duì)列tasklet_hi_vec[cpu]中,然后觸發(fā)軟中斷請(qǐng)求HI_SOFTIRQ,如下所示(include/linux/interrupt.h):
static inline void mark_bh(int nr){tasklet_hi_schedule(bh_task_vec+nr);}
在32個(gè)BH函數(shù)指針中,大多數(shù)已經(jīng)固定用于一些常見的外設(shè),比如:第0個(gè)BH函數(shù)就固定地用于時(shí)鐘中斷。Linux在頭文件include/linux/interrupt.h中定義了這些已經(jīng)被使用的BH函數(shù)所引,如下所示:
enum {
TIMER_BH = 0,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};