日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux内核的Softirq机制

發(fā)布時(shí)間:2023/12/10 linux 73 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内核的Softirq机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
前言?
中斷服務(wù)程序往往都是在CPU關(guān)中斷的條件下執(zhí)行的,以避免中斷嵌套而使控制復(fù)雜化。但是CPU關(guān)中斷的時(shí)間不能太長(zhǎng),否則容易丟失中斷信號(hào)。為此,Linux將中斷服務(wù)程序一分為二,各稱作“Top?Half”和“Bottom?Half”。前者通常對(duì)時(shí)間要求較為嚴(yán)格,必須在中斷請(qǐng)求發(fā)生后立即或至少在一定的時(shí)間限制內(nèi)完成。因此為了保證這種處理能原子地完成,Top?Half通常是在CPU關(guān)中斷的條件下執(zhí)行的。具體地說(shuō),Top?Half的范圍包括:從在IDT中登記的中斷入口函數(shù)一直到驅(qū)動(dòng)程序注冊(cè)在中斷服務(wù)隊(duì)列中的ISR。而Bottom?Half則是Top?Half根據(jù)需要來(lái)調(diào)度執(zhí)行的,這些操作允許延遲到稍后執(zhí)行,它的時(shí)間要求并不嚴(yán)格,因此它通常是在CPU開中斷的條件下執(zhí)行的。?
但是,Linux的這種Bottom?Half(以下簡(jiǎn)稱BH)機(jī)制有兩個(gè)缺點(diǎn),也即:(1)在任意一時(shí)刻,系統(tǒng)只能有一個(gè)CPU可以執(zhí)行Bottom?Half代碼,以防止兩個(gè)或多個(gè)CPU同時(shí)來(lái)執(zhí)行Bottom?Half函數(shù)而相互干擾。因此BH代碼的執(zhí)行是嚴(yán)格“串行化”的。(2)BH函數(shù)不允許嵌套。?
這兩個(gè)缺點(diǎn)在單CPU系統(tǒng)中是無(wú)關(guān)緊要的,但在SMP系統(tǒng)中卻是非常致命的。因?yàn)锽H機(jī)制的嚴(yán)格串行化執(zhí)行顯然沒有充分利用SMP系統(tǒng)的多CPU特點(diǎn)。為此,Linux2.4內(nèi)核在BH機(jī)制的基礎(chǔ)上進(jìn)行了擴(kuò)展,這就是所謂的“軟中斷請(qǐng)求”(softirq)機(jī)制。?

6.1?軟中斷請(qǐng)求機(jī)制?
Linux的softirq機(jī)制是與SMP緊密不可分的。為此,整個(gè)softirq機(jī)制的設(shè)計(jì)與實(shí)現(xiàn)中自始自終都貫徹了一個(gè)思想:“誰(shuí)觸發(fā),誰(shuí)執(zhí)行”(Who?marks,Who?runs),也即觸發(fā)軟中斷的那個(gè)CPU負(fù)責(zé)執(zhí)行它所觸發(fā)的軟中斷,而且每個(gè)CPU都由它自己的軟中斷觸發(fā)與控制機(jī)制。這個(gè)設(shè)計(jì)思想也使得softirq機(jī)制充分利用了SMP系統(tǒng)的性能和特點(diǎn)。?

6.1.1?軟中斷描述符?
Linux在include/linux/interrupt.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)softirq_action,來(lái)描述一個(gè)軟中斷請(qǐng)求,如下所示:?
/*?softirq?mask?and?active?fields?moved?to?irq_cpustat_t?in?
*?asm/hardirq.h?to?get?better?cache?usage.?KAO?
*/?
struct?softirq_action?
{?
void?(*action)(struct?softirq_action?*);?
void?*data;?
};?
其中,函數(shù)指針action指向軟中斷請(qǐng)求的服務(wù)函數(shù),而指針data則指向由服務(wù)函數(shù)自行解釋的數(shù)據(jù)。?

基于上述軟中斷描述符,Linux在kernel/softirq.c文件中定義了一個(gè)全局的softirq_vec[32]數(shù)組:?
static?struct?softirq_action?softirq_vec[32]?__cacheline_aligned;?
在這里系統(tǒng)一共定義了32個(gè)軟中斷請(qǐng)求描述符。軟中斷向量i(0≤i≤31)所對(duì)應(yīng)的軟中斷請(qǐng)求描述符就是softirq_vec[i]。這個(gè)數(shù)組是個(gè)系統(tǒng)全局?jǐn)?shù)組,也即它被所有的CPU所共享。這里需要注意的一點(diǎn)是:每個(gè)CPU雖然都由它自己的觸發(fā)和控制機(jī)制,并且只執(zhí)行他自己所觸發(fā)的軟中斷請(qǐng)求,但是各個(gè)CPU所執(zhí)行的軟中斷服務(wù)例程卻是相同的,也即都是執(zhí)行softirq_vec[]數(shù)組中定義的軟中斷服務(wù)函數(shù)。?

6.1.2?軟中斷觸發(fā)機(jī)制?
要實(shí)現(xiàn)“誰(shuí)觸發(fā),誰(shuí)執(zhí)行”的思想,就必須為每個(gè)CPU都定義它自己的觸發(fā)和控制變量。為此,Linux在include/asm-i386/hardirq.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)irq_cpustat_t來(lái)描述一個(gè)CPU的中斷統(tǒng)計(jì)信息,其中就有用于觸發(fā)和控制軟中斷的成員變量。數(shù)據(jù)結(jié)構(gòu)irq_cpustat_t的定義如下:?
/*?entry.S?is?sensitive?to?the?offsets?of?these?fields?*/?
typedef?struct?{?
unsigned?int?__softirq_active;?
unsigned?int?__softirq_mask;?
unsigned?int?__local_irq_count;?
unsigned?int?__local_bh_count;?
unsigned?int?__syscall_count;?
unsigned?int?__nmi_count;?/*?arch?dependent?*/?
}?____cacheline_aligned?irq_cpustat_t;?
結(jié)構(gòu)中每一個(gè)成員都是一個(gè)32位的無(wú)符號(hào)整數(shù)。其中__softirq_active和__softirq_mask就是用于觸發(fā)和控制軟中斷的成員變量。?
①__softirq_active變量:32位的無(wú)符號(hào)整數(shù),表示軟中斷向量0~31的狀態(tài)。如果bit[i](0≤i≤31)為1,則表示軟中斷向量i在某個(gè)CPU上已經(jīng)被觸發(fā)而處于active狀態(tài);為0表示處于非活躍狀態(tài)。?
②__softirq_mask變量:32位的無(wú)符號(hào)整數(shù),軟中斷向量的屏蔽掩碼。如果bit[i](0≤i≤31)為1,則表示使能(enable)軟中斷向量i,為0表示該軟中斷向量被禁止(disabled)。?
根據(jù)系統(tǒng)中當(dāng)前的CPU個(gè)數(shù)(由宏NR_CPUS表示),Linux在kernel/softirq.c文件中為每個(gè)CPU都定義了它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu),如下所示:?
/*?No?separate?irq_stat?for?s390,?it?is?part?of?PSA?*/?
#if?!defined(CONFIG_ARCH_S390)?
irq_cpustat_t?irq_stat[NR_CPUS];?
#endif?/*?CONFIG_ARCH_S390?*/?

這樣,每個(gè)CPU都只操作它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu)。假設(shè)有一個(gè)編號(hào)為id的CPU,那么它只能操作它自己的中斷統(tǒng)計(jì)信息結(jié)構(gòu)irq_stat[id](0≤id≤NR_CPUS-1),從而使各CPU之間互不影響。這個(gè)數(shù)組在include/linux/irq_cpustat.h頭文件中也作了原型聲明。?

l?觸發(fā)軟中斷請(qǐng)求的操作函數(shù)?
函數(shù)__cpu_raise_softirq()用于在編號(hào)為cpu的處理器上觸發(fā)軟中斷向量nr。它通過(guò)將相應(yīng)的__softirq_active成員變量中的相應(yīng)位設(shè)置為1來(lái)實(shí)現(xiàn)軟中斷觸發(fā)。如下所示(include/linux/interrupt.h):?
static?inline?void?__cpu_raise_softirq(int?cpu,?int?nr)?
{?
softirq_active(cpu)?|=?(1<<nr);?
}?
為了保證“原子”性地完成軟中斷的觸發(fā)過(guò)程,Linux在interrupt.h頭文件中對(duì)上述內(nèi)聯(lián)函數(shù)又作了高層封裝,也即函數(shù)raise_softirq()。該函數(shù)向下通過(guò)調(diào)用__cpu_raise_softirq()函數(shù)來(lái)實(shí)現(xiàn)軟中斷的觸發(fā),但在調(diào)用該函數(shù)之前,它先通過(guò)local_irq_save()函數(shù)來(lái)關(guān)閉當(dāng)前CPU的中斷并保存標(biāo)志寄存器的內(nèi)容,如下所示:?
/*?I?do?not?want?to?use?atomic?variables?now,?so?that?cli/sti?*/?
static?inline?void?raise_softirq(int?nr)?
{?
unsigned?long?flags;?

local_irq_save(flags);?
__cpu_raise_softirq(smp_processor_id(),?nr);?
local_irq_restore(flags);?
}?

6.1.3?Linux對(duì)軟中斷的預(yù)定義分類?
在軟中斷向量0~31中,Linux內(nèi)核僅僅使用了軟中斷向量0~3,其余被留待系統(tǒng)以后擴(kuò)展。Linux在頭文件include/linux/interrupt.h中對(duì)軟中斷向量0~3進(jìn)行了預(yù)定義:?
/*?PLEASE,?avoid?to?allocate?new?softirqs,?if?you?need?not?_really_?high?
frequency?threaded?job?scheduling.?For?almost?all?the?purposes?
tasklets?are?more?than?enough.?F.e.?all?serial?device?BHs?et?
al.?should?be?converted?to?tasklets,?not?to?softirqs.?
*/?
enum?
{?
HI_SOFTIRQ=0,?
NET_TX_SOFTIRQ,?
NET_RX_SOFTIRQ,?
TASKLET_SOFTIRQ?
};?
其中,軟中斷向量0(即HI_SOFTIRQ)用于實(shí)現(xiàn)高優(yōu)先級(jí)的軟中斷,如:高優(yōu)先級(jí)的tasklet(將在后面詳細(xì)描述)。軟中斷向量1和2則分別用于網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送與接收。軟中斷向量3(即TASKLET_SOFTIRQ)則用于實(shí)現(xiàn)諸如tasklet這樣的一般性軟中斷。關(guān)于tasklet我們將在后面詳細(xì)描述。NOTE!Linix內(nèi)核并不鼓勵(lì)一般用戶擴(kuò)展使用剩余的軟中斷向量,因?yàn)樗J(rèn)為其預(yù)定義的軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ已經(jīng)足夠應(yīng)付絕大多數(shù)應(yīng)用。?

6.1.4?軟中斷機(jī)制的初始化?
函數(shù)softirq_init()完成softirq機(jī)制的初始化。該函數(shù)由內(nèi)核啟動(dòng)例程start_kernel()所調(diào)用。函數(shù)源碼如下所示(kernel/softirq.c):?
void?__init?softirq_init()?
{?
int?i;?

for?(i=0;?i<32;?i++)?
tasklet_init(bh_task_vec+i,?bh_action,?i);?

open_softirq(TASKLET_SOFTIRQ,?tasklet_action,?NULL);?
open_softirq(HI_SOFTIRQ,?tasklet_hi_action,?NULL);?
}?
初始化的過(guò)程如下:?
(1)先用一個(gè)for循環(huán)來(lái)初始化用于實(shí)現(xiàn)BH機(jī)制的bh_task_vec[32]數(shù)組。這一點(diǎn)我們將在后面詳細(xì)解釋。?
(2)調(diào)用open_softirq()函數(shù)開啟使用軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并將它們的軟中斷服務(wù)函數(shù)指針分別指向tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。函數(shù)open_softirq()的主要作用是初始化設(shè)置軟中斷請(qǐng)求描述符softirq_vec[nr]。?

6.1.5?開啟一個(gè)指定的軟中斷向量?
函數(shù)open_softirq()用于開啟一個(gè)指定的軟中斷向量nr,也即適當(dāng)?shù)爻跏蓟浿袛嘞蛄縩r所對(duì)應(yīng)的軟中斷描述符softirq_vec[nr]。它主要做兩件事情:(1)初始化設(shè)置軟中斷向量nr所對(duì)應(yīng)的軟中斷描述符softirq_vec[nr]。(2)將所有CPU的軟中斷屏蔽掩碼變量__softirq_mask中的對(duì)應(yīng)位設(shè)置為1,以使能該軟中斷向量。該函數(shù)的源碼如下所示(kernel/softirq.c):?
void?open_softirq(int?nr,?void?(*action)(struct?softirq_action*),?void?*data)?
{?
unsigned?long?flags;?
int?i;?

spin_lock_irqsave(&softirq_mask_lock,?flags);?
softirq_vec[nr].data?=?data;?
softirq_vec[nr].action?=?action;?

for?(i=0;?i<NR_CPUS;?i++)?
softirq_mask(i)?|=?(1<<nr);?
spin_unlock_irqrestore(&softirq_mask_lock,?flags);?
}?

6.1.6?軟中斷服務(wù)的執(zhí)行函數(shù)do_softirq()?
函數(shù)do_softirq()負(fù)責(zé)執(zhí)行數(shù)組softirq_vec[32]中設(shè)置的軟中斷服務(wù)函數(shù)。每個(gè)CPU都是通過(guò)執(zhí)行這個(gè)函數(shù)來(lái)執(zhí)行軟中斷服務(wù)的。由于同一個(gè)CPU上的軟中斷服務(wù)例程不允許嵌套,因此,do_softirq()函數(shù)一開始就檢查當(dāng)前CPU是否已經(jīng)正出在中斷服務(wù)中,如果是則do_softirq()函數(shù)立即返回。舉個(gè)例子,假設(shè)CPU0正在執(zhí)行do_softirq()函數(shù),執(zhí)行過(guò)程產(chǎn)生了一個(gè)高優(yōu)先級(jí)的硬件中斷,于是CPU0轉(zhuǎn)去執(zhí)行這個(gè)高優(yōu)先級(jí)中斷所對(duì)應(yīng)的中斷服務(wù)程序。總所周知,所有的中斷服務(wù)程序最后都要跳轉(zhuǎn)到do_IRQ()函數(shù)并由它來(lái)依次執(zhí)行中斷服務(wù)隊(duì)列中的ISR,這里我們假定這個(gè)高優(yōu)先級(jí)中斷的ISR請(qǐng)求觸發(fā)了一次軟中斷,于是do_IRQ()函數(shù)在退出之前看到有軟中斷請(qǐng)求,從而調(diào)用do_softirq()函數(shù)來(lái)服務(wù)軟中斷請(qǐng)求。因此,CPU0再次進(jìn)入do_softirq()函數(shù)(也即do_softirq()函數(shù)在CPU0上被重入了)。但是在這一次進(jìn)入do_softirq()函數(shù)時(shí),它馬上發(fā)現(xiàn)CPU0此前已經(jīng)處在中斷服務(wù)狀態(tài)中了,因此這一次do_softirq()函數(shù)立即返回。于是,CPU0回到該開始時(shí)的do_softirq()函數(shù)繼續(xù)執(zhí)行,并為高優(yōu)先級(jí)中斷的ISR所觸發(fā)的軟中斷請(qǐng)求補(bǔ)上一次服務(wù)。從這里可以看出,do_softirq()函數(shù)在同一個(gè)CPU上的執(zhí)行是串行的。?
函數(shù)源碼如下(kernel/softirq.c):?
asmlinkage?void?do_softirq()?
{?
int?cpu?=?smp_processor_id();?
__u32?active,?mask;?

if?(in_interrupt())?
return;?

local_bh_disable();?

local_irq_disable();?
mask?=?softirq_mask(cpu);?
active?=?softirq_active(cpu)?&?mask;?

if?(active)?{?
struct?softirq_action?*h;?

restart:?
/*?Reset?active?bitmask?before?enabling?irqs?*/?
softirq_active(cpu)?&=?~active;?

local_irq_enable();?

h?=?softirq_vec;?
mask?&=?~active;?

do?{?
if?(active?&?1)?
h->action(h);?
h++;?
active?>>=?1;?
}?while?(active);?

local_irq_disable();?

active?=?softirq_active(cpu);?
if?((active?&=?mask)?!=?0)?
goto?retry;?
}?

local_bh_enable();?

/*?Leave?with?locally?disabled?hard?irqs.?It?is?critical?to?close?
*?window?for?infinite?recursion,?while?we?help?local?bh?count,?
*?it?protected?us.?Now?we?are?defenceless.?
*/?
return;?

retry:?
goto?restart;?
}?
結(jié)合上述源碼,我們可以看出軟中斷服務(wù)的執(zhí)行過(guò)程如下:?
(1)調(diào)用宏in_interrupt()來(lái)檢測(cè)當(dāng)前CPU此次是否已經(jīng)處于中斷服務(wù)中。該宏定義在hardirq.h,請(qǐng)參見5.7節(jié)。?
(2)調(diào)用local_bh_disable()宏將當(dāng)前CPU的中斷統(tǒng)計(jì)信息結(jié)構(gòu)中的__local_bh_count成員變量加1,表示當(dāng)前CPU已經(jīng)處在軟中斷服務(wù)狀態(tài)。?
(3)由于接下來(lái)要讀寫當(dāng)前CPU的中斷統(tǒng)計(jì)信息結(jié)構(gòu)中的__softirq_active變量和__softirq_mask變量,因此為了保證這一個(gè)操作過(guò)程的原子性,先用local_irq_disable()宏(實(shí)際上就是cli指令)關(guān)閉當(dāng)前CPU的中斷。?
(4)然后,讀當(dāng)前CPU的__softirq_active變量值和__softirq_mask變量值。當(dāng)某個(gè)軟中斷向量被觸發(fā)時(shí)(即__softirq_active變量中的相應(yīng)位被置1),只有__softirq_mask變量中的相應(yīng)位也為1時(shí),它的軟中斷服務(wù)函數(shù)才能得到執(zhí)行。因此,需要將__softirq_active變量和__softirq_mask變量作一次“與”邏輯操作。?
(5)如果active變量非0,說(shuō)明需要執(zhí)行軟中斷服務(wù)函數(shù)。因此:①先將當(dāng)前CPU的__softirq_active中的相應(yīng)位清零,然后用local_irq_enable()宏(實(shí)際上就是sti指令)打開當(dāng)前CPU的中斷。②將局部變量mask中的相應(yīng)位清零,其目的是:讓do_softirq()函數(shù)的這一次執(zhí)行不對(duì)同一個(gè)軟中斷向量上的再次軟中斷請(qǐng)求進(jìn)行服務(wù),而是將它留待下一次do_softirq()執(zhí)行時(shí)去服務(wù),從而使do_sottirq()函數(shù)避免陷入無(wú)休止的軟中斷服務(wù)中。③用一個(gè)do{}while循環(huán)來(lái)根據(jù)active的值去執(zhí)行相應(yīng)的軟中斷服務(wù)函數(shù)。④由于接下來(lái)又要檢測(cè)當(dāng)前CPU的__softirq_active變量,因此再一次調(diào)用local_irq_disable()宏關(guān)閉當(dāng)前CPU的中斷。⑤讀取當(dāng)前CPU的__softirq_active變量的值,并將它與局部變量mask進(jìn)行與操作,以看看是否又有其他軟中斷服務(wù)被觸發(fā)了(比如前面所說(shuō)的那種情形)。如果有的話,那就跳轉(zhuǎn)到entry程序段(實(shí)際上是跳轉(zhuǎn)到restart程序段)重新執(zhí)行軟中斷服務(wù)。如果沒有的話,那么此次軟中斷服務(wù)過(guò)程就宣告結(jié)束。?
(6)最后,通過(guò)local_bh_enable()宏將當(dāng)前CPU的__local_bh_count變量值減1,表示當(dāng)前CPU已經(jīng)離開軟中斷服務(wù)狀態(tài)。宏local_bh_enable()也定義在include/asm-i386/softirq.h頭文件中。
6.2?tasklet機(jī)制?
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ù))。?

6.2.1?tasklet描述符?
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。?

6.2.2?改變一個(gè)tasklet狀態(tài)的操作?
在這里,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);?
}?

6.2.3?tasklet描述符的初始化與殺死?
函數(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?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);?
}?

6.2.4?tasklet對(duì)列?
多個(gè)tasklet可以通過(guò)tasklet描述符中的next成員指針鏈接成一個(gè)單向?qū)α小榇?#xff0c;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ù)。?

6.2.5?軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ?
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í)行步驟如下:?
l?用指針t來(lái)表示當(dāng)前隊(duì)列元素,即當(dāng)前需要執(zhí)行的tasklet。?
l?更新list指針為list->next,使它指向下一個(gè)要執(zhí)行的tasklet。?
l?用tasklet_trylock()宏試圖對(duì)當(dāng)前要執(zhí)行的tasklet(由指針t所指向)進(jìn)行加鎖,如果加鎖成功(當(dāng)前沒有任何其他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位。?
l?如果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)開中斷。?
l?最后,回到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();?
}?
}

.3?Bottom?Half機(jī)制?
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ù)的。?

6.3.1?數(shù)據(jù)結(jié)構(gòu)的定義?
原有的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;?

6.3.2?初始化?
在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)鍵所在。?

6.2.3?bh_action()函數(shù)?
該函數(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í)行。?

6.3.4?Bottom?Half的原有接口函數(shù)?
(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);?
}?

6.3.5?預(yù)定義的BH函數(shù)?
在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?
};

6.4?任務(wù)隊(duì)列Task?Queue?
任務(wù)隊(duì)列是與Bottom?Half機(jī)制緊密相連的。因?yàn)锽ottom?Half機(jī)制只有有限的32個(gè)函數(shù)指針,而且大部分都已被系統(tǒng)預(yù)定義使用,所以早期版本的Linux內(nèi)核為了擴(kuò)展Bottom?Half機(jī)制,就設(shè)計(jì)了任務(wù)隊(duì)列機(jī)制。?
所謂任務(wù)隊(duì)列就是指以雙向隊(duì)列形式連接起來(lái)的任務(wù)鏈表,每一個(gè)鏈表元數(shù)都描述了一個(gè)可執(zhí)行的任務(wù)(以函數(shù)的形式表現(xiàn))。如下圖所示:?

任務(wù)隊(duì)列機(jī)制實(shí)現(xiàn)在include/linux/tqueue.h頭文件中。?

6.4.1?數(shù)據(jù)結(jié)構(gòu)的定義?
Linux用數(shù)據(jù)結(jié)構(gòu)tq_struct來(lái)描述任務(wù)隊(duì)列中的每一個(gè)鏈表元數(shù)(即一個(gè)可執(zhí)行的任務(wù)):?
struct?tq_struct?{?
struct?list_head?list;?/*?linked?list?of?active?bh's?*/?
unsigned?long?sync;?/*?must?be?initialized?to?zero?*/?
void?(*routine)(void?*);?/*?function?to?call?*/?
void?*data;?/*?argument?to?function?*/?
};?
這個(gè)數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單,在此就不詳述。?
然后,Linux定義了數(shù)據(jù)結(jié)構(gòu)task_queue來(lái)描述任務(wù)隊(duì)列的頭部,其實(shí)task_queue就是結(jié)構(gòu)類型list_head,如下:?
typedef?struct?list_head?task_queue;?

但是Linux又定義了一個(gè)宏DECLARE_TASK_QUEUE()來(lái)輔助我們更方便地定義任務(wù)隊(duì)列的鏈表表頭:?
#define?DECLARE_TASK_QUEUE(q)?LIST_HEAD(q)?

一個(gè)任務(wù)隊(duì)列是否處于active狀態(tài)主要取決于其鏈表表頭(即task_queue結(jié)構(gòu))是否為空,因此Linux定義宏TQ_ACTIVE()來(lái)判斷一個(gè)任務(wù)隊(duì)列是否有效:?
#define?TQ_ACTIVE(q)?(!list_empty(&q))?
顯然,只要任務(wù)隊(duì)列表頭q不為空,該任務(wù)隊(duì)列就是有效的。?

6.4.2?向任務(wù)隊(duì)列中插入一個(gè)新任務(wù)?
(1)保護(hù)自旋鎖?
由于任務(wù)隊(duì)列是系統(tǒng)全局的共享資源,所以面臨競(jìng)爭(zhēng)的問題。為了實(shí)現(xiàn)對(duì)任務(wù)隊(duì)列鏈表的互斥訪問,Linux在kernel/timer.c文件中定義了一個(gè)任務(wù)隊(duì)列保護(hù)自旋鎖tqueue_lock,如下:?
spinlock_t?tqueue_lock?=?SPIN_LOCK_UNLOCKED;?
該自旋鎖在tqueue.h頭文件中也有原型聲明:?
extern?spinlock_t?tqueue_lock;?
任何想要訪問任務(wù)隊(duì)列的代碼都首先必須先持有該自旋鎖。?

(2)queue_task()函數(shù)?
實(shí)現(xiàn)在tqueue.h頭文件中的內(nèi)聯(lián)函數(shù)queue_task()用來(lái)將一個(gè)指定的任務(wù)添加到某指定的任務(wù)隊(duì)列的尾部,如下:?
/*?
*?Queue?a?task?on?a?tq.?Return?non-zero?if?it?was?successfully?
*?added.?
*/?
static?inline?int?queue_task(struct?tq_struct?*bh_pointer,?task_queue?*bh_list)?
{?
int?ret?=?0;?
if?(!test_and_set_bit(0,&bh_pointer->sync))?{?
unsigned?long?flags;?
spin_lock_irqsave(&tqueue_lock,?flags);?
list_add_tail(&bh_pointer->list,?bh_list);?
spin_unlock_irqrestore(&tqueue_lock,?flags);?
ret?=?1;?
}?
return?ret;?
}?

6.4.3?運(yùn)行任務(wù)隊(duì)列?
函數(shù)run_task_queue()用于實(shí)現(xiàn)指定的任務(wù)隊(duì)列。它只有一個(gè)參數(shù):指針list——指向待運(yùn)行的任務(wù)隊(duì)列頭部task_queue結(jié)構(gòu)變量。該函數(shù)實(shí)現(xiàn)在tqueue.h頭文件中:?
static?inline?void?run_task_queue(task_queue?*list)?
{?
if?(TQ_ACTIVE(*list))?
__run_task_queue(list);?
}?
顯然,函數(shù)首先調(diào)用宏TQ_ACTIVE()來(lái)判斷參數(shù)list指定的待運(yùn)行任務(wù)隊(duì)列是否為空。如果不為空,則調(diào)用__run_task_queue()函數(shù)來(lái)實(shí)際運(yùn)行這個(gè)有效的任務(wù)隊(duì)列。?
函數(shù)__run_task_queue()實(shí)現(xiàn)在kernel/softirq.c文件中。該函數(shù)將依次遍歷任務(wù)隊(duì)列中的每一個(gè)元數(shù),并調(diào)用執(zhí)行每一個(gè)元數(shù)的可執(zhí)行函數(shù)。其源碼如下:?
void?__run_task_queue(task_queue?*list)?
{?
struct?list_head?head,?*next;?
unsigned?long?flags;?

spin_lock_irqsave(&tqueue_lock,?flags);?
list_add(&head,?list);?
list_del_init(list);?
spin_unlock_irqrestore(&tqueue_lock,?flags);?

next?=?head.next;?
while?(next?!=?&head)?{?
void?(*f)?(void?*);?
struct?tq_struct?*p;?
void?*data;?

p?=?list_entry(next,?struct?tq_struct,?list);?
next?=?next->next;?
f?=?p->routine;?
data?=?p->data;?
wmb();?
p->sync?=?0;?
if?(f)?
f(data);?
}?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先,用一個(gè)局部的表頭head來(lái)代替參數(shù)list所指向的表頭。這是因?yàn)?#xff1a;在__run_task_queue()函數(shù)的運(yùn)行期間可能還會(huì)有新的任務(wù)加入到list任務(wù)隊(duì)列中來(lái),但是__run_task_queue()函數(shù)顯然不想陷入無(wú)休止的不斷增加的任務(wù)處理中,因此它用局部的表頭head來(lái)代替參數(shù)list所指向的表頭,以使要執(zhí)行的任務(wù)個(gè)數(shù)固定化。為此:①先對(duì)全局的自旋鎖tqueue_lock進(jìn)行加鎖,以實(shí)現(xiàn)對(duì)任務(wù)隊(duì)列的互斥訪問;②將局部的表頭head加在表頭(*list)和第一個(gè)元數(shù)之間。③將(*list)表頭從隊(duì)列中去除,并將其初始化為空。④解除自旋鎖tqueue_lock。?
(2)接下來(lái),用一個(gè)while循環(huán)來(lái)遍歷整個(gè)隊(duì)列head,并調(diào)用執(zhí)行每一個(gè)隊(duì)列元素中的函數(shù)。注意!任務(wù)隊(duì)列是一個(gè)雙向循環(huán)隊(duì)列。?

6.4.4?內(nèi)核預(yù)定義的任務(wù)隊(duì)列?
Bottom?Half機(jī)制與任務(wù)隊(duì)列是緊密相連的。大多數(shù)BH函數(shù)都是通過(guò)調(diào)用run_task_queue()函數(shù)來(lái)執(zhí)行某個(gè)預(yù)定義好的任務(wù)隊(duì)列。最常見的內(nèi)核預(yù)定義任務(wù)隊(duì)列有:?
l?tq_timer:對(duì)應(yīng)于TQUEUE_BH。?
l?tq_immediate:對(duì)應(yīng)于IMMEDIATE_BH。?
l?tq_disk:用于塊設(shè)備任務(wù)。?

任務(wù)隊(duì)列tq_timer和tq_immediate都定義在kernel/timer.c文件中,如下所示:?
DECLARE_TASK_QUEUE(tq_timer);?
DECLARE_TASK_QUEUE(tq_immediate);?

BH向量TQUEUE_BH和IMMEDIATE_BH的BH函數(shù)分別是:queue_bh()函數(shù)和immediate_bh()函數(shù),它們都僅僅是簡(jiǎn)單地調(diào)用run_task_queue()函數(shù)來(lái)分別運(yùn)行任務(wù)隊(duì)列tq_timer和tq_immediate,如下所示(kernel/timer.c):?
void?tqueue_bh(void)?
{?
run_task_queue(&tq_timer);?
}?

void?immediate_bh(void)?
{?
run_task_queue(&tq_immediate);?
}

總結(jié)

以上是生活随笔為你收集整理的Linux内核的Softirq机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。