資料介紹
本文從Linux內(nèi)核幾種軟中斷機(jī)制相互關(guān)系和發(fā)展沿革入手,分析了這些機(jī)制的實(shí)現(xiàn)方法,給出了它們的基本用法。
軟中斷概況
軟中斷是利用硬件中斷的概念,用軟件方式進(jìn)行模擬,實(shí)現(xiàn)宏觀上的異步執(zhí)行效果。很多情況下,軟中斷和“信號(hào)”有些類似,同時(shí),軟中斷又是和硬中斷相對(duì)應(yīng)的,“硬中斷是外部設(shè)備對(duì)CPU的中斷”,“軟中斷通常是硬中斷服務(wù)程序?qū)?nèi)核的中斷”,“信號(hào)則是由內(nèi)核(或其他進(jìn)程)對(duì)某個(gè)進(jìn)程的中斷”(《Linux內(nèi)核源代碼情景分析》第三章)。軟中斷的一種典型應(yīng)用就是所謂的“下半部”(bottom half),它的得名來自于將硬件中斷處理分離成“上半部”和“下半部”兩個(gè)階段的機(jī)制:上半部在屏蔽中斷的上下文中運(yùn)行,用于完成關(guān)鍵性的處理動(dòng)作;而下半部則相對(duì)來說并不是非常緊急的,通常還是比較耗時(shí)的,因此由系統(tǒng)自行安排運(yùn)行時(shí)機(jī),不在中斷服務(wù)上下文中執(zhí)行。bottom half的應(yīng)用也是激勵(lì)內(nèi)核發(fā)展出目前的軟中斷機(jī)制的原因,因此,我們先從bottom half的實(shí)現(xiàn)開始。
bottom half
在Linux內(nèi)核中,bottom half通常用“bh”表示,最初用于在特權(quán)級(jí)較低的上下文中完成中斷服務(wù)的非關(guān)鍵耗時(shí)動(dòng)作,現(xiàn)在也用于一切可在低優(yōu)先級(jí)的上下文中執(zhí)行的異步動(dòng)作。最早的bottom half實(shí)現(xiàn)是借用中斷向量表的方式,在目前的2.4.x內(nèi)核中仍然可以看到:
static void (*bh_base[32])(void); /* kernel/softirq.c */
系統(tǒng)如此定義了一個(gè)函數(shù)指針數(shù)組,共有32個(gè)函數(shù)指針,采用數(shù)組索引來訪問,與此相對(duì)應(yīng)的是一套函數(shù):
void init_bh(int nr,void (*routine)(void));
為第nr個(gè)函數(shù)指針賦值為routine。
void remove_bh(int nr);
動(dòng)作與init_bh()相反,卸下nr函數(shù)指針。
void mark_bh(int nr);
標(biāo)志第nr個(gè)bottom half可執(zhí)行了。
由于歷史的原因,bh_base各個(gè)函數(shù)指針位置大多有了預(yù)定義的意義,在v2.4.2內(nèi)核里有這樣一個(gè)枚舉:
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
};
并約定某個(gè)驅(qū)動(dòng)使用某個(gè)bottom half位置,比如串口中斷就約定使用SERIAL_BH,現(xiàn)在我們用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但語義已經(jīng)很不一樣了,因?yàn)檎麄€(gè)bottom half的使用方式已經(jīng)很不一樣了,這三個(gè)函數(shù)僅僅是在接口上保持了向下兼容,在實(shí)現(xiàn)上一直都在隨著內(nèi)核的軟中斷機(jī)制在變?,F(xiàn)在,在2.4.x內(nèi)核里,它用的是tasklet機(jī)制。
task queue
在介紹tasklet之前,有必要先看看出現(xiàn)得更早一些的task queue機(jī)制。顯而易見,原始的bottom half機(jī)制有幾個(gè)很大的局限,最重要的一個(gè)就是個(gè)數(shù)限制在32個(gè)以內(nèi),隨著系統(tǒng)硬件越來越多,軟中斷的應(yīng)用范圍越來越大,這個(gè)數(shù)目顯然是不夠用的,而且,每個(gè)bottom half上只能掛接一個(gè)函數(shù),也是不夠用的。因此,在2.0.x內(nèi)核里,已經(jīng)在用task queue(任務(wù)隊(duì)列)的辦法對(duì)其進(jìn)行了擴(kuò)充,這里使用的是2.4.2中的實(shí)現(xiàn)。
task queue是在系統(tǒng)隊(duì)列數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上建成的,以下即為task queue的數(shù)據(jù)結(jié)構(gòu),定義在include/linux/tqueue.h中:
struct tq_struct {
struct list_head list; /* 鏈表結(jié)構(gòu) */
unsigned long sync; /* 初識(shí)為0,入隊(duì)時(shí)原子的置1,以避免重復(fù)入隊(duì) */
void (*routine)(void *); /* 激活時(shí)調(diào)用的函數(shù) */
void *data; /* routine(data) */
};
typedef struct list_head task_queue;
在使用時(shí),按照下列步驟進(jìn)行:
DECLARE_TASK_QUEUE(my_tqueue); /* 定義一個(gè)my_tqueue,實(shí)際上就是一個(gè)以tq_struct為元素的list_head隊(duì)列 */
說明并定義一個(gè)tq_struct變量my_task;
queue_task(&my_task,&my_tqueue); /* 將my_task注冊(cè)到my_tqueue中 */
run_task_queue(&my_tqueue); /* 在適當(dāng)?shù)臅r(shí)候手工啟動(dòng)my_tqueue */
大多數(shù)情況下,都沒有必要調(diào)用DECLARE_TASK_QUEUE()定義自己的task queue,因?yàn)橄到y(tǒng)已經(jīng)預(yù)定義了三個(gè)task queue:
tq_timer,由時(shí)鐘中斷服務(wù)程序啟動(dòng);
tq_immediate,在中斷返回前以及schedule()函數(shù)中啟動(dòng);
tq_disk,內(nèi)存管理模塊內(nèi)部使用。
一般使用tq_immediate就可以完成大多數(shù)異步任務(wù)了。
run_task_queue(task_queue *list)函數(shù)可用于啟動(dòng)list中掛接的所有task,可以手動(dòng)調(diào)用,也可以掛接在上面提到的bottom half向量表中啟動(dòng)。以run_task_queue()作為bh_base[nr]的函數(shù)指針,實(shí)際上就是擴(kuò)充了每個(gè)bottom half的函數(shù)句柄數(shù),而對(duì)于系統(tǒng)預(yù)定義的tq_timer和tq_immediate的確是分別掛接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH沒有如此使用,但TQUEUE_BH也是在do_timer()中啟動(dòng)的),從而可以用于擴(kuò)充bottom half的個(gè)數(shù)。此時(shí),不需要手工調(diào)用run_task_queue()(這原本就不合適),而只需調(diào)用mark_bh(IMMEDIATE_BH),讓bottom half機(jī)制在合適的時(shí)候調(diào)度它。
tasklet
由上看出,task queue以bottom half為基礎(chǔ);而bottom half在v2.4.x中則以新引入的tasklet為實(shí)現(xiàn)基礎(chǔ)。
之所以引入tasklet,最主要的考慮是為了更好的支持SMP,提高SMP多個(gè)CPU的利用率:不同的tasklet可以同時(shí)運(yùn)行于不同的CPU上。在它的源碼注釋中還說明了幾點(diǎn)特性,歸結(jié)為一點(diǎn),就是:同一個(gè)tasklet只會(huì)在一個(gè)CPU上運(yùn)行。
struct tasklet_struct
{
struct tasklet_struct *next; /* 隊(duì)列指針 */
unsigned long state; /* tasklet的狀態(tài),按位操作,目前定義了兩個(gè)位的含義:
TASKLET_STATE_SCHED(第0位)或TASKLET_STATE_RUN(第1位) */
atomic_t count; /* 引用計(jì)數(shù),通常用1表示disabled */
void (*func)(unsigned long); /* 函數(shù)指針 */
unsigned long data; /* func(data) */
};
把上面的結(jié)構(gòu)與tq_struct比較,可以看出,tasklet擴(kuò)充了一點(diǎn)功能,主要是state屬性,用于CPU間的同步。
tasklet的使用相當(dāng)簡(jiǎn)單:
定義一個(gè)處理函數(shù)void my_tasklet_func(unsigned long);
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /* 定義一個(gè)tasklet結(jié)構(gòu)my_tasklet,與my_tasklet_func(data)函數(shù)相關(guān)聯(lián),相當(dāng)于DECLARE_TASK_QUEUE() */
tasklet_schedule(&my_tasklet); /* 登記my_tasklet,允許系統(tǒng)在適當(dāng)?shù)臅r(shí)候進(jìn)行調(diào)度運(yùn)行,相當(dāng)于queue_task(&my_task,&tq_immediate)和mark_bh(IMMEDIATE_BH) */
可見tasklet的使用比task queue更簡(jiǎn)單,而且,tasklet還能更好的支持SMP結(jié)構(gòu),因此,在新的2.4.x內(nèi)核中,tasklet是建議的異步任務(wù)執(zhí)行機(jī)制。除了以上提到的使用步驟外,tasklet機(jī)制還提供了另外一些調(diào)用接口:
DECLARE_TASKLET_DISABLED(name,function,data); /* 和DECLARE_TASKLET()類似,不過即使被調(diào)度到也不會(huì)馬上運(yùn)行,必須等到enable */
tasklet_enable(struct tasklet_struct *); /* tasklet使能 */
tasklet_disble(struct tasklet_struct *); /* 禁用tasklet,只要tasklet還沒運(yùn)行,則會(huì)推遲到它被enable */
tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); /* 類似DECLARE_TASKLET() */
tasklet_kill(struct tasklet_struct *); /* 清除指定tasklet的可調(diào)度位,即不允許調(diào)度該tasklet,但不做tasklet本身的清除 */
前面提到過,在2.4.x內(nèi)核中,bottom half是利用tasklet機(jī)制實(shí)現(xiàn)的,它表現(xiàn)在所有的bottom half動(dòng)作都以一類tasklet的形式運(yùn)行,這類tasklet與我們一般使用的tasklet不同。
在2.4.x中,系統(tǒng)定義了兩個(gè)tasklet隊(duì)列的向量表,每個(gè)向量對(duì)應(yīng)一個(gè)CPU(向量表大小為系統(tǒng)能支持的CPU最大個(gè)數(shù),SMP方式下目前2.4.2為32)組織成一個(gè)tasklet鏈表:
struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
另外,對(duì)于32個(gè)bottom half,系統(tǒng)也定義了對(duì)應(yīng)的32個(gè)tasklet結(jié)構(gòu):
struct tasklet_struct bh_task_vec[32];
在軟中斷子系統(tǒng)初始化時(shí),這組tasklet的動(dòng)作被初始化為bh_action(nr),而bh_action(nr)就會(huì)去調(diào)用bh_base[nr]的函數(shù)指針,從而與bottom half的語義掛鉤。mark_bh(nr)被實(shí)現(xiàn)為調(diào)用tasklet_hi_schedule(bh_tasklet_vec+nr),在這個(gè)函數(shù)中,bh_tasklet_vec[nr]將被掛接在tasklet_hi_vec[cpu]鏈上(其中cpu為當(dāng)前cpu編號(hào),也就是說哪個(gè)cpu提出了bottom half的請(qǐng)求,則在哪個(gè)cpu上執(zhí)行該請(qǐng)求),然后激發(fā)HI_SOFTIRQ軟中斷信號(hào),從而在HI_SOFTIRQ的中斷響應(yīng)中啟動(dòng)運(yùn)行。
tasklet_schedule(&my_tasklet)將把my_tasklet掛接到tasklet_vec[cpu]上,激發(fā)TASKLET_SOFTIRQ,在TASKLET_SOFTIRQ的中斷響應(yīng)中執(zhí)行。HI_SOFTIRQ和TASKLET_SOFTIRQ是softirq子系統(tǒng)中的術(shù)語,下一節(jié)將對(duì)它做介紹。
softirq
從前面的討論可以看出,task queue基于bottom half,bottom half基于tasklet,而tasklet則基于softirq。
可以這么說,softirq沿用的是最早的bottom half思想,但在這個(gè)“bottom half”機(jī)制之上,已經(jīng)實(shí)現(xiàn)了一個(gè)更加龐大和復(fù)雜的軟中斷子系統(tǒng)。
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned;
這個(gè)softirq_vec[]僅比bh_base[]增加了action()函數(shù)的參數(shù),在執(zhí)行上,softirq比bottom half的限制更少。
和bottom half類似,系統(tǒng)也預(yù)定義了幾個(gè)softirq_vec[]結(jié)構(gòu)的用途,通過以下枚舉表示:
enum
{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};
HI_SOFTIRQ被用于實(shí)現(xiàn)bottom half,TASKLET_SOFTIRQ用于公共的tasklet使用,NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于網(wǎng)絡(luò)子系統(tǒng)的報(bào)文收發(fā)。在軟中斷子系統(tǒng)初始化(softirq_init())時(shí),調(diào)用了open_softirq()對(duì)HI_SOFTIRQ和TASKLET_SOFTIRQ做了初始化:
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
open_softirq()會(huì)填充softirq_vec[nr],將action和data設(shè)為傳入的參數(shù)。TASKLET_SOFTIRQ填充為tasklet_action(NULL),HI_SOFTIRQ填充為tasklet_hi_action(NULL),在do_softirq()函數(shù)中,這兩個(gè)函數(shù)會(huì)被調(diào)用,分別啟動(dòng)tasklet_vec[cpu]和tasklet_hi_vec[cpu]鏈上的tasklet運(yùn)行。
static inline void __cpu_raise_softirq(int cpu, int nr)
這個(gè)函數(shù)用來激活軟中斷,實(shí)際上就是第cpu號(hào)CPU的第nr號(hào)軟中斷的active位置1。在do_softirq()中將判斷這個(gè)active位。tasklet_schedule()和tasklet_hi_schedule()都會(huì)調(diào)用這個(gè)函數(shù)。
do_softirq()有4個(gè)執(zhí)行時(shí)機(jī),分別是:從系統(tǒng)調(diào)用中返回(arch/i386/kernel/entry.S::ENTRY(ret_from_sys_call))、從異常中返回(arch/i386/kernel/entry.S::ret_from_exception標(biāo)號(hào))、調(diào)度程序中(kernel/sched.c::schedule()),以及處理完硬件中斷之后(kernel/irq.c::do_IRQ())。它將遍歷所有的softirq_vec,依次啟動(dòng)其中的action()。需要注意的是,軟中斷服務(wù)程序,不允許在硬中斷服務(wù)程序中執(zhí)行,也不允許在軟中斷服務(wù)程序中嵌套執(zhí)行,但允許多個(gè)軟中斷服務(wù)程序同時(shí)在多個(gè)CPU上并發(fā)。
使用示例
softirq作為一種底層機(jī)制,很少由內(nèi)核程序員直接使用,因此,這里的使用范例僅對(duì)其余幾種軟中斷機(jī)制。
1.bottom half
原有的bottom half用法在drivers/char/serial.c中還能看到,包括三個(gè)步驟:
init_bh(SERIAL_BH,do_serial_bh); //在串口設(shè)備的初始化函數(shù)rs_init()中,do_serial_bh()是處理函數(shù)
mark_bh(SERIAL_BH); //在rs_sched_event()中,這個(gè)函數(shù)由中斷處理例程調(diào)用
remove_bh(SERIAL_BH); //在串口設(shè)備的結(jié)束函數(shù)rs_fini()中調(diào)用
盡管邏輯上還是這么三步,但在do_serial_bh()函數(shù)中的動(dòng)作卻是啟動(dòng)一個(gè)task queue:run_task_queue(&tq_serial),而在rs_sched_event()中,mark_bh()之前調(diào)用的則是queue_task(。..,&tq_serial),也就是說串口bottom half已經(jīng)結(jié)合task queue使用了。而那些更通用一些的bottom half,比如IMMEDIATE_BH,更是必須要與task queue結(jié)合使用,而且一般情況下,task queue也很少獨(dú)立使用,而是與bottom half結(jié)合,這在下一節(jié)task queue使用示例中可以清楚地看到。
2.task queue
一般來說,程序員很少自己定義task queue,而是結(jié)合bottom half,直接使用系統(tǒng)預(yù)定義的tq_immediate等,尤以tq_immediate使用最頻繁??匆韵麓a段,節(jié)選自drivers/block/floppy.c:
static struct tq_struct floppy_tq; //定義一個(gè)tq_struct結(jié)構(gòu)變量floppy_tq,不需要作其他初始化動(dòng)作
static void schedule_bh( void (*handler)(void*) )
{
floppy_tq.routine = (void *)(void *) handler;
//指定floppy_tq的調(diào)用函數(shù)為handler,不需要考慮floppy_tq中的其他域
queue_task(&floppy_tq, &tq_immediate);
//將floppy_tq加入到tq_immediate中
mark_bh(IMMEDIATE_BH);
//激活I(lǐng)MMEDIATE_BH,由上所述可知,
這實(shí)際上將引發(fā)一個(gè)軟中斷來執(zhí)行tq_immediate中掛接的各個(gè)函數(shù)
}
當(dāng)然,我們還是可以定義并使用自己的task queue,而不用tq_immediate,在drivers/char/serial.c中提到的tq_serial就是串口驅(qū)動(dòng)自己定義的:
static DECLARE_TASK_QUEUE(tq_serial);
此時(shí)就需要自行調(diào)用run_task_queue(&tq_serial)來啟動(dòng)其中的函數(shù)了,因此并不常用。
3.tasklet
這是比task queue和bottom half更加強(qiáng)大的一套軟中斷機(jī)制,使用上也相對(duì)簡(jiǎn)單,見下面代碼段:
1: void foo_tasklet_action(unsigned long t);
2: unsigned long stop_tasklet;
3: DECLARE_TASKLET(foo_tasklet, foo_tasklet_action, 0);
4: void foo_tasklet_action(unsigned long t)
5: {
6: //do something
7:
8: //reschedule
9: if(!stop_tasklet)
10: tasklet_schedule(&foo_tasklet);
11: }
12: void foo_init(void)
13: {
14: stop_tasklet=0;
15: tasklet_schedule(&foo_tasklet);
16: }
17: void foo_clean(void)
18: {
19: stop_tasklet=1;
20: tasklet_kill(&foo_tasklet);
21: }
這個(gè)比較完整的代碼段利用一個(gè)反復(fù)執(zhí)行的tasklet來完成一定的工作,首先在第3行定義foo_tasklet,與相應(yīng)的動(dòng)作函數(shù)foo_tasklet_action相關(guān)聯(lián),并指定foo_tasklet_action()的參數(shù)為0。雖然此處以0為參數(shù),但也同樣可以指定有意義的其他參數(shù)值,但需要注意的是,這個(gè)參數(shù)值在定義的時(shí)候必須是有固定值的變量或常數(shù)(如上例),也就是說可以定義一個(gè)全局變量,將其地址作為參數(shù)傳給foo_tasklet_action(),例如:
int flags;
DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,&flags);
void foo_tasklet_action(unsigned long t)
{
int flags=*(int *)t;
。..
}
這樣就可以通過改變flags的值將信息帶入tasklet中。直接在DECLARE_TASKLET處填寫flags,gcc會(huì)報(bào)“initializer element is not constant”錯(cuò)。
第9、10行是一種RESCHEDULE的技術(shù)。我們知道,一個(gè)tasklet執(zhí)行結(jié)束后,它就從執(zhí)行隊(duì)列里刪除了,要想重新讓它轉(zhuǎn)入運(yùn)行,必須重新調(diào)用tasklet_schedule(),調(diào)用的時(shí)機(jī)可以是某個(gè)事件發(fā)生的時(shí)候,也可以是像這樣在tasklet動(dòng)作中。而這種reschedule技術(shù)將導(dǎo)致tasklet永遠(yuǎn)運(yùn)行,因此在子系統(tǒng)退出時(shí),應(yīng)該有辦法停止tasklet。stop_tasklet變量和tasklet_kill()就是干這個(gè)的。
?
軟中斷概況
軟中斷是利用硬件中斷的概念,用軟件方式進(jìn)行模擬,實(shí)現(xiàn)宏觀上的異步執(zhí)行效果。很多情況下,軟中斷和“信號(hào)”有些類似,同時(shí),軟中斷又是和硬中斷相對(duì)應(yīng)的,“硬中斷是外部設(shè)備對(duì)CPU的中斷”,“軟中斷通常是硬中斷服務(wù)程序?qū)?nèi)核的中斷”,“信號(hào)則是由內(nèi)核(或其他進(jìn)程)對(duì)某個(gè)進(jìn)程的中斷”(《Linux內(nèi)核源代碼情景分析》第三章)。軟中斷的一種典型應(yīng)用就是所謂的“下半部”(bottom half),它的得名來自于將硬件中斷處理分離成“上半部”和“下半部”兩個(gè)階段的機(jī)制:上半部在屏蔽中斷的上下文中運(yùn)行,用于完成關(guān)鍵性的處理動(dòng)作;而下半部則相對(duì)來說并不是非常緊急的,通常還是比較耗時(shí)的,因此由系統(tǒng)自行安排運(yùn)行時(shí)機(jī),不在中斷服務(wù)上下文中執(zhí)行。bottom half的應(yīng)用也是激勵(lì)內(nèi)核發(fā)展出目前的軟中斷機(jī)制的原因,因此,我們先從bottom half的實(shí)現(xiàn)開始。
bottom half
在Linux內(nèi)核中,bottom half通常用“bh”表示,最初用于在特權(quán)級(jí)較低的上下文中完成中斷服務(wù)的非關(guān)鍵耗時(shí)動(dòng)作,現(xiàn)在也用于一切可在低優(yōu)先級(jí)的上下文中執(zhí)行的異步動(dòng)作。最早的bottom half實(shí)現(xiàn)是借用中斷向量表的方式,在目前的2.4.x內(nèi)核中仍然可以看到:
static void (*bh_base[32])(void); /* kernel/softirq.c */
系統(tǒng)如此定義了一個(gè)函數(shù)指針數(shù)組,共有32個(gè)函數(shù)指針,采用數(shù)組索引來訪問,與此相對(duì)應(yīng)的是一套函數(shù):
void init_bh(int nr,void (*routine)(void));
為第nr個(gè)函數(shù)指針賦值為routine。
void remove_bh(int nr);
動(dòng)作與init_bh()相反,卸下nr函數(shù)指針。
void mark_bh(int nr);
標(biāo)志第nr個(gè)bottom half可執(zhí)行了。
由于歷史的原因,bh_base各個(gè)函數(shù)指針位置大多有了預(yù)定義的意義,在v2.4.2內(nèi)核里有這樣一個(gè)枚舉:
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
};
并約定某個(gè)驅(qū)動(dòng)使用某個(gè)bottom half位置,比如串口中斷就約定使用SERIAL_BH,現(xiàn)在我們用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但語義已經(jīng)很不一樣了,因?yàn)檎麄€(gè)bottom half的使用方式已經(jīng)很不一樣了,這三個(gè)函數(shù)僅僅是在接口上保持了向下兼容,在實(shí)現(xiàn)上一直都在隨著內(nèi)核的軟中斷機(jī)制在變?,F(xiàn)在,在2.4.x內(nèi)核里,它用的是tasklet機(jī)制。
task queue
在介紹tasklet之前,有必要先看看出現(xiàn)得更早一些的task queue機(jī)制。顯而易見,原始的bottom half機(jī)制有幾個(gè)很大的局限,最重要的一個(gè)就是個(gè)數(shù)限制在32個(gè)以內(nèi),隨著系統(tǒng)硬件越來越多,軟中斷的應(yīng)用范圍越來越大,這個(gè)數(shù)目顯然是不夠用的,而且,每個(gè)bottom half上只能掛接一個(gè)函數(shù),也是不夠用的。因此,在2.0.x內(nèi)核里,已經(jīng)在用task queue(任務(wù)隊(duì)列)的辦法對(duì)其進(jìn)行了擴(kuò)充,這里使用的是2.4.2中的實(shí)現(xiàn)。
task queue是在系統(tǒng)隊(duì)列數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上建成的,以下即為task queue的數(shù)據(jù)結(jié)構(gòu),定義在include/linux/tqueue.h中:
struct tq_struct {
struct list_head list; /* 鏈表結(jié)構(gòu) */
unsigned long sync; /* 初識(shí)為0,入隊(duì)時(shí)原子的置1,以避免重復(fù)入隊(duì) */
void (*routine)(void *); /* 激活時(shí)調(diào)用的函數(shù) */
void *data; /* routine(data) */
};
typedef struct list_head task_queue;
在使用時(shí),按照下列步驟進(jìn)行:
DECLARE_TASK_QUEUE(my_tqueue); /* 定義一個(gè)my_tqueue,實(shí)際上就是一個(gè)以tq_struct為元素的list_head隊(duì)列 */
說明并定義一個(gè)tq_struct變量my_task;
queue_task(&my_task,&my_tqueue); /* 將my_task注冊(cè)到my_tqueue中 */
run_task_queue(&my_tqueue); /* 在適當(dāng)?shù)臅r(shí)候手工啟動(dòng)my_tqueue */
大多數(shù)情況下,都沒有必要調(diào)用DECLARE_TASK_QUEUE()定義自己的task queue,因?yàn)橄到y(tǒng)已經(jīng)預(yù)定義了三個(gè)task queue:
tq_timer,由時(shí)鐘中斷服務(wù)程序啟動(dòng);
tq_immediate,在中斷返回前以及schedule()函數(shù)中啟動(dòng);
tq_disk,內(nèi)存管理模塊內(nèi)部使用。
一般使用tq_immediate就可以完成大多數(shù)異步任務(wù)了。
run_task_queue(task_queue *list)函數(shù)可用于啟動(dòng)list中掛接的所有task,可以手動(dòng)調(diào)用,也可以掛接在上面提到的bottom half向量表中啟動(dòng)。以run_task_queue()作為bh_base[nr]的函數(shù)指針,實(shí)際上就是擴(kuò)充了每個(gè)bottom half的函數(shù)句柄數(shù),而對(duì)于系統(tǒng)預(yù)定義的tq_timer和tq_immediate的確是分別掛接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH沒有如此使用,但TQUEUE_BH也是在do_timer()中啟動(dòng)的),從而可以用于擴(kuò)充bottom half的個(gè)數(shù)。此時(shí),不需要手工調(diào)用run_task_queue()(這原本就不合適),而只需調(diào)用mark_bh(IMMEDIATE_BH),讓bottom half機(jī)制在合適的時(shí)候調(diào)度它。
tasklet
由上看出,task queue以bottom half為基礎(chǔ);而bottom half在v2.4.x中則以新引入的tasklet為實(shí)現(xiàn)基礎(chǔ)。
之所以引入tasklet,最主要的考慮是為了更好的支持SMP,提高SMP多個(gè)CPU的利用率:不同的tasklet可以同時(shí)運(yùn)行于不同的CPU上。在它的源碼注釋中還說明了幾點(diǎn)特性,歸結(jié)為一點(diǎn),就是:同一個(gè)tasklet只會(huì)在一個(gè)CPU上運(yùn)行。
struct tasklet_struct
{
struct tasklet_struct *next; /* 隊(duì)列指針 */
unsigned long state; /* tasklet的狀態(tài),按位操作,目前定義了兩個(gè)位的含義:
TASKLET_STATE_SCHED(第0位)或TASKLET_STATE_RUN(第1位) */
atomic_t count; /* 引用計(jì)數(shù),通常用1表示disabled */
void (*func)(unsigned long); /* 函數(shù)指針 */
unsigned long data; /* func(data) */
};
把上面的結(jié)構(gòu)與tq_struct比較,可以看出,tasklet擴(kuò)充了一點(diǎn)功能,主要是state屬性,用于CPU間的同步。
tasklet的使用相當(dāng)簡(jiǎn)單:
定義一個(gè)處理函數(shù)void my_tasklet_func(unsigned long);
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /* 定義一個(gè)tasklet結(jié)構(gòu)my_tasklet,與my_tasklet_func(data)函數(shù)相關(guān)聯(lián),相當(dāng)于DECLARE_TASK_QUEUE() */
tasklet_schedule(&my_tasklet); /* 登記my_tasklet,允許系統(tǒng)在適當(dāng)?shù)臅r(shí)候進(jìn)行調(diào)度運(yùn)行,相當(dāng)于queue_task(&my_task,&tq_immediate)和mark_bh(IMMEDIATE_BH) */
可見tasklet的使用比task queue更簡(jiǎn)單,而且,tasklet還能更好的支持SMP結(jié)構(gòu),因此,在新的2.4.x內(nèi)核中,tasklet是建議的異步任務(wù)執(zhí)行機(jī)制。除了以上提到的使用步驟外,tasklet機(jī)制還提供了另外一些調(diào)用接口:
DECLARE_TASKLET_DISABLED(name,function,data); /* 和DECLARE_TASKLET()類似,不過即使被調(diào)度到也不會(huì)馬上運(yùn)行,必須等到enable */
tasklet_enable(struct tasklet_struct *); /* tasklet使能 */
tasklet_disble(struct tasklet_struct *); /* 禁用tasklet,只要tasklet還沒運(yùn)行,則會(huì)推遲到它被enable */
tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); /* 類似DECLARE_TASKLET() */
tasklet_kill(struct tasklet_struct *); /* 清除指定tasklet的可調(diào)度位,即不允許調(diào)度該tasklet,但不做tasklet本身的清除 */
前面提到過,在2.4.x內(nèi)核中,bottom half是利用tasklet機(jī)制實(shí)現(xiàn)的,它表現(xiàn)在所有的bottom half動(dòng)作都以一類tasklet的形式運(yùn)行,這類tasklet與我們一般使用的tasklet不同。
在2.4.x中,系統(tǒng)定義了兩個(gè)tasklet隊(duì)列的向量表,每個(gè)向量對(duì)應(yīng)一個(gè)CPU(向量表大小為系統(tǒng)能支持的CPU最大個(gè)數(shù),SMP方式下目前2.4.2為32)組織成一個(gè)tasklet鏈表:
struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
另外,對(duì)于32個(gè)bottom half,系統(tǒng)也定義了對(duì)應(yīng)的32個(gè)tasklet結(jié)構(gòu):
struct tasklet_struct bh_task_vec[32];
在軟中斷子系統(tǒng)初始化時(shí),這組tasklet的動(dòng)作被初始化為bh_action(nr),而bh_action(nr)就會(huì)去調(diào)用bh_base[nr]的函數(shù)指針,從而與bottom half的語義掛鉤。mark_bh(nr)被實(shí)現(xiàn)為調(diào)用tasklet_hi_schedule(bh_tasklet_vec+nr),在這個(gè)函數(shù)中,bh_tasklet_vec[nr]將被掛接在tasklet_hi_vec[cpu]鏈上(其中cpu為當(dāng)前cpu編號(hào),也就是說哪個(gè)cpu提出了bottom half的請(qǐng)求,則在哪個(gè)cpu上執(zhí)行該請(qǐng)求),然后激發(fā)HI_SOFTIRQ軟中斷信號(hào),從而在HI_SOFTIRQ的中斷響應(yīng)中啟動(dòng)運(yùn)行。
tasklet_schedule(&my_tasklet)將把my_tasklet掛接到tasklet_vec[cpu]上,激發(fā)TASKLET_SOFTIRQ,在TASKLET_SOFTIRQ的中斷響應(yīng)中執(zhí)行。HI_SOFTIRQ和TASKLET_SOFTIRQ是softirq子系統(tǒng)中的術(shù)語,下一節(jié)將對(duì)它做介紹。
softirq
從前面的討論可以看出,task queue基于bottom half,bottom half基于tasklet,而tasklet則基于softirq。
可以這么說,softirq沿用的是最早的bottom half思想,但在這個(gè)“bottom half”機(jī)制之上,已經(jīng)實(shí)現(xiàn)了一個(gè)更加龐大和復(fù)雜的軟中斷子系統(tǒng)。
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned;
這個(gè)softirq_vec[]僅比bh_base[]增加了action()函數(shù)的參數(shù),在執(zhí)行上,softirq比bottom half的限制更少。
和bottom half類似,系統(tǒng)也預(yù)定義了幾個(gè)softirq_vec[]結(jié)構(gòu)的用途,通過以下枚舉表示:
enum
{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};
HI_SOFTIRQ被用于實(shí)現(xiàn)bottom half,TASKLET_SOFTIRQ用于公共的tasklet使用,NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于網(wǎng)絡(luò)子系統(tǒng)的報(bào)文收發(fā)。在軟中斷子系統(tǒng)初始化(softirq_init())時(shí),調(diào)用了open_softirq()對(duì)HI_SOFTIRQ和TASKLET_SOFTIRQ做了初始化:
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
open_softirq()會(huì)填充softirq_vec[nr],將action和data設(shè)為傳入的參數(shù)。TASKLET_SOFTIRQ填充為tasklet_action(NULL),HI_SOFTIRQ填充為tasklet_hi_action(NULL),在do_softirq()函數(shù)中,這兩個(gè)函數(shù)會(huì)被調(diào)用,分別啟動(dòng)tasklet_vec[cpu]和tasklet_hi_vec[cpu]鏈上的tasklet運(yùn)行。
static inline void __cpu_raise_softirq(int cpu, int nr)
這個(gè)函數(shù)用來激活軟中斷,實(shí)際上就是第cpu號(hào)CPU的第nr號(hào)軟中斷的active位置1。在do_softirq()中將判斷這個(gè)active位。tasklet_schedule()和tasklet_hi_schedule()都會(huì)調(diào)用這個(gè)函數(shù)。
do_softirq()有4個(gè)執(zhí)行時(shí)機(jī),分別是:從系統(tǒng)調(diào)用中返回(arch/i386/kernel/entry.S::ENTRY(ret_from_sys_call))、從異常中返回(arch/i386/kernel/entry.S::ret_from_exception標(biāo)號(hào))、調(diào)度程序中(kernel/sched.c::schedule()),以及處理完硬件中斷之后(kernel/irq.c::do_IRQ())。它將遍歷所有的softirq_vec,依次啟動(dòng)其中的action()。需要注意的是,軟中斷服務(wù)程序,不允許在硬中斷服務(wù)程序中執(zhí)行,也不允許在軟中斷服務(wù)程序中嵌套執(zhí)行,但允許多個(gè)軟中斷服務(wù)程序同時(shí)在多個(gè)CPU上并發(fā)。
使用示例
softirq作為一種底層機(jī)制,很少由內(nèi)核程序員直接使用,因此,這里的使用范例僅對(duì)其余幾種軟中斷機(jī)制。
1.bottom half
原有的bottom half用法在drivers/char/serial.c中還能看到,包括三個(gè)步驟:
init_bh(SERIAL_BH,do_serial_bh); //在串口設(shè)備的初始化函數(shù)rs_init()中,do_serial_bh()是處理函數(shù)
mark_bh(SERIAL_BH); //在rs_sched_event()中,這個(gè)函數(shù)由中斷處理例程調(diào)用
remove_bh(SERIAL_BH); //在串口設(shè)備的結(jié)束函數(shù)rs_fini()中調(diào)用
盡管邏輯上還是這么三步,但在do_serial_bh()函數(shù)中的動(dòng)作卻是啟動(dòng)一個(gè)task queue:run_task_queue(&tq_serial),而在rs_sched_event()中,mark_bh()之前調(diào)用的則是queue_task(。..,&tq_serial),也就是說串口bottom half已經(jīng)結(jié)合task queue使用了。而那些更通用一些的bottom half,比如IMMEDIATE_BH,更是必須要與task queue結(jié)合使用,而且一般情況下,task queue也很少獨(dú)立使用,而是與bottom half結(jié)合,這在下一節(jié)task queue使用示例中可以清楚地看到。
2.task queue
一般來說,程序員很少自己定義task queue,而是結(jié)合bottom half,直接使用系統(tǒng)預(yù)定義的tq_immediate等,尤以tq_immediate使用最頻繁??匆韵麓a段,節(jié)選自drivers/block/floppy.c:
static struct tq_struct floppy_tq; //定義一個(gè)tq_struct結(jié)構(gòu)變量floppy_tq,不需要作其他初始化動(dòng)作
static void schedule_bh( void (*handler)(void*) )
{
floppy_tq.routine = (void *)(void *) handler;
//指定floppy_tq的調(diào)用函數(shù)為handler,不需要考慮floppy_tq中的其他域
queue_task(&floppy_tq, &tq_immediate);
//將floppy_tq加入到tq_immediate中
mark_bh(IMMEDIATE_BH);
//激活I(lǐng)MMEDIATE_BH,由上所述可知,
這實(shí)際上將引發(fā)一個(gè)軟中斷來執(zhí)行tq_immediate中掛接的各個(gè)函數(shù)
}
當(dāng)然,我們還是可以定義并使用自己的task queue,而不用tq_immediate,在drivers/char/serial.c中提到的tq_serial就是串口驅(qū)動(dòng)自己定義的:
static DECLARE_TASK_QUEUE(tq_serial);
此時(shí)就需要自行調(diào)用run_task_queue(&tq_serial)來啟動(dòng)其中的函數(shù)了,因此并不常用。
3.tasklet
這是比task queue和bottom half更加強(qiáng)大的一套軟中斷機(jī)制,使用上也相對(duì)簡(jiǎn)單,見下面代碼段:
1: void foo_tasklet_action(unsigned long t);
2: unsigned long stop_tasklet;
3: DECLARE_TASKLET(foo_tasklet, foo_tasklet_action, 0);
4: void foo_tasklet_action(unsigned long t)
5: {
6: //do something
7:
8: //reschedule
9: if(!stop_tasklet)
10: tasklet_schedule(&foo_tasklet);
11: }
12: void foo_init(void)
13: {
14: stop_tasklet=0;
15: tasklet_schedule(&foo_tasklet);
16: }
17: void foo_clean(void)
18: {
19: stop_tasklet=1;
20: tasklet_kill(&foo_tasklet);
21: }
這個(gè)比較完整的代碼段利用一個(gè)反復(fù)執(zhí)行的tasklet來完成一定的工作,首先在第3行定義foo_tasklet,與相應(yīng)的動(dòng)作函數(shù)foo_tasklet_action相關(guān)聯(lián),并指定foo_tasklet_action()的參數(shù)為0。雖然此處以0為參數(shù),但也同樣可以指定有意義的其他參數(shù)值,但需要注意的是,這個(gè)參數(shù)值在定義的時(shí)候必須是有固定值的變量或常數(shù)(如上例),也就是說可以定義一個(gè)全局變量,將其地址作為參數(shù)傳給foo_tasklet_action(),例如:
int flags;
DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,&flags);
void foo_tasklet_action(unsigned long t)
{
int flags=*(int *)t;
。..
}
這樣就可以通過改變flags的值將信息帶入tasklet中。直接在DECLARE_TASKLET處填寫flags,gcc會(huì)報(bào)“initializer element is not constant”錯(cuò)。
第9、10行是一種RESCHEDULE的技術(shù)。我們知道,一個(gè)tasklet執(zhí)行結(jié)束后,它就從執(zhí)行隊(duì)列里刪除了,要想重新讓它轉(zhuǎn)入運(yùn)行,必須重新調(diào)用tasklet_schedule(),調(diào)用的時(shí)機(jī)可以是某個(gè)事件發(fā)生的時(shí)候,也可以是像這樣在tasklet動(dòng)作中。而這種reschedule技術(shù)將導(dǎo)致tasklet永遠(yuǎn)運(yùn)行,因此在子系統(tǒng)退出時(shí),應(yīng)該有辦法停止tasklet。stop_tasklet變量和tasklet_kill()就是干這個(gè)的。
?
下載該資料的人也在下載
下載該資料的人還在閱讀
更多 >
- linux內(nèi)核-時(shí)鐘中斷
- Linux內(nèi)核文件Cache機(jī)制
- 嵌入式LINUX系統(tǒng)內(nèi)核和內(nèi)核模塊調(diào)試
- Linux內(nèi)核配置的網(wǎng)絡(luò)資料說明 14次下載
- Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)的課程實(shí)驗(yàn)指導(dǎo)書 7次下載
- 如何才能編譯Linux的內(nèi)核 8次下載
- Kinetis的中斷機(jī)制詳細(xì)資料說明 7次下載
- 如何進(jìn)行Linux內(nèi)核的中斷和異常分析資料說明
- REDIce-Linux--靈活的實(shí)時(shí)Linux內(nèi)核 12次下載
- 關(guān)于Linux 2.6內(nèi)核Makefile的分析 1次下載
- 基于Linux 2.6內(nèi)核Makefile分析 0次下載
- 基于Linux內(nèi)核2_6的進(jìn)程攔截機(jī)制的研究和實(shí)現(xiàn)_王全民 3次下載
- linux 中斷和設(shè)備驅(qū)動(dòng)
- 面向嵌入式Linux系統(tǒng)的軟中斷設(shè)計(jì)與實(shí)現(xiàn)
- Linux的內(nèi)核教程 0次下載
- Linux內(nèi)核中的頁面分配機(jī)制 135次閱讀
- 獲取Linux內(nèi)核源碼的方法 514次閱讀
- 淺談Linux kernel中的同步機(jī)制 745次閱讀
- Linux內(nèi)核的安全性對(duì)Android的影響 1109次閱讀
- Linux:QEMU調(diào)試內(nèi)核的步驟 3040次閱讀
- 干貨:Linux內(nèi)核中等待隊(duì)列的四個(gè)用法 2765次閱讀
- Linux內(nèi)核中有哪些鎖 3356次閱讀
- 基于s3c2410的任務(wù)切換軟中斷級(jí)服務(wù)的實(shí)現(xiàn) 1567次閱讀
- 淺談51內(nèi)核單片機(jī)中斷源 4964次閱讀
- Linux內(nèi)核與Android的關(guān)系 4504次閱讀
- 基于嵌入式Linux內(nèi)核的系統(tǒng)設(shè)備驅(qū)動(dòng)程序開發(fā)設(shè)計(jì) 1113次閱讀
- Linux內(nèi)核地址映射模型與Linux內(nèi)核高端內(nèi)存詳解 3397次閱讀
- 基于ARM9和NANDFlash對(duì)uboot和Linux內(nèi)核進(jìn)行修改 2715次閱讀
- 基于Linux 軟中斷機(jī)制以及tasklet、工作隊(duì)列機(jī)制分析 3795次閱讀
- Linux內(nèi)核開發(fā)工具介紹 4641次閱讀
下載排行
本周
- 1TC358743XBG評(píng)估板參考手冊(cè)
- 1.36 MB | 330次下載 | 免費(fèi)
- 2開關(guān)電源基礎(chǔ)知識(shí)
- 5.73 MB | 6次下載 | 免費(fèi)
- 3100W短波放大電路圖
- 0.05 MB | 4次下載 | 3 積分
- 4嵌入式linux-聊天程序設(shè)計(jì)
- 0.60 MB | 3次下載 | 免費(fèi)
- 5基于FPGA的光纖通信系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)
- 0.61 MB | 2次下載 | 免費(fèi)
- 6基于FPGA的C8051F單片機(jī)開發(fā)板設(shè)計(jì)
- 0.70 MB | 2次下載 | 免費(fèi)
- 751單片機(jī)窗簾控制器仿真程序
- 1.93 MB | 2次下載 | 免費(fèi)
- 8基于51單片機(jī)的RGB調(diào)色燈程序仿真
- 0.86 MB | 2次下載 | 免費(fèi)
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 2555集成電路應(yīng)用800例(新編版)
- 0.00 MB | 33564次下載 | 免費(fèi)
- 3接口電路圖大全
- 未知 | 30323次下載 | 免費(fèi)
- 4開關(guān)電源設(shè)計(jì)實(shí)例指南
- 未知 | 21548次下載 | 免費(fèi)
- 5電氣工程師手冊(cè)免費(fèi)下載(新編第二版pdf電子書)
- 0.00 MB | 15349次下載 | 免費(fèi)
- 6數(shù)字電路基礎(chǔ)pdf(下載)
- 未知 | 13750次下載 | 免費(fèi)
- 7電子制作實(shí)例集錦 下載
- 未知 | 8113次下載 | 免費(fèi)
- 8《LED驅(qū)動(dòng)電路設(shè)計(jì)》 溫德爾著
- 0.00 MB | 6653次下載 | 免費(fèi)
總榜
- 1matlab軟件下載入口
- 未知 | 935054次下載 | 免費(fèi)
- 2protel99se軟件下載(可英文版轉(zhuǎn)中文版)
- 78.1 MB | 537796次下載 | 免費(fèi)
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420026次下載 | 免費(fèi)
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 5Altium DXP2002下載入口
- 未知 | 233046次下載 | 免費(fèi)
- 6電路仿真軟件multisim 10.0免費(fèi)下載
- 340992 | 191185次下載 | 免費(fèi)
- 7十天學(xué)會(huì)AVR單片機(jī)與C語言視頻教程 下載
- 158M | 183278次下載 | 免費(fèi)
- 8proe5.0野火版下載(中文版免費(fèi)下載)
- 未知 | 138040次下載 | 免費(fèi)
評(píng)論
查看更多