軟件中斷(softIRQ)是內(nèi)核提供的一種延遲執(zhí)行機制,它完全由軟件觸發(fā),雖然說是延遲機制,實際上,在大多數(shù)情況下,它與普通進程相比,能得到更快的響應(yīng)時間。軟中斷也是其他一些內(nèi)核機制的基礎(chǔ),比如tasklet,高分辨率timer等。
1. ?軟件中斷的數(shù)據(jù)結(jié)構(gòu)
1.1 ?struct softirq_action
內(nèi)核用softirq_action結(jié)構(gòu)管理軟件中斷的注冊和激活等操作,它的定義如下:
[cpp]?view plain?copy
struct?softirq_action??
{??
void????(*action)(struct?softirq_action?*);??
};??
非常簡單,只有一個用于回調(diào)的函數(shù)指針。軟件中斷的資源是有限的,內(nèi)核目前只實現(xiàn)了10種類型的軟件中斷,它們是:
[cpp]?view plain?copy
enum??
{??
HI_SOFTIRQ=0,??
TIMER_SOFTIRQ,??
NET_TX_SOFTIRQ,??
NET_RX_SOFTIRQ,??
BLOCK_SOFTIRQ,??
BLOCK_IOPOLL_SOFTIRQ,??
TASKLET_SOFTIRQ,??
SCHED_SOFTIRQ,??
HRTIMER_SOFTIRQ,??
RCU_SOFTIRQ,????/*?Preferable?RCU?should?always?be?the?last?softirq?*/??
NR_SOFTIRQS??
};??
內(nèi)核的開發(fā)者們不建議我們擅自增加軟件中斷的數(shù)量,如果需要新的軟件中斷,盡可能把它們實現(xiàn)為基于軟件中斷的tasklet形式。與上面的枚舉值相對應(yīng),內(nèi)核定義了一個softirq_action的結(jié)構(gòu)數(shù)組,每種軟中斷對應(yīng)數(shù)組中的一項:
[cpp]?view plain?copy
static?struct?softirq_action?softirq_vec[NR_SOFTIRQS]?__cacheline_aligned_in_smp;??
1.2 ?irq_cpustat_t
多個軟中斷可以同時在多個cpu運行,就算是同一種軟中斷,也有可能同時在多個cpu上運行。內(nèi)核為每個cpu都管理著一個待決軟中斷變量(pending),它就是irq_cpustat_t:
[cpp]?view plain?copy
typedef?struct?{??
unsigned?int?__softirq_pending;??
}?____cacheline_aligned?irq_cpustat_t;??
[cpp]?view plain?copy
irq_cpustat_t?irq_stat[NR_CPUS]?____cacheline_aligned;??
__softirq_pending字段中的每一個bit,對應(yīng)著某一個軟中斷,某個bit被置位,說明有相應(yīng)的軟中斷等待處理。
1.3 ?軟中斷的守護進程ksoftirqd
在cpu的熱插拔階段,內(nèi)核為每個cpu創(chuàng)建了一個用于執(zhí)行軟件中斷的守護進程ksoftirqd,同時定義了一個per_cpu變量用于保存每個守護進程的task_struct結(jié)構(gòu)指針:
[cpp]?view plain?copy
DEFINE_PER_CPU(struct?task_struct?*,?ksoftirqd);??
大多數(shù)情況下,軟中斷都會在irq_exit階段被執(zhí)行,在irq_exit階段沒有處理完的軟中斷才有可能會在守護進程中執(zhí)行。
2. ?觸發(fā)軟中斷
要觸發(fā)一個軟中斷,只要調(diào)用api:raise_softirq即可,它的實現(xiàn)很簡單,先是關(guān)閉本地cpu中斷,然后調(diào)用:raise_softirq_irqoff
[cpp]?view plain?copy
void?raise_softirq(unsigned?int?nr)??
{??
unsigned?long?flags;??
local_irq_save(flags);??
raise_softirq_irqoff(nr);??
local_irq_restore(flags);??
}??
再看看raise_softirq_irqoff:
[cpp]?view plain?copy
inline?void?raise_softirq_irqoff(unsigned?int?nr)??
{??
__raise_softirq_irqoff(nr);??
......??
if?(!in_interrupt())??
wakeup_softirqd();??
}??
先是通過__raise_softirq_irqoff設(shè)置cpu的軟中斷pending標(biāo)志位(irq_stat[NR_CPUS] ),然后通過in_interrupt判斷現(xiàn)在是否在中斷上下文中,或者軟中斷是否被禁止,如果都不成立,則喚醒軟中斷的守護進程,在守護進程中執(zhí)行軟中斷的回調(diào)函數(shù)。否則什么也不做,軟中斷將會在中斷的退出階段被執(zhí)行。
3. ?軟中斷的執(zhí)行
基于上面所說,軟中斷的執(zhí)行既可以守護進程中執(zhí)行,也可以在中斷的退出階段執(zhí)行。實際上,軟中斷更多的是在中斷的退出階段執(zhí)行(irq_exit),以便達到更快的響應(yīng),加入守護進程機制,只是擔(dān)心一旦有大量的軟中斷等待執(zhí)行,會使得內(nèi)核過長地留在中斷上下文中。
3.1 ?在irq_exit中執(zhí)行
看看irq_exit的部分:
[cpp]?view plain?copy
void?irq_exit(void)??
{??
......??
sub_preempt_count(IRQ_EXIT_OFFSET);??
if?(!in_interrupt()?&&?local_softirq_pending())??
invoke_softirq();??
......??
}??
如果中斷發(fā)生嵌套,in_interrupt()保證了只有在最外層的中斷的irq_exit階段,invoke_interrupt才會被調(diào)用,當(dāng)然,local_softirq_pending也會實現(xiàn)判斷當(dāng)前cpu有無待決的軟中斷。代碼最終會進入__do_softirq中,內(nèi)核會保證調(diào)用__do_softirq時,本地cpu的中斷處于關(guān)閉狀態(tài),進入__do_softirq:
[cpp]?view plain?copy
asmlinkage?void?__do_softirq(void)??
{??
......??
pending?=?local_softirq_pending();??
__local_bh_disable((unsigned?long)__builtin_return_address(0),??
SOFTIRQ_OFFSET);??
restart:??
/*?Reset?the?pending?bitmask?before?enabling?irqs?*/??
set_softirq_pending(0);??
local_irq_enable();??
h?=?softirq_vec;??
do?{??
if?(pending?&?1)?{??
......??
trace_softirq_entry(vec_nr);??
h->action(h);??
trace_softirq_exit(vec_nr);??
......??
}??
h++;??
pending?>>=?1;??
}?while?(pending);??
local_irq_disable();??
pending?=?local_softirq_pending();??
if?(pending?&&?--max_restart)??
goto?restart;??
if?(pending)??
wakeup_softirqd();??
lockdep_softirq_exit();??
__local_bh_enable(SOFTIRQ_OFFSET);??
}??
首先取出pending的狀態(tài);
禁止軟中斷,主要是為了防止和軟中斷守護進程發(fā)生競爭;
清除所有的軟中斷待決標(biāo)志;
打開本地cpu中斷;
循環(huán)執(zhí)行待決軟中斷的回調(diào)函數(shù);
如果循環(huán)完畢,發(fā)現(xiàn)新的軟中斷被觸發(fā),則重新啟動循環(huán),直到以下條件滿足,才退出:
沒有新的軟中斷等待執(zhí)行;
循環(huán)已經(jīng)達到最大的循環(huán)次數(shù)MAX_SOFTIRQ_RESTART,目前的設(shè)定值時10次;
如果經(jīng)過MAX_SOFTIRQ_RESTART次循環(huán)后還未處理完,則激活守護進程,處理剩下的軟中斷;
推出前恢復(fù)軟中斷;
3.2 ?在ksoftirqd進程中執(zhí)行
從前面幾節(jié)的討論我們可以看出,軟中斷也可能由ksoftirqd守護進程執(zhí)行,這要發(fā)生在以下兩種情況下:
在irq_exit中執(zhí)行軟中斷,但是在經(jīng)過MAX_SOFTIRQ_RESTART次循環(huán)后,軟中斷還未處理完,這種情況雖然極少發(fā)生,但畢竟有可能;
內(nèi)核的其它代碼主動調(diào)用raise_softirq,而這時正好不是在中斷上下文中,守護進程將被喚醒;
守護進程最終也會調(diào)用__do_softirq執(zhí)行軟中斷的回調(diào),具體的代碼位于run_ksoftirqd函數(shù)中,內(nèi)核會關(guān)閉搶占的情況下執(zhí)行__do_softirq,具體的過程這里不做討論。
4. ?tasklet
因為內(nèi)核已經(jīng)定義好了10種軟中斷類型,并且不建議我們自行添加額外的軟中斷,所以對軟中斷的實現(xiàn)方式,我們主要是做一個簡單的了解,對于驅(qū)動程序的開發(fā)者來說,無需實現(xiàn)自己的軟中斷。但是,對于某些情況下,我們不希望一些操作直接在中斷的handler中執(zhí)行,但是又希望在稍后的時間里得到快速地處理,這就需要使用tasklet機制。 tasklet是建立在軟中斷上的一種延遲執(zhí)行機制,它的實現(xiàn)基于TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷類型。
4.1 ?tasklet_struct ? ? ? ?
在軟中斷的初始化函數(shù)softirq_init的最后,內(nèi)核注冊了TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷:
[cpp]?view plain?copy
void?__init?softirq_init(void)??
{??
......??
open_softirq(TASKLET_SOFTIRQ,?tasklet_action);??
open_softirq(HI_SOFTIRQ,?tasklet_hi_action);??
}??
內(nèi)核用一個tasklet_struct來表示一個tasklet,它的定義如下:
[cpp]?view plain?copy
struct?tasklet_struct??
{??
struct?tasklet_struct?*next;??
unsigned?long?state;??
atomic_t?count;??
void?(*func)(unsigned?long);??
unsigned?long?data;??
};??
next用于把同一個cpu的tasklet鏈接成一個鏈表,state用于表示該tasklet的當(dāng)前狀態(tài),目前只是用了最低的兩個bit,分別用于表示已經(jīng)準(zhǔn)備被調(diào)度執(zhí)行和已經(jīng)在另一個cpu上執(zhí)行:
[cpp]?view plain?copy
enum??
{??
TASKLET_STATE_SCHED,????/*?Tasklet?is?scheduled?for?execution?*/??
TASKLET_STATE_RUN???/*?Tasklet?is?running?(SMP?only)?*/??
};??
原子變量count用于tasklet對tasklet_disable和tasklet_enable的計數(shù),count為0時表示允許tasklet執(zhí)行,否則不允許執(zhí)行,每次tasklet_disable時,該值加1,tasklet_enable時該值減1。func是tasklet被執(zhí)行時的回調(diào)函數(shù)指針,data則用作回調(diào)函數(shù)func的參數(shù)。
4.2 ?初始化一個tasklet
有兩種辦法初始化一個tasklet,第一種是靜態(tài)初始化,使用以下兩個宏,這兩個宏定義一個tasklet_struct結(jié)構(gòu),并用相應(yīng)的參數(shù)對結(jié)構(gòu)中的字段進行初始化:
DECLARE_TASKLET(name, func, data);定義名字為name的tasklet,默認為enable狀態(tài),也就是count字段等于0。
DECLARE_TASKLET_DISABLED(name, func, data);定義名字為name的tasklet,默認為enable狀態(tài),也就是count字段等于1。
第二個是動態(tài)初始化方法:先定義一個tasklet_struct,然后用tasklet_init函數(shù)進行初始化,該方法默認tasklet處于enable狀態(tài):
[cpp]?view plain?copy
struct?tasklet_struct?tasklet_xxx;??
......??
tasklet_init(&tasklet_xxx,?func,?data);??
4.3 ?tasklet的使用方法
使能和禁止tasklet,使用以下函數(shù):
tasklet_disable() ?通過給count字段加1來禁止一個tasklet,如果tasklet正在運行中,則等待運行完畢才返回(通過TASKLET_STATE_RUN標(biāo)志)。
tasklet_disable_nosync() ?tasklet_disable的異步版本,它不會等待tasklet運行完畢。
tasklet_enable() ?使能tasklet,只是簡單地給count字段減1。
調(diào)度tasklet的執(zhí)行,使用以下函數(shù):
tasklet_schedule(struct tasklet_struct *t) ?如果TASKLET_STATE_SCHED標(biāo)志為0,則置位TASKLET_STATE_SCHED,然后把tasklet掛到該cpu等待執(zhí)行的tasklet鏈表上,接著發(fā)出TASKLET_SOFTIRQ軟件中斷請求。
tasklet_hi_schedule(struct tasklet_struct *t) ?效果同上,區(qū)別是它發(fā)出的是HI_SOFTIRQ軟件中斷請求。
銷毀tasklet,使用以下函數(shù):
tasklet_kill(struct tasklet_struct *t) ?如果tasklet處于TASKLET_STATE_SCHED狀態(tài),或者tasklet正在執(zhí)行,則會等待tasklet執(zhí)行完畢,然后清除TASKLET_STATE_SCHED狀態(tài)。
4.4 ?tasklet的內(nèi)部執(zhí)行機制
內(nèi)核為每個cpu用定義了一個tasklet_head結(jié)構(gòu),用于管理每個cpu上的tasklet的調(diào)度和執(zhí)行:
[cpp]?view plain?copy
struct?tasklet_head??
{??
struct?tasklet_struct?*head;??
struct?tasklet_struct?**tail;??
};??
static?DEFINE_PER_CPU(struct?tasklet_head,?tasklet_vec);??
static?DEFINE_PER_CPU(struct?tasklet_head,?tasklet_hi_vec);??
回到4.1節(jié),我們知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ這兩個軟中斷來實現(xiàn)的,兩個軟中斷只是有優(yōu)先級的差別,所以我們只討論TASKLET_SOFTIRQ的實現(xiàn),TASKLET_SOFTIRQ的中斷回調(diào)函數(shù)是tasklet_action,我們看看它的代碼:
[cpp]?view plain?copy
static?void?tasklet_action(struct?softirq_action?*a)??
{??
struct?tasklet_struct?*list;??
local_irq_disable();??
list?=?__this_cpu_read(tasklet_vec.head);??
__this_cpu_write(tasklet_vec.head,?NULL);??
__this_cpu_write(tasklet_vec.tail,?&__get_cpu_var(tasklet_vec).head);??
local_irq_enable();??
while?(list)?{??
struct?tasklet_struct?*t?=?list;??
list?=?list->next;??
if?(tasklet_trylock(t))?{??
if?(!atomic_read(&t->count))?{??
if?(!test_and_clear_bit(TASKLET_STATE_SCHED,?&t->state))??
BUG();??
t->func(t->data);??
tasklet_unlock(t);??
continue;??
}??
tasklet_unlock(t);??
}??
local_irq_disable();??
t->next?=?NULL;??
*__this_cpu_read(tasklet_vec.tail)?=?t;??
__this_cpu_write(tasklet_vec.tail,?&(t->next));??
__raise_softirq_irqoff(TASKLET_SOFTIRQ);??
local_irq_enable();??
}??
}??
解析如下:
關(guān)閉本地中斷的前提下,移出當(dāng)前cpu的待處理tasklet鏈表到一個臨時鏈表后,清除當(dāng)前cpu的tasklet鏈表,之所以這樣處理,是為了處理當(dāng)前tasklet鏈表的時候,允許新的tasklet被調(diào)度進待處理鏈表中。
遍歷臨時鏈表,用tasklet_trylock判斷當(dāng)前tasklet是否已經(jīng)在其他cpu上運行,而且tasklet沒有被禁止:
如果沒有運行,也沒有禁止,則清除TASKLET_STATE_SCHED狀態(tài)位,執(zhí)行tasklet的回調(diào)函數(shù)。
如果已經(jīng)在運行,或者被禁止,則把該tasklet重新添加會當(dāng)前cpu的待處理tasklet鏈表上,然后觸發(fā)TASKLET_SOFTIRQ軟中斷,等待下一次軟中斷時再次執(zhí)行。
分析到這了我有個疑問,看了上面的代碼,如果一個tasklet被tasklet_schedule后,在沒有被執(zhí)行前被tasklet_disable了,豈不是會無窮無盡地引發(fā)TASKLET_SOFTIRQ軟中斷?
通過以上的分析,我們需要注意的是,tasklet有以下幾個特征:
同一個tasklet只能同時在一個cpu上執(zhí)行,但不同的tasklet可以同時在不同的cpu上執(zhí)行;
一旦tasklet_schedule被調(diào)用,內(nèi)核會保證tasklet一定會在某個cpu上執(zhí)行一次;
如果tasklet_schedule被調(diào)用時,tasklet不是出于正在執(zhí)行狀態(tài),則它只會執(zhí)行一次;
如果tasklet_schedule被調(diào)用時,tasklet已經(jīng)正在執(zhí)行,則它會在稍后被調(diào)度再次被執(zhí)行;
兩個tasklet之間如果有資源沖突,應(yīng)該要用自旋鎖進行同步保護;
?
評論
查看更多