亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
12下一頁
最近訪問板塊 發(fā)新帖
查看: 24872 | 回復(fù): 18
打印 上一主題 下一主題

Linux內(nèi)核的Softirq機制 [復(fù)制鏈接]

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2008-11-03 11:23 |只看該作者 |倒序瀏覽
第六章 Linux內(nèi)核的Softirq機制
(By 詹榮開,NUDT)





Copyright © 2003 by 詹榮開
E-mail:zhanrk@sohu.com
Linux-2.4.0
Version 1.0.0,2003-2-14




摘要:本文主要從內(nèi)核實現(xiàn)的角度分析了Linux 2.4.0內(nèi)核的Softirq機制。本文是為那些想要了解Linux I/O子系統(tǒng)的讀者和Linux驅(qū)動程序開發(fā)人員而寫的。
關(guān)鍵詞:Linux、Softirq、軟中斷、Bottom half、設(shè)備驅(qū)動程序

申明:這份文檔是按照自由軟件開放源代碼的精神發(fā)布的,任何人可以免費獲得、使用和重新發(fā)布,但是你沒有限制別人重新發(fā)布你發(fā)布內(nèi)容的權(quán)利。發(fā)布本文的目的是希望它能對讀者有用,但沒有任何擔保,甚至沒有適合特定目的的隱含的擔保。更詳細的情況請參閱GNU通用公共許可證(GPL),以及GNU自由文檔協(xié)議(GFDL)。

你應(yīng)該已經(jīng)和文檔一起收到一份GNU通用公共許可證(GPL)的副本。如果還沒有,寫信給:
The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA

歡迎各位指出文檔中的錯誤與疑問。

前言
中斷服務(wù)程序往往都是在CPU關(guān)中斷的條件下執(zhí)行的,以避免中斷嵌套而使控制復(fù)雜化。但是CPU關(guān)中斷的時間不能太長,否則容易丟失中斷信號。為此,Linux將中斷服務(wù)程序一分為二,各稱作“Top Half”和“Bottom Half”。前者通常對時間要求較為嚴格,必須在中斷請求發(fā)生后立即或至少在一定的時間限制內(nèi)完成。因此為了保證這種處理能原子地完成,Top Half通常是在CPU關(guān)中斷的條件下執(zhí)行的。具體地說,Top Half的范圍包括:從在IDT中登記的中斷入口函數(shù)一直到驅(qū)動程序注冊在中斷服務(wù)隊列中的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機制的設(shè)計與實現(xiàn)中自始自終都貫徹了一個思想:“誰觸發(fā),誰執(zhí)行”(Who marks,Who runs),也即觸發(fā)軟中斷的那個CPU負責執(zhí)行它所觸發(fā)的軟中斷,而且每個CPU都由它自己的軟中斷觸發(fā)與控制機制。這個設(shè)計思想也使得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指向軟中斷請求的服務(wù)函數(shù),而指針data則指向由服務(wù)函數(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)所對應(yīng)的軟中斷請求描述符就是softirq_vec[i]。這個數(shù)組是個系統(tǒng)全局數(shù)組,也即它被所有的CPU所共享。這里需要注意的一點是:每個CPU雖然都由它自己的觸發(fā)和控制機制,并且只執(zhí)行他自己所觸發(fā)的軟中斷請求,但是各個CPU所執(zhí)行的軟中斷服務(wù)例程卻是相同的,也即都是執(zhí)行softirq_vec[]數(shù)組中定義的軟中斷服務(wù)函數(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)。假設(shè)有一個編號為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。它通過將相應(yīng)的__softirq_active成員變量中的相應(yīng)位設(shè)置為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對軟中斷的預(yù)定義分類
在軟中斷向量0~31中,Linux內(nèi)核僅僅使用了軟中斷向量0~3,其余被留待系統(tǒng)以后擴展。Linux在頭文件include/linux/interrupt.h中對軟中斷向量0~3進行了預(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)用于實現(xiàn)高優(yōu)先級的軟中斷,如:高優(yōu)先級的tasklet(將在后面詳細描述)。軟中斷向量1和2則分別用于網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送與接收。軟中斷向量3(即TASKLET_SOFTIRQ)則用于實現(xiàn)諸如tasklet這樣的一般性軟中斷。關(guān)于tasklet我們將在后面詳細描述。NOTE!Linix內(nèi)核并不鼓勵一般用戶擴展使用剩余的軟中斷向量,因為它認為其預(yù)定義的軟中斷向量HI_SOFTIRQ和TASKLET_SOFTIRQ已經(jīng)足夠應(yīng)付絕大多數(shù)應(yīng)用。

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,并將它們的軟中斷服務(wù)函數(shù)指針分別指向tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。函數(shù)open_softirq()的主要作用是初始化設(shè)置軟中斷請求描述符softirq_vec[nr]。

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

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [報告]
發(fā)表于 2008-11-03 11:25 |只看該作者
6.2 tasklet機制
Tasklet機制是一種較為特殊的軟中斷。Tasklet一詞的原意是“小片任務(wù)”的意思,這里是指一小段可執(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上運行,而不像一般的軟中斷服務(wù)函數(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]位設(shè)置成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進行上鎖(即設(shè)置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殺死,即將其恢復(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對列
多個tasklet可以通過tasklet描述符中的next成員指針鏈接成一個單向?qū)α。為此,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)的,因此,它的設(shè)計與實現(xiàn)仍然必須堅持“誰觸發(fā),誰執(zhí)行”的思想。為此,Linux為系統(tǒng)中的每一個CPU都定義了一個tasklet對列頭部,來表示應(yīng)該有各個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服務(wù)于軟中斷向量TASKLET_SOFTIRQ時被CPUi所執(zhí)行。同樣地,如果CPUi(0≤i≤NR_CPUS-1)觸發(fā)了軟中斷向量HI_SOFTIRQ,那么隊列tasklet_vec[i]中的每一個tasklet都將CPUi在對軟中斷向量HI_SOFTIRQ進行服務(wù)時被CPUi所執(zhí)行。
隊列tasklet_vec[I]和tasklet_hi_vec[I]中的各個tasklet是怎樣被所CPUi所執(zhí)行的呢?其關(guān)鍵就是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務(wù)程序——tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)。下面我們就來分析這兩個函數(shù)。

6.2.5 軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ
Linux為軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ實現(xiàn)了專用的觸發(fā)函數(shù)和軟中斷服務(wù)函數(shù)。其中,tasklet_schedule()函數(shù)和tasklet_hi_schedule()函數(shù)分別用來在當前CPU上觸發(fā)軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入當前CPU所對應(yīng)的tasklet隊列中去等待執(zhí)行。而tasklet_action()函數(shù)和tasklet_hi_action()函數(shù)則分別是軟中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ的軟中斷服務(wù)函數(shù)。在初始化函數(shù)softirq_init()中,這兩個軟中斷向量對應(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ù)實現(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位)設(shè)置為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對應(yīng)的tasklet隊列的首部。
④接著,調(diào)用__cpu_raise_softirq()函數(shù)在當前CPU上觸發(fā)軟中斷請求TASKLET_SOFTIRQ。
⑤最后,調(diào)用local_irq_restore()函數(shù)來開當前CPU的中斷。

(2)軟中斷向量TASKLET_SOFTIRQ的服務(wù)程序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隊列頭部指針設(shè)置為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()設(shè)置的TASKLET_STATE_RUN位。
l 如果tasklet_trylock()加鎖不成功,或者因為當前tasklet的count值非0而不允許執(zhí)行時,我們必須將這個tasklet重新放回到當前CPU的tasklet隊列中,以留待這個CPU下次服務(wù)軟中斷向量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的服務(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
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
3 [報告]
發(fā)表于 2008-11-03 11:26 |只看該作者
.3 Bottom Half機制
Bottom Half機制在新的softirq機制中被保留下來,并作為softirq框架的一部分。其實現(xiàn)也似乎更為復(fù)雜些,因為它是通過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ù)都對應(yīng)有一個tasklet,并由tasklet的可執(zhí)行函數(shù)func來負責調(diào)用相應(yīng)的bh函數(shù)(func函數(shù)的參數(shù)指定調(diào)用哪一個BH函數(shù))。與32個BH函數(shù)指針相對應(yīng)的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è)置為指向同一個函數(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ù)將負責相應(yīng)地調(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不是處于硬件中斷請求服務(wù)中,如果鎖定失敗,跳轉(zhuǎn)到resched_unlock程序段,以便先對global_bh_lock解鎖,在重新調(diào)度一次該BH函數(shù)。
③此時,我們已經(jīng)可以放心地在當前CPU上執(zhí)行BH函數(shù)了。當然,對應(yīng)的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ù)留待下次軟中斷服務(wù)時執(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ù)指針,同時將相對應(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ù)用來向當前CPU標記由一個BH函數(shù)等待去執(zhí)行。它實際上通過調(diào)用tasklet_hi_schedule()函數(shù)將相應(yīng)的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 預(yù)定義的BH函數(shù)
在32個BH函數(shù)指針中,大多數(shù)已經(jīng)固定用于一些常見的外設(shè),比如:第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
};

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
4 [報告]
發(fā)表于 2008-11-03 11:26 |只看該作者
6.4 任務(wù)隊列Task Queue
任務(wù)隊列是與Bottom Half機制緊密相連的。因為Bottom Half機制只有有限的32個函數(shù)指針,而且大部分都已被系統(tǒng)預(yù)定義使用,所以早期版本的Linux內(nèi)核為了擴展Bottom Half機制,就設(shè)計了任務(wù)隊列機制。
所謂任務(wù)隊列就是指以雙向隊列形式連接起來的任務(wù)鏈表,每一個鏈表元數(shù)都描述了一個可執(zhí)行的任務(wù)(以函數(shù)的形式表現(xiàn))。如下圖所示:

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

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

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

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

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

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

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

任務(wù)隊列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ù)來分別運行任務(wù)隊列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);
}

論壇徽章:
36
IT運維版塊每日發(fā)帖之星
日期:2016-04-10 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-16 06:20:0015-16賽季CBA聯(lián)賽之廣東
日期:2016-04-16 19:59:32IT運維版塊每日發(fā)帖之星
日期:2016-04-18 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-19 06:20:00每日論壇發(fā)貼之星
日期:2016-04-19 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-25 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-06 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-08 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-13 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-28 06:20:00每日論壇發(fā)貼之星
日期:2016-05-28 06:20:00
5 [報告]
發(fā)表于 2008-11-03 12:00 |只看該作者
頂一下。好東西啊

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
6 [報告]
發(fā)表于 2008-11-03 12:17 |只看該作者
原帖由 Godbach 于 2008-11-3 12:00 發(fā)表
頂一下。好東西啊


我接觸這一行,是從底層驅(qū)動往上走的,所以,我還是很喜歡從下往上走來領(lǐng)悟linux的精髓,呵呵。

[ 本帖最后由 dreamice 于 2008-11-6 10:09 編輯 ]

論壇徽章:
0
7 [報告]
發(fā)表于 2008-11-03 14:28 |只看該作者
very good, thanks

論壇徽章:
0
8 [報告]
發(fā)表于 2008-11-05 14:37 |只看該作者
好東西啊,要頂。

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
9 [報告]
發(fā)表于 2008-11-05 14:46 |只看該作者
原帖由 Zcanoe 于 2008-11-5 14:37 發(fā)表
好東西啊,要頂。



你們趕緊多發(fā)好帖,有積分獎勵滴

論壇徽章:
0
10 [報告]
發(fā)表于 2008-11-06 10:02 |只看該作者
版主老大啊,我發(fā)現(xiàn)詹榮開老大寫得很好,你還有他的文檔嗎?給大伙都貢獻出來吧。感激涕零啊。。。
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP