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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux内核的Softirq机制

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

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

6.1.1?軟中斷描述符?
Linux在include/linux/interrupt.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)softirq_action,來描述一個軟中斷請求,如下所示:?
/*?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指向軟中斷請求的服務函數(shù),而指針data則指向由服務函數(shù)自行解釋的數(shù)據(jù)。?

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

6.1.2?軟中斷觸發(fā)機制?
要實現(xiàn)“誰觸發(fā),誰執(zhí)行”的思想,就必須為每個CPU都定義它自己的觸發(fā)和控制變量。為此,Linux在include/asm-i386/hardirq.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)irq_cpustat_t來描述一個CPU的中斷統(tǒng)計信息,其中就有用于觸發(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)中每一個成員都是一個32位的無符號整數(shù)。其中__softirq_active和__softirq_mask就是用于觸發(fā)和控制軟中斷的成員變量。?
①__softirq_active變量:32位的無符號整數(shù),表示軟中斷向量0~31的狀態(tài)。如果bit[i](0≤i≤31)為1,則表示軟中斷向量i在某個CPU上已經(jīng)被觸發(fā)而處于active狀態(tài);為0表示處于非活躍狀態(tài)。?
②__softirq_mask變量:32位的無符號整數(shù),軟中斷向量的屏蔽掩碼。如果bit[i](0≤i≤31)為1,則表示使能(enable)軟中斷向量i,為0表示該軟中斷向量被禁止(disabled)。?
根據(jù)系統(tǒng)中當前的CPU個數(shù)(由宏NR_CPUS表示),Linux在kernel/softirq.c文件中為每個CPU都定義了它自己的中斷統(tǒng)計信息結(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?*/?

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

l?觸發(fā)軟中斷請求的操作函數(shù)?
函數(shù)__cpu_raise_softirq()用于在編號為cpu的處理器上觸發(fā)軟中斷向量nr。它通過將相應的__softirq_active成員變量中的相應位設置為1來實現(xiàn)軟中斷觸發(fā)。如下所示(include/linux/interrupt.h):?
static?inline?void?__cpu_raise_softirq(int?cpu,?int?nr)?
{?
softirq_active(cpu)?|=?(1<<nr);?
}?
為了保證“原子”性地完成軟中斷的觸發(fā)過程,Linux在interrupt.h頭文件中對上述內(nèi)聯(lián)函數(shù)又作了高層封裝,也即函數(shù)raise_softirq()。該函數(shù)向下通過調(diào)用__cpu_raise_softirq()函數(shù)來實現(xiàn)軟中斷的觸發(fā),但在調(diào)用該函數(shù)之前,它先通過local_irq_save()函數(shù)來關(guān)閉當前CPU的中斷并保存標志寄存器的內(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對軟中斷的預定義分類?
在軟中斷向量0~31中,Linux內(nèi)核僅僅使用了軟中斷向量0~3,其余被留待系統(tǒng)以后擴展。Linux在頭文件include/linux/interrupt.h中對軟中斷向量0~3進行了預定義:?
/*?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)用于實現(xiàn)高優(yōu)先級的軟中斷,如:高優(yōu)先級的tasklet(將在后面詳細描述)。軟中斷向量1和2則分別用于網(wǎng)絡數(shù)據(jù)的發(fā)送與接收。軟中斷向量3(即TASKLET_SOFTIRQ)則用于實現(xiàn)諸如tasklet這樣的一般性軟中斷。關(guān)于tasklet我們將在后面詳細描述。NOTE!Linix內(nèi)核并不鼓勵一般用戶擴展使用剩余的軟中斷向量,因為它認為其預定義的軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ已經(jīng)足夠應付絕大多數(shù)應用。?

6.1.4?軟中斷機制的初始化?
函數(shù)softirq_init()完成softirq機制的初始化。該函數(shù)由內(nèi)核啟動例程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);?
}?
初始化的過程如下:?
(1)先用一個for循環(huán)來初始化用于實現(xiàn)BH機制的bh_task_vec[32]數(shù)組。這一點我們將在后面詳細解釋。?
(2)調(diào)用open_softirq()函數(shù)開啟使用軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并將它們的軟中斷服務函數(shù)指針分別指向tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。函數(shù)open_softirq()的主要作用是初始化設置軟中斷請求描述符softirq_vec[nr]。?

6.1.5?開啟一個指定的軟中斷向量?
函數(shù)open_softirq()用于開啟一個指定的軟中斷向量nr,也即適當?shù)爻跏蓟浿袛嘞蛄縩r所對應的軟中斷描述符softirq_vec[nr]。它主要做兩件事情:(1)初始化設置軟中斷向量nr所對應的軟中斷描述符softirq_vec[nr]。(2)將所有CPU的軟中斷屏蔽掩碼變量__softirq_mask中的對應位設置為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?軟中斷服務的執(zhí)行函數(shù)do_softirq()?
函數(shù)do_softirq()負責執(zhí)行數(shù)組softirq_vec[32]中設置的軟中斷服務函數(shù)。每個CPU都是通過執(zhí)行這個函數(shù)來執(zhí)行軟中斷服務的。由于同一個CPU上的軟中斷服務例程不允許嵌套,因此,do_softirq()函數(shù)一開始就檢查當前CPU是否已經(jīng)正出在中斷服務中,如果是則do_softirq()函數(shù)立即返回。舉個例子,假設CPU0正在執(zhí)行do_softirq()函數(shù),執(zhí)行過程產(chǎn)生了一個高優(yōu)先級的硬件中斷,于是CPU0轉(zhuǎn)去執(zhí)行這個高優(yōu)先級中斷所對應的中斷服務程序。總所周知,所有的中斷服務程序最后都要跳轉(zhuǎn)到do_IRQ()函數(shù)并由它來依次執(zhí)行中斷服務隊列中的ISR,這里我們假定這個高優(yōu)先級中斷的ISR請求觸發(fā)了一次軟中斷,于是do_IRQ()函數(shù)在退出之前看到有軟中斷請求,從而調(diào)用do_softirq()函數(shù)來服務軟中斷請求。因此,CPU0再次進入do_softirq()函數(shù)(也即do_softirq()函數(shù)在CPU0上被重入了)。但是在這一次進入do_softirq()函數(shù)時,它馬上發(fā)現(xiàn)CPU0此前已經(jīng)處在中斷服務狀態(tài)中了,因此這一次do_softirq()函數(shù)立即返回。于是,CPU0回到該開始時的do_softirq()函數(shù)繼續(xù)執(zhí)行,并為高優(yōu)先級中斷的ISR所觸發(fā)的軟中斷請求補上一次服務。從這里可以看出,do_softirq()函數(shù)在同一個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é)合上述源碼,我們可以看出軟中斷服務的執(zhí)行過程如下:?
(1)調(diào)用宏in_interrupt()來檢測當前CPU此次是否已經(jīng)處于中斷服務中。該宏定義在hardirq.h,請參見5.7節(jié)。?
(2)調(diào)用local_bh_disable()宏將當前CPU的中斷統(tǒng)計信息結(jié)構(gòu)中的__local_bh_count成員變量加1,表示當前CPU已經(jīng)處在軟中斷服務狀態(tài)。?
(3)由于接下來要讀寫當前CPU的中斷統(tǒng)計信息結(jié)構(gòu)中的__softirq_active變量和__softirq_mask變量,因此為了保證這一個操作過程的原子性,先用local_irq_disable()宏(實際上就是cli指令)關(guān)閉當前CPU的中斷。?
(4)然后,讀當前CPU的__softirq_active變量值和__softirq_mask變量值。當某個軟中斷向量被觸發(fā)時(即__softirq_active變量中的相應位被置1),只有__softirq_mask變量中的相應位也為1時,它的軟中斷服務函數(shù)才能得到執(zhí)行。因此,需要將__softirq_active變量和__softirq_mask變量作一次“與”邏輯操作。?
(5)如果active變量非0,說明需要執(zhí)行軟中斷服務函數(shù)。因此:①先將當前CPU的__softirq_active中的相應位清零,然后用local_irq_enable()宏(實際上就是sti指令)打開當前CPU的中斷。②將局部變量mask中的相應位清零,其目的是:讓do_softirq()函數(shù)的這一次執(zhí)行不對同一個軟中斷向量上的再次軟中斷請求進行服務,而是將它留待下一次do_softirq()執(zhí)行時去服務,從而使do_sottirq()函數(shù)避免陷入無休止的軟中斷服務中。③用一個do{}while循環(huán)來根據(jù)active的值去執(zhí)行相應的軟中斷服務函數(shù)。④由于接下來又要檢測當前CPU的__softirq_active變量,因此再一次調(diào)用local_irq_disable()宏關(guān)閉當前CPU的中斷。⑤讀取當前CPU的__softirq_active變量的值,并將它與局部變量mask進行與操作,以看看是否又有其他軟中斷服務被觸發(fā)了(比如前面所說的那種情形)。如果有的話,那就跳轉(zhuǎn)到entry程序段(實際上是跳轉(zhuǎn)到restart程序段)重新執(zhí)行軟中斷服務。如果沒有的話,那么此次軟中斷服務過程就宣告結(jié)束。?
(6)最后,通過local_bh_enable()宏將當前CPU的__local_bh_count變量值減1,表示當前CPU已經(jīng)離開軟中斷服務狀態(tài)。宏local_bh_enable()也定義在include/asm-i386/softirq.h頭文件中。
6.2?tasklet機制?
Tasklet機制是一種較為特殊的軟中斷。Tasklet一詞的原意是“小片任務”的意思,這里是指一小段可執(zhí)行的代碼,且通常以函數(shù)的形式出現(xiàn)。軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet機制來實現(xiàn)的。?
從某種程度上講,tasklet機制是Linux內(nèi)核對BH機制的一種擴展。在2.4內(nèi)核引入了softirq機制后,原有的BH機制正是通過tasklet機制這個橋梁來納入softirq機制的整體框架中的。正是由于這種歷史的延伸關(guān)系,使得tasklet機制與一般意義上的軟中斷有所不同,而呈現(xiàn)出以下兩個顯著的特點:?
1.?與一般的軟中斷不同,某一段tasklet代碼在某個時刻只能在一個CPU上運行,而不像一般的軟中斷服務函數(shù)(即softirq_action結(jié)構(gòu)中的action函數(shù)指針)那樣——在同一時刻可以被多個CPU并發(fā)地執(zhí)行。?
2.?與BH機制不同,不同的tasklet代碼在同一時刻可以在多個CPU上并發(fā)地執(zhí)行,而不像BH機制那樣必須嚴格地串行化執(zhí)行(也即在同一時刻系統(tǒng)中只能有一個CPU執(zhí)行BH函數(shù))。?

6.2.1?tasklet描述符?
Linux用數(shù)據(jù)結(jié)構(gòu)tasklet_struct來描述一個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指針:指向下一個tasklet的指針。?
(2)state:定義了這個tasklet的當前狀態(tài)。這一個32位的無符號長整數(shù),當前只使用了bit[1]和bit[0]兩個狀態(tài)位。其中,bit[1]=1表示這個tasklet當前正在某個CPU上被執(zhí)行,它僅對SMP系統(tǒng)才有意義,其作用就是為了防止多個CPU同時執(zhí)行一個tasklet的情形出現(xiàn);bit[0]=1表示這個tasklet已經(jīng)被調(diào)度去等待執(zhí)行了。對這兩個狀態(tài)位的宏定義如下所示(interrupt.h):?
enum?
{?
TASKLET_STATE_SCHED,?/*?Tasklet?is?scheduled?for?execution?*/?
TASKLET_STATE_RUN?/*?Tasklet?is?running?(SMP?only)?*/?
};?
(3)原子計數(shù)count:對這個tasklet的引用計數(shù)值。NOTE!只有當count等于0時,tasklet代碼段才能執(zhí)行,也即此時tasklet是被使能的;如果count非零,則這個tasklet是被禁止的。任何想要執(zhí)行一個tasklet代碼段的人都首先必須先檢查其count成員是否為0。?
(4)函數(shù)指針func:指向以函數(shù)形式表現(xiàn)的可執(zhí)行tasklet代碼段。?
(5)data:函數(shù)func的參數(shù)。這是一個32位的無符號整數(shù),其具體含義可供func函數(shù)自行解釋,比如將其解釋成一個指向某個用戶自定義數(shù)據(jù)結(jié)構(gòu)的地址值。?

Linux在interrupt.h頭文件中又定義了兩個用來定義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在初始化時是被使能的(enabled),因為其count成員為0。而用DECLARE_TASKLET_DISABLED宏定義的tasklet在初始時是被禁止的(disabled),因為其count等于1。?

6.2.2?改變一個tasklet狀態(tài)的操作?
在這里,tasklet狀態(tài)指兩個方面:(1)state成員所表示的運行狀態(tài);(2)count成員決定的使能/禁止狀態(tài)。?
(1)改變一個tasklet的運行狀態(tài)?
state成員中的bit[0]表示一個tasklet是否已被調(diào)度去等待執(zhí)行,bit[1]表示一個tasklet是否正在某個CPU上執(zhí)行。對于state變量中某位的改變必須是一個原子操作,因此可以用定義在include/asm/bitops.h頭文件中的位操作來進行。?
由于bit[1]這一位(即TASKLET_STATE_RUN)僅僅對于SMP系統(tǒng)才有意義,因此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系統(tǒng)同,tasklet_trylock()宏將把一個tasklet_struct結(jié)構(gòu)變量中的state成員中的bit[1]位設置成1,同時還返回bit[1]位的非。因此,如果bit[1]位原有值為1(表示另外一個CPU正在執(zhí)行這個tasklet代碼),那么tasklet_trylock()宏將返回值0,也就表示上鎖不成功。如果bit[1]位的原有值為0,那么tasklet_trylock()宏將返回值1,表示加鎖成功。而在單CPU系統(tǒng)中,tasklet_trylock()宏總是返回為1。?
任何想要執(zhí)行某個tasklet代碼的程序都必須首先調(diào)用宏tasklet_trylock()來試圖對這個tasklet進行上鎖(即設置TASKLET_STATE_RUN位),且只能在上鎖成功的情況下才能執(zhí)行這個tasklet。建議!即使你的程序只在CPU系統(tǒng)上運行,你也要在執(zhí)行tasklet之前調(diào)用tasklet_trylock()宏,以便使你的代碼獲得良好可移植性。?
在SMP系統(tǒng)中,tasklet_unlock_wait()宏將一直不停地測試TASKLET_STATE_RUN位的值,直到該位的值變?yōu)?(即一直等待到解鎖),假如:CPU0正在執(zhí)行tasklet?A的代碼,在此期間,CPU1也想執(zhí)行tasklet?A的代碼,但CPU1發(fā)現(xiàn)tasklet?A的TASKLET_STATE_RUN位為1,于是它就可以通過tasklet_unlock_wait()宏等待tasklet?A被解鎖(也即TASKLET_STATE_RUN位被清零)。在單CPU系統(tǒng)中,這是一個空操作。?
宏tasklet_unlock()用來對一個tasklet進行解鎖操作,也即將TASKLET_STATE_RUN位清零。在單CPU系統(tǒng)中,這是一個空操作。?

(2)使能/禁止一個tasklet?
使能與禁止操作往往總是成對地被調(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()也是一個靜態(tài)inline函數(shù),它簡單地通過原子操作將count成員變量的值減1。如下所示(interrupt.h):?
static?inline?void?tasklet_disable_nosync(struct?tasklet_struct?*t)?
{?
atomic_inc(&t->count);?
}?
函數(shù)tasklet_enable()用于使能一個tasklet,如下所示(interrupt.h):?
static?inline?void?tasklet_enable(struct?tasklet_struct?*t)?
{?
atomic_dec(&t->count);?
}?

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

函數(shù)tasklet_kill()用來將一個已經(jīng)被調(diào)度了的tasklet殺死,即將其恢復到未調(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對列?
多個tasklet可以通過tasklet描述符中的next成員指針鏈接成一個單向?qū)α小榇?#xff0c;Linux專門在頭文件include/linux/interrupt.h中定義了數(shù)據(jù)結(jié)構(gòu)tasklet_head來描述一個tasklet對列的頭部指針。如下所示:?
struct?tasklet_head?
{?
struct?tasklet_struct?*list;?
}?__attribute__?((__aligned__(SMP_CACHE_BYTES)));?
盡管tasklet機制是特定于軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一種實現(xiàn),但是tasklet機制仍然屬于softirq機制的整體框架范圍內(nèi)的,因此,它的設計與實現(xiàn)仍然必須堅持“誰觸發(fā),誰執(zhí)行”的思想。為此,Linux為系統(tǒng)中的每一個CPU都定義了一個tasklet對列頭部,來表示應該有各個CPU負責執(zhí)行的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[]數(shù)組用于軟中斷向量TASKLET_SOFTIRQ,而tasklet_hi_vec[]數(shù)組則用于軟中斷向量HI_SOFTIRQ。也即,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量TASKLET_SOFTIRQ,那么對列tasklet_vec[i]中的每一個tasklet都將在CPUi服務于軟中斷向量TASKLET_SOFTIRQ時被CPUi所執(zhí)行。同樣地,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量HI_SOFTIRQ,那么隊列tasklet_vec[i]中的每一個tasklet都將CPUi在對軟中斷向量HI_SOFTIRQ進行服務時被CPUi所執(zhí)行。?
隊列tasklet_vec[I]和tasklet_hi_vec[I]中的各個tasklet是怎樣被所CPUi所執(zhí)行的呢?其關(guān)鍵就是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務程序——tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。下面我們就來分析這兩個函數(shù)。?

6.2.5?軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ?
Linux為軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ實現(xiàn)了專用的觸發(fā)函數(shù)和軟中斷服務函數(shù)。其中,tasklet_schedule()函數(shù)和tasklet_hi_schedule()函數(shù)分別用來在當前CPU上觸發(fā)軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入當前CPU所對應的tasklet隊列中去等待執(zhí)行。而tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)則分別是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務函數(shù)。在初始化函數(shù)softirq_init()中,這兩個軟中斷向量對應的描述符softirq_vec[0]和softirq_vec[3]中的action函數(shù)指針就被分別初始化成指向函數(shù)tasklet_hi_action()和函數(shù)tasklet_action()。?

(1)軟中斷向量TASKLET_SOFTIRQ的觸發(fā)函數(shù)tasklet_schedule()?
該函數(shù)實現(xiàn)在include/linux/interrupt.h頭文件中,是一個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指向要在當前CPU上被執(zhí)行的tasklet。對該函數(shù)的NOTE如下:?
①調(diào)用test_and_set_bit()函數(shù)將待調(diào)度的tasklet的state成員變量的bit[0]位(也即TASKLET_STATE_SCHED位)設置為1,該函數(shù)同時還返回TASKLET_STATE_SCHED位的原有值。因此如果bit[0]為的原有值已經(jīng)為1,那就說明這個tasklet已經(jīng)被調(diào)度到另一個CPU上去等待執(zhí)行了。由于一個tasklet在某一個時刻只能由一個CPU來執(zhí)行,因此tasklet_schedule()函數(shù)什么也不做就直接返回了。否則,就繼續(xù)下面的調(diào)度操作。?
②首先,調(diào)用local_irq_save()函數(shù)來關(guān)閉當前CPU的中斷,以保證下面的步驟在當前CPU上原子地被執(zhí)行。?
③然后,將待調(diào)度的tasklet添加到當前CPU對應的tasklet隊列的首部。?
④接著,調(diào)用__cpu_raise_softirq()函數(shù)在當前CPU上觸發(fā)軟中斷請求TASKLET_SOFTIRQ。?
⑤最后,調(diào)用local_irq_restore()函數(shù)來開當前CPU的中斷。?

(2)軟中斷向量TASKLET_SOFTIRQ的服務程序tasklet_action()?
函數(shù)tasklet_action()是tasklet機制與軟中斷向量TASKLET_SOFTIRQ的聯(lián)系紐帶。正是該函數(shù)將當前CPU的tasklet隊列中的各個tasklet放到當前CPU上來執(zhí)行的。該函數(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();?
}?
}?
注釋如下:?
①首先,在當前CPU關(guān)中斷的情況下,“原子”地讀取當前CPU的tasklet隊列頭部指針,將其保存到局部變量list指針中,然后將當前CPU的tasklet隊列頭部指針設置為NULL,以表示理論上當前CPU將不再有tasklet需要執(zhí)行(但最后的實際結(jié)果卻并不一定如此,下面將會看到)。?
②然后,用一個while{}循環(huán)來遍歷由list所指向的tasklet隊列,隊列中的各個元素就是將在當前CPU上執(zhí)行的tasklet。循環(huán)體的執(zhí)行步驟如下:?
l?用指針t來表示當前隊列元素,即當前需要執(zhí)行的tasklet。?
l?更新list指針為list->next,使它指向下一個要執(zhí)行的tasklet。?
l?用tasklet_trylock()宏試圖對當前要執(zhí)行的tasklet(由指針t所指向)進行加鎖,如果加鎖成功(當前沒有任何其他CPU正在執(zhí)行這個tasklet),則用原子讀函數(shù)atomic_read()進一步判斷count成員的值。如果count為0,說明這個tasklet是允許執(zhí)行的,于是:(1)先清除TASKLET_STATE_SCHED位;(2)然后,調(diào)用這個tasklet的可執(zhí)行函數(shù)func;(3)執(zhí)行barrier()操作;(4)調(diào)用宏tasklet_unlock()來清除TASKLET_STATE_RUN位。(5)最后,執(zhí)行continue語句跳過下面的步驟,回到while循環(huán)繼續(xù)遍歷隊列中的下一個元素。如果count不為0,說明這個tasklet是禁止運行的,于是調(diào)用tasklet_unlock()清除前面用tasklet_trylock()設置的TASKLET_STATE_RUN位。?
l?如果tasklet_trylock()加鎖不成功,或者因為當前tasklet的count值非0而不允許執(zhí)行時,我們必須將這個tasklet重新放回到當前CPU的tasklet隊列中,以留待這個CPU下次服務軟中斷向量TASKLET_SOFTIRQ時再執(zhí)行。為此進行這樣幾步操作:(1)先關(guān)CPU中斷,以保證下面操作的原子性。(2)把這個tasklet重新放回到當前CPU的tasklet隊列的首部;(3)調(diào)用__cpu_raise_softirq()函數(shù)在當前CPU上再觸發(fā)一次軟中斷請求TASKLET_SOFTIRQ;(4)開中斷。?
l?最后,回到while循環(huán)繼續(xù)遍歷隊列。?

(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的服務函數(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機制?
Bottom?Half機制在新的softirq機制中被保留下來,并作為softirq框架的一部分。其實現(xiàn)也似乎更為復雜些,因為它是通過tasklet機制這個中介橋梁來納入softirq框架中的。實際上,軟中斷向量HI_SOFTIRQ是內(nèi)核專用于執(zhí)行BH函數(shù)的。?

6.3.1?數(shù)據(jù)結(jié)構(gòu)的定義?
原有的32個BH函數(shù)指針被保留,定義在kernel/softirq.c文件中:?
static?void?(*bh_base[32])(void);?

但是,每個BH函數(shù)都對應有一個tasklet,并由tasklet的可執(zhí)行函數(shù)func來負責調(diào)用相應的bh函數(shù)(func函數(shù)的參數(shù)指定調(diào)用哪一個BH函數(shù))。與32個BH函數(shù)指針相對應的tasklet的定義如下所示(kernel/softirq.c):?
struct?tasklet_struct?bh_task_vec[32];?

上述tasklet數(shù)組使系統(tǒng)全局的,它對所有的CPU均可見。由于在某一個時刻只能有一個CPU在執(zhí)行BH函數(shù),因此定義一個全局的自旋鎖來保護BH函數(shù),如下所示(kernel/softirq.c):?
spinlock_t?global_bh_lock?=?SPIN_LOCK_UNLOCKED;?

6.3.2?初始化?
在softirq機制的初始化函數(shù)softirq_init()中將bh_task_vec[32]數(shù)組中的每一個tasklet中的func函數(shù)指針都設置為指向同一個函數(shù)bh_action,而data成員(也即func函數(shù)的調(diào)用參數(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ù)將負責相應地調(diào)用參數(shù)所指定的bh函數(shù)。該函數(shù)是連接tasklet機制與Bottom?Half機制的關(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);?
}?
對該函數(shù)的注釋如下:?
①首先,調(diào)用spin_trylock()函數(shù)試圖對自旋鎖global_bh_lock進行加鎖,同時該函數(shù)還將返回自旋鎖global_bh_lock的原有值的非。因此,如果global_bh_lock已被某個CPU上鎖而為非0值(那個CPU肯定在執(zhí)行某個BH函數(shù)),那么spin_trylock()將返回為0表示上鎖失敗,在這種情況下,當前CPU是不能執(zhí)行BH函數(shù)的,因為另一個CPU正在執(zhí)行BH函數(shù),于是執(zhí)行g(shù)oto語句跳轉(zhuǎn)到resched程序段,以便在當前CPU上再一次調(diào)度該BH函數(shù)。?
②調(diào)用hardirq_trylock()函數(shù)鎖定當前CPU,確保當前CPU不是處于硬件中斷請求服務中,如果鎖定失敗,跳轉(zhuǎn)到resched_unlock程序段,以便先對global_bh_lock解鎖,在重新調(diào)度一次該BH函數(shù)。?
③此時,我們已經(jīng)可以放心地在當前CPU上執(zhí)行BH函數(shù)了。當然,對應的BH函數(shù)指針bh_base[nr]必須有效才行。?
④從BH函數(shù)返回后,先調(diào)用hardirq_endlock()函數(shù)(實際上它什么也不干,調(diào)用它只是為了保此加、解鎖的成對關(guān)系),然后解除自旋鎖global_bh_lock,最后函數(shù)就可以返回了。?
⑤resched_unlock程序段:先解除自旋鎖global_bh_lock,然后執(zhí)行reched程序段。?
⑥r(nóng)esched程序段:當某個CPU正在執(zhí)行BH函數(shù)時,當前CPU就不能通過bh_action()函數(shù)來調(diào)用執(zhí)行任何BH函數(shù),所以就通過調(diào)用mark_bh()函數(shù)在當前CPU上再重新調(diào)度一次,以便將這個BH函數(shù)留待下次軟中斷服務時執(zhí)行。?

6.3.4?Bottom?Half的原有接口函數(shù)?
(1)init_bh()函數(shù)?
該函數(shù)用來在bh_base[]數(shù)組登記一個指定的bh函數(shù),如下所示(kernel/softirq.c):?
void?init_bh(int?nr,?void?(*routine)(void))?
{?
bh_base[nr]?=?routine;?
mb();?
}?

(2)remove_bh()函數(shù)?
該函數(shù)用來在bh_base[]數(shù)組中注銷指定的函數(shù)指針,同時將相對應的tasklet殺掉。如下所示(kernel/softirq.c):?
void?remove_bh(int?nr)?
{?
tasklet_kill(bh_task_vec+nr);?
bh_base[nr]?=?NULL;?
}?

(3)mark_bh()函數(shù)?
該函數(shù)用來向當前CPU標記由一個BH函數(shù)等待去執(zhí)行。它實際上通過調(diào)用tasklet_hi_schedule()函數(shù)將相應的tasklet加入到當前CPU的tasklet隊列tasklet_hi_vec[cpu]中,然后觸發(fā)軟中斷請求HI_SOFTIRQ,如下所示(include/linux/interrupt.h):?
static?inline?void?mark_bh(int?nr)?
{?
tasklet_hi_schedule(bh_task_vec+nr);?
}?

6.3.5?預定義的BH函數(shù)?
在32個BH函數(shù)指針中,大多數(shù)已經(jīng)固定用于一些常見的外設,比如:第0個BH函數(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?任務隊列Task?Queue?
任務隊列是與Bottom?Half機制緊密相連的。因為Bottom?Half機制只有有限的32個函數(shù)指針,而且大部分都已被系統(tǒng)預定義使用,所以早期版本的Linux內(nèi)核為了擴展Bottom?Half機制,就設計了任務隊列機制。?
所謂任務隊列就是指以雙向隊列形式連接起來的任務鏈表,每一個鏈表元數(shù)都描述了一個可執(zhí)行的任務(以函數(shù)的形式表現(xiàn))。如下圖所示:?

任務隊列機制實現(xiàn)在include/linux/tqueue.h頭文件中。?

6.4.1?數(shù)據(jù)結(jié)構(gòu)的定義?
Linux用數(shù)據(jù)結(jié)構(gòu)tq_struct來描述任務隊列中的每一個鏈表元數(shù)(即一個可執(zhí)行的任務):?
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?*/?
};?
這個數(shù)據(jù)結(jié)構(gòu)很簡單,在此就不詳述。?
然后,Linux定義了數(shù)據(jù)結(jié)構(gòu)task_queue來描述任務隊列的頭部,其實task_queue就是結(jié)構(gòu)類型list_head,如下:?
typedef?struct?list_head?task_queue;?

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

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

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

(2)queue_task()函數(shù)?
實現(xiàn)在tqueue.h頭文件中的內(nèi)聯(lián)函數(shù)queue_task()用來將一個指定的任務添加到某指定的任務隊列的尾部,如下:?
/*?
*?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?運行任務隊列?
函數(shù)run_task_queue()用于實現(xiàn)指定的任務隊列。它只有一個參數(shù):指針list——指向待運行的任務隊列頭部task_queue結(jié)構(gòu)變量。該函數(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()來判斷參數(shù)list指定的待運行任務隊列是否為空。如果不為空,則調(diào)用__run_task_queue()函數(shù)來實際運行這個有效的任務隊列。?
函數(shù)__run_task_queue()實現(xiàn)在kernel/softirq.c文件中。該函數(shù)將依次遍歷任務隊列中的每一個元數(shù),并調(diào)用執(zhí)行每一個元數(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);?
}?
}?
對該函數(shù)的注釋如下:?
(1)首先,用一個局部的表頭head來代替參數(shù)list所指向的表頭。這是因為:在__run_task_queue()函數(shù)的運行期間可能還會有新的任務加入到list任務隊列中來,但是__run_task_queue()函數(shù)顯然不想陷入無休止的不斷增加的任務處理中,因此它用局部的表頭head來代替參數(shù)list所指向的表頭,以使要執(zhí)行的任務個數(shù)固定化。為此:①先對全局的自旋鎖tqueue_lock進行加鎖,以實現(xiàn)對任務隊列的互斥訪問;②將局部的表頭head加在表頭(*list)和第一個元數(shù)之間。③將(*list)表頭從隊列中去除,并將其初始化為空。④解除自旋鎖tqueue_lock。?
(2)接下來,用一個while循環(huán)來遍歷整個隊列head,并調(diào)用執(zhí)行每一個隊列元素中的函數(shù)。注意!任務隊列是一個雙向循環(huán)隊列。?

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

任務隊列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ù),它們都僅僅是簡單地調(diào)用run_task_queue()函數(shù)來分別運行任務隊列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机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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