Linux tasklet 分析筆記

來源:互聯網
上載者:User

Linux tasklet 分析筆記

    Chapter 1:

驅動程式在初始化時,通過函數task_init建立一個tasklet,然後調用函數tasklet_schedule將這個tasklet放在 tasklet_vec鏈表的頭部,並喚醒後台線程ksoftirqd。當後台線程ksoftirqd運行調用__do_softirq時,會執行在中斷向量表softirq_vec裡中斷號TASKLET_SOFTIRQ對應的tasklet_action函數,然後tasklet_action遍曆 tasklet_vec鏈表,調用每個tasklet的函數完成非強制中斷操作。

下面對函數tasklet_init和tasklet_schedule分析:

函數tasklet_init初始化一個tasklet,其參數t是tasklet_struct結構描述的tasklet,參數(*func)是非強制中斷響應函數。

void tasklet_init(struct tasklet_struct *t,  void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}

驅動程式調用函數tasklet_schedule來運行tasklet。

static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
  __tasklet_schedule(t);
}

函數__tasklet_schedule得到當前CPU的tasklet_vec鏈表,並執行TASKLET_SOFTIRQ非強制中斷。

void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;

local_irq_save(flags);
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}

函數raise_softirq_irqoff設定非強制中斷nr為掛起狀態,並在沒有中斷時喚醒線程ksoftirqd。函數raise_softirq_irqoff必須在關中斷情況下運行。

inline fastcall void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);

/*
  * If we're in an interrupt or softirq, we're done
  * (this also catches softirq-disabled code). We will
  * actually run the softirq once we return from
  * the irq or softirq.
  *
  * Otherwise we wake up ksoftirqd to make sure we
  * schedule the softirq soon.
  */
if (!in_interrupt())
  wakeup_softirqd();
}

下面是tasklet_struct和softirq_action的定義。

struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};

struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};

摘錄於《Linux核心分析及編程>
http://hi.baidu.com/ryderlee/blog/item/ceeec316e8d1f318962b431a.html
1.Tasklet 可被hi-schedule和一般schedule,hi-schedule一定比一般shedule早運行;
2.同一個Tasklet可同時被hi-schedule和一般schedule;
3.同一個Tasklet若被同時hi-schedule多次,等同於只hi-shedule一次,因為,在tasklet未 運行時,hi- 
     shedule同一tasklet無意義,會衝掉前一個tasklet;
4.對於一般shedule, 同上。
5.不同的tasklet不按先後shedule順序運行,而是並行運行。
6.Tasklet的已耗用時間:
         a.若在中斷中schedule tasklet, 中斷結束後立即運行;
         b.若CPU忙,在不在此次中斷後立即運行;
         c.不在中斷中shedule tasklet;
         d.有軟或硬中斷在運行;
         e.從系統調用中返回;(僅當process閑時)
         f.從異常中返回;
         g.偵錯工具調度。(ksoftirqd運行時,此時CPU閑)
7.Taskelet的hi-schedule 使用softirq 0, 一般schedule用softirq 30;
8.Tasklet的已耗用時間最完在下一次time tick 時。(因為最外層中斷一定會運行使能的softirq, 面不在中斷中便能或shedule的softirq在下一定中斷後一定會被調用。)
綜上: Tasklet 能保證的已耗用時間是(1000/HZ)ms,一般是10ms。Tasklet在CPU閑或中斷後被調用。

Chapter 2:

Tasklet機制是一種較為特殊的非強制中斷。Tasklet一詞的原意是“小片任務”的意思,這
裡是指一小段可執行檔代碼,且通常以函數的形式出現。非強制中斷向量HI_SOFTIRQ和
TASKLET_SOFTIRQ均是用tasklet機制來實現的。
從某種程度上講,tasklet機制是Linux核心對BH機制的一種擴充。在2.4核心引入了
softirq機制後,原有的BH機制正是通過tasklet機制這個橋樑來納入softirq機制的整體
架構中的。正是由於這種曆史的延伸關係,使得tasklet機制與一般意義上的非強制中斷有所
不同,而呈現出以下兩個顯著的特點:
1. 與一般的非強制中斷不同,某一段tasklet代碼在某個時刻只能在一個CPU上運行,而不像
一般的非強制中斷服務函數(即softirq_action結構中的action函數指標)那樣——在同一
時刻可以被多個CPU並發地執行。
2. 與BH機制不同,不同的tasklet代碼在同一時刻可以在多個CPU上並發地執行,而不像
BH機制那樣必須嚴格地序列化執行(也即在同一時刻系統中只能有一個CPU執行BH函
數)。
Linux用資料結構tasklet_struct來描述一個tasklet。該資料結構定義在
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指標:指向下一個tasklet的指標。
(2)state:定義了這個tasklet的目前狀態。這一個32位的無符號長整數,當前只使用
了bit[1]和bit[0]兩個狀態位。其中,bit[1]=1表示這個tasklet當前正在某個
CPU上被執行,它僅對SMP系統才有意義,其作用就是為了防止多個CPU同時執行一個
tasklet的情形出現;bit[0]=1表示這個tasklet已經被調度去等待執行了。對這兩個
狀態位的宏定義如下所示(interrupt.h):
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
(3)原子計數count:對這個tasklet的引用計數值。NOTE!只有當count等於0時,
tasklet程式碼片段才能執行,也即此時tasklet是被使能的;如果count非零,則這個
tasklet是被禁止的。任何想要執行一個tasklet程式碼片段的人都首先必須先檢查其count成
員是否為0。
(4)函數指標func:指向以函數形式表現的可執行tasklet程式碼片段。
(5)data:函數func的參數。這是一個32位的不帶正負號的整數,其具體含義可供func函數自
行解釋,比如將其解釋成一個指向某個使用者自訂資料結構的地址值。
Linux在interrupt.h標頭檔中又定義了兩個用來定義tasklet_struct結構變數的輔助
宏:
#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在初始化時是被使
能的(enabled),因為其count成員為0。而用DECLARE_TASKLET_DISABLED宏定義的
tasklet在初始時是被禁止的(disabled),因為其count等於1。
在這裡,tasklet狀態指兩個方面:(1)state成員所表示的運行狀態;(2)count成員
決定的使能/禁止狀態。
(1)改變一個tasklet的運行狀態
state成員中的bit[0]表示一個tasklet是否已被調度去等待執行,bit[1]表示一個
tasklet是否正在某個CPU上執行。對於state變數中某位的改變必須是一個原子操作,因
此可以用定義在include/asm/bitops.h標頭檔中的位操作來進行。
由於bit[1]這一位(即TASKLET_STATE_RUN)僅僅對於SMP系統才有意義,因此Linux在
Interrupt.h標頭檔中顯示地定義了對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系統同,tasklet_trylock()宏將把一個tasklet_struct結構變數中的state
成員中的bit[1]位設定成1,同時還返回bit[1]位的非。因此,如果bit[1]位原有
值為1(表示另外一個CPU正在執行這個tasklet代碼),那麼tasklet_trylock()宏將返
回值0,也就表示上鎖不成功。如果bit[1]位的原有值為0,那麼tasklet_trylock()宏
將傳回值1,表示加鎖成功。而在單CPU系統中,tasklet_trylock()宏總是返回為1。
任何想要執行某個tasklet代碼的程式都必須首先調用宏tasklet_trylock()來試圖對這
個tasklet進行上鎖(即設定TASKLET_STATE_RUN位),且只能在上鎖成功的情況下才能
執行這個tasklet。建議!即使你的程式只在CPU系統上運行,你也要在執行tasklet之前
調用tasklet_trylock()宏,以便使你的代碼獲得良好可移植性。
在SMP系統中,tasklet_unlock_wait()宏將一直不停地測試TASKLET_STATE_RUN位的值,
直到該位的值變為0(即一直等待到解鎖),假如:CPU0正在執行tasklet A的代碼,在
此期間,CPU1也想執行tasklet A的代碼,但CPU1發現tasklet A的TASKLET_STATE_RUN位
為1,於是它就可以通過tasklet_unlock_wait()宏等待tasklet A被解鎖(也即
TASKLET_STATE_RUN位被清零)。在單CPU系統中,這是一個空操作。
宏tasklet_unlock()用來對一個tasklet進行解鎖操作,也即將TASKLET_STATE_RUN位清
零。在單CPU系統中,這是一個空操作。
(2)使能/禁止一個tasklet
使能與禁止操作往往總是成對地被調用的,tasklet_disable()函數如下
(interrupt.h):
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
}
函數tasklet_disable_nosync()也是一個靜態inline函數,它簡單地通過原子操作將
count成員變數的值減1。如下所示(interrupt.h):
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
}
函數tasklet_enable()用於使能一個tasklet,如下所示(interrupt.h):
static inline void tasklet_enable(struct tasklet_struct *t)
{
atomic_dec(&t->count);
}
函數tasklet_init()用來初始化一個指定的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);
}
函數tasklet_kill()用來將一個已經被調度了的tasklet殺死,即將其恢複到未調度的狀
態。其源碼如下所示(kernel/softirq.c):
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt/n");
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);
}
多個tasklet可以通過tasklet描述符中的next成員指標連結成一個單向對列。為此,
Linux專門在標頭檔include/linux/interrupt.h中定義了資料結構tasklet_head來描述
一個tasklet對列的頭部指標。如下所示:
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
儘管tasklet機制是特定於非強制中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一種實現,但是
tasklet機制仍然屬於softirq機制的整體架構範圍內的,因此,它的設計與實現仍然必
須堅持“誰觸發,誰執行”的思想。為此,Linux為系統中的每一個CPU都定義了一個
tasklet對列頭部,來表示應該有各個CPU負責執行的tasklet對列。如下所示
(kernel/softirq.c):
struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
其中,tasklet_vec[]數組用於非強制中斷向量TASKLET_SOFTIRQ,而tasklet_hi_vec[]
數組則用於非強制中斷向量HI_SOFTIRQ。也即,如果CPUi(0≤i≤NR_CPUS-1)觸發了非強制中斷
向量TASKLET_SOFTIRQ,那麼對列tasklet_vec[i]中的每一個tasklet都將在CPUi服務
於非強制中斷向量TASKLET_SOFTIRQ時被CPUi所執行。同樣地,如果CPUi(0≤i≤NR_CPUS-
1)觸發了非強制中斷向量HI_SOFTIRQ,那麼隊列tasklet_vec[i]中的每一個tasklet都將
CPUi在對非強制中斷向量HI_SOFTIRQ進行服務時被CPUi所執行。
隊列tasklet_vec[I]和tasklet_hi_vec[I]中的各個tasklet是怎樣被所CPUi所執行
的呢?其關鍵就是非強制中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的非強制中斷服務程式——
tasklet_action()函數和tasklet_hi_action()函數。下面我們就來分析這兩個函數。
Linux為非強制中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ實現了專用的觸發函數和非強制中斷服務
函數。其中,tasklet_schedule()函數和tasklet_hi_schedule()函數分別用來在當前
CPU上觸發非強制中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,並把指定的tasklet加入當前CPU
所對應的tasklet隊列中去等待執行。而tasklet_action()函數和tasklet_hi_action()
函數則分別是非強制中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的非強制中斷服務函數。在初始化函
數softirq_init()中,這兩個非強制中斷向量對應的描述符softirq_vec[0]和softirq_vec
[3]中的action函數指標就被分別初始化成指向函數tasklet_hi_action()和函數
tasklet_action()。
(1)非強制中斷向量TASKLET_SOFTIRQ的觸發函數tasklet_schedule()
該函數實現在include/linux/interrupt.h標頭檔中,是一個inline函數。其源碼如下所
示:
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);
}
}
該函數的參數t指向要在當前CPU上被執行的tasklet。對該函數的NOTE如下:
①調用test_and_set_bit()函數將待調度的tasklet的state成員變數的bit[0]位(也
即TASKLET_STATE_SCHED位)設定為1,該函數同時還返回TASKLET_STATE_SCHED位的原有
值。因此如果bit[0]為的原有值已經為1,那就說明這個tasklet已經被調度到另一個
CPU上去等待執行了。由於一個tasklet在某一個時刻只能由一個CPU來執行,因此
tasklet_schedule()函數什麼也不做就直接返回了。否則,就繼續下面的調度操作。
②首先,調用local_irq_save()函數來關閉當前CPU的中斷,以保證下面的步驟在當前
CPU上原子地被執行。
③然後,將待調度的tasklet添加到當前CPU對應的tasklet隊列的首部。
④接著,調用__cpu_raise_softirq()函數在當前CPU上觸發非強制中斷請求
TASKLET_SOFTIRQ。
⑤最後,調用local_irq_restore()函數來開當前CPU的中斷。
(2)非強制中斷向量TASKLET_SOFTIRQ的服務程式tasklet_action()
函數tasklet_action()是tasklet機制與非強制中斷向量TASKLET_SOFTIRQ的聯絡紐帶。正是
該函數將當前CPU的tasklet隊列中的各個tasklet放到當前CPU上來執行的。該函數實現
在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();
}
}
注釋如下:
①首先,在當前CPU關中斷的情況下,“原子”地讀取當前CPU的tasklet隊列頭部指標,
將其儲存到局部變數list指標中,然後將當前CPU的tasklet隊列頭部指標設定為NULL,
以表示理論上當前CPU將不再有tasklet需要執行(但最後的實際結果卻並不一定如此,
下面將會看到)。
②然後,用一個while{}迴圈來遍曆由list所指向的tasklet隊列,隊列中的各個元素就
是將在當前CPU上執行的tasklet。迴圈體的執行步驟如下:
l 用指標t來表示當前隊列元素,即當前需要執行的tasklet。
l 更新list指標為list->next,使它指向下一個要執行的tasklet。
l 用tasklet_trylock()宏試圖對當前要執行的tasklet(由指標t所指向)進行加鎖,如
果加鎖成功(當前沒有任何其他CPU正在執行這個tasklet),則用原子讀函數
atomic_read()進一步判斷count成員的值。如果count為0,說明這個tasklet是允許執行
的,於是:(1)先清除TASKLET_STATE_SCHED位;(2)然後,調用這個tasklet的可執
行函數func;(3)執行barrier()操作;(4)調用宏tasklet_unlock()來清除
TASKLET_STATE_RUN位。(5)最後,執行continue語句跳過下面的步驟,回到while迴圈
繼續遍曆隊列中的下一個元素。如果count不為0,說明這個tasklet是禁止啟動並執行,於是
調用tasklet_unlock()清除前面用tasklet_trylock()設定的TASKLET_STATE_RUN位。
l 如果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)開中斷。
l 最後,回到while迴圈繼續遍曆隊列。
(3)非強制中斷向量HI_SOFTIRQ的觸發函數tasklet_hi_schedule()
該函數與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的服務函數tasklet_hi_action()
該函數與tasklet_action()函數幾乎相同,其源碼如下(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機制在新的softirq機制中被保留下來,並作為softirq架構的一部分。其實
現也似乎更為複雜些,因為它是通過tasklet機制這個中介橋樑來納入softirq架構中
的。實際上,非強制中斷向量HI_SOFTIRQ是核心專用於執行BH函數的。
原有的32個BH函數指標被保留,定義在kernel/softirq.c檔案中:
static void (*bh_base[32])(void);
但是,每個BH函數都對應有一個tasklet,並由tasklet的可執行函數func來負責調用相
應的bh函數(func函數的參數指定調用哪一個BH函數)。與32個BH函數指標相對應的
tasklet的定義如下所示(kernel/softirq.c):
struct tasklet_struct bh_task_vec[32];
上述tasklet數組使系統全域的,它對所有的CPU均可見。由於在某一個時刻只能有一個
CPU在執行BH函數,因此定義一個全域的自旋鎖來保護BH函數,如下所示
(kernel/softirq.c):
spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED;
在softirq機制的初始化函數softirq_init()中將bh_task_vec[32]數組中的每一個
tasklet中的func函數指標都設定為指向同一個函數bh_action,而data成員(也即func
函數的調用參數)則被設定成該tasklet在數組中的索引值,如下所示:
void __init softirq_init()
{
……
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
……
}
因此,bh_action()函數將負責相應地調用參數所指定的bh函數。該函數是串連tasklet
機制與Bottom Half機制的關鍵所在。
該函數的源碼如下(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);
}
對該函數的注釋如下:
①首先,調用spin_trylock()函數試圖對自旋鎖global_bh_lock進行加鎖,同時該函數
還將返回自旋鎖global_bh_lock的原有值的非。因此,如果global_bh_lock已被某個CPU
上鎖而為非0值(那個CPU肯定在執行某個BH函數),那麼spin_trylock()將返回為0表示
上鎖失敗,在這種情況下,當前CPU是不能執行BH函數的,因為另一個CPU正在執行BH函
數,於是執行goto語句跳轉到resched程式段,以便在當前CPU上再一次調度該BH函數。
②調用hardirq_trylock()函數鎖定當前CPU,確保當前CPU不是處於硬體插斷要求服務
中,如果鎖定失敗,跳轉到resched_unlock程式段,以便先對global_bh_lock解鎖,在
重新調度一次該BH函數。
③此時,我們已經可以放心地在當前CPU上執行BH函數了。當然,對應的BH函數指標
bh_base[nr]必須有效才行。
④從BH函數返回後,先調用hardirq_endlock()函數(實際上它什麼也不幹,調用它只是
為了保此加、解鎖的成對關係),然後解除自旋鎖global_bh_lock,最後函數就可以返
回了。
⑤resched_unlock程式段:先解除自旋鎖global_bh_lock,然後執行reched程式段。
⑥resched程式段:當某個CPU正在執行BH函數時,當前CPU就不能通過bh_action()函
數來調用執行任何BH函數,所以就通過調用mark_bh()函數在當前CPU上再重新調度一
次,以便將這個BH函數留待下次非強制中斷服務時執行。
(1)init_bh()函數
該函數用來在bh_base[]數組登記一個指定的bh函數,如下所示
(kernel/softirq.c):
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
(2)remove_bh()函數
該函數用來在bh_base[]數組中登出指定的函數指標,同時將相對應的tasklet殺掉。
如下所示(kernel/softirq.c):
void remove_bh(int nr)
{
tasklet_kill(bh_task_vec+nr);
bh_base[nr] = NULL;
}
(3)mark_bh()函數
該函數用來向當前CPU標記由一個BH函數等待去執行。它實際上通過調用
tasklet_hi_schedule()函數將相應的tasklet加入到當前CPU的tasklet隊列
tasklet_hi_vec[cpu]中,然後觸發非強制中斷請求HI_SOFTIRQ,如下所示
(include/linux/interrupt.h):
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
在32個BH函數指標中,大多數已經固定用於一些常見的外設,比如:第0個BH函數就固定
地用於時鐘中斷。Linux在標頭檔include/linux/interrupt.h中定義了這些已經被使用
的BH函數所引,如下所示:
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
};

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.