<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 嵌入式系統 > 設計應用 > Linux 2.4.x內核軟中斷機制

Linux 2.4.x內核軟中斷機制

作者: 時(shí)間:2010-05-06 來(lái)源:網(wǎng)絡(luò ) 收藏
本文從Linux內核幾種機制相互關(guān)系和發(fā)展沿革入手,分析了這些機制的實(shí)現方法,給出了它們的基本用法。

概況

本文引用地址:http://dyxdggzs.com/article/257971.htm

是利用硬件中斷的概念,用軟件方式進(jìn)行模擬,實(shí)現宏觀(guān)上的異步執行效果。很多情況下,軟中斷和信號有些類(lèi)似,同時(shí),軟中斷又是和硬中斷相對應的,硬中斷是外部設備對CPU的中斷,軟中斷通常是硬中斷服務(wù)程序對內核的中斷,信號則是由內核(或其他進(jìn)程)對某個(gè)進(jìn)程的中斷(《Linux內核》第三章)。軟中斷的一種典型應用就是所謂的下半部(bottom half),它的得名來(lái)自于將硬件中斷處理分離成上半部和下半部?jì)蓚€(gè)階段的機制:上半部在屏蔽中斷的上下文中運行,用于完成關(guān)鍵性的處理動(dòng)作;而下半部則相對來(lái)說(shuō)并不是非常緊急的,通常還是比較耗時(shí)的,因此由系統自行安排運行時(shí)機,不在中斷服務(wù)上下文中執行。bottom half的應用也是激勵內核發(fā)展出目前的軟中斷機制的原因,因此,我們先從bottom half的實(shí)現開(kāi)始。


bottom half

在Linux內核中,bottom half通常用bh表示,最初用于在特權級較低的上下文中完成中斷服務(wù)的非關(guān)鍵耗時(shí)動(dòng)作,現在也用于一切可在低優(yōu)先級的上下文中執行的異步動(dòng)作。最早的bottom half實(shí)現是借用中斷向量表的方式,在目前的2.4.x內核中仍然可以看到:

static void (*bh_base[32])(void);	         /* kernel/softirq.c */

系統如此定義了一個(gè)函數指針數組,共有32個(gè)函數指針,采用數組索引來(lái)訪(fǎng)問(wèn),與此相對應的是一套函數:

void init_bh(int nr,void (*routine)(void));

為第nr個(gè)函數指針賦值為routine。

void remove_bh(int nr);

動(dòng)作與init_bh()相反,卸下nr函數指針。

void mark_bh(int nr);

標志第nr個(gè)bottom half可執行了。

由于歷史的原因,bh_base各個(gè)函數指針位置大多有了預定義的意義,在v2.4.2內核里有這樣一個(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è)驅動(dòng)使用某個(gè)bottom half位置,比如串口中斷就約定使用SERIAL_BH,現在我們用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但語(yǔ)義已經(jīng)很不一樣了,因為整個(gè)bottom half的使用方式已經(jīng)很不一樣了,這三個(gè)函數僅僅是在接口上保持了向下兼容,在實(shí)現上一直都在隨著(zhù)內核的軟中斷機制在變?,F在,在2.4.x內核里,它用的是tasklet機制。

task queue

在介紹tasklet之前,有必要先看看出現得更早一些的task queue機制。顯而易見(jiàn),原始的bottom half機制有幾個(gè)很大的局限,最重要的一個(gè)就是個(gè)數限制在32個(gè)以?xún)?,隨著(zhù)系統硬件越來(lái)越多,軟中斷的應用范圍越來(lái)越大,這個(gè)數目顯然是不夠用的,而且,每個(gè)bottom half上只能掛接一個(gè)函數,也是不夠用的。因此,在2.0.x內核里,已經(jīng)在用task queue(任務(wù)隊列)的辦法對其進(jìn)行了擴充,這里使用的是2.4.2中的實(shí)現。

task queue是在系統隊列數據結構的基礎上建成的,以下即為task queue的數據結構,定義在include/linux/tqueue.h中:

struct tq_struct {struct list_head list;          /* 鏈表結構 */unsigned long sync;             /* 初識為0,入隊時(shí)原子的置1,以避免重復入隊 */void (*routine)(void *);        /* 激活時(shí)調用的函數 */void *data;                     /* routine(data) */};typedef struct list_head task_queue;

在使用時(shí),按照下列步驟進(jìn)行:

  1. DECLARE_TASK_QUEUE(my_tqueue); /* 定義一個(gè)my_tqueue,實(shí)際上就是一個(gè)以tq_struct為元素的list_head隊列 */
  2. 說(shuō)明并定義一個(gè)tq_struct變量my_task;
  3. queue_task(my_task,my_tqueue); /* 將my_task注冊到my_tqueue中 */
  4. run_task_queue(my_tqueue); /* 在適當的時(shí)候手工啟動(dòng)my_tqueue */

大多數情況下,都沒(méi)有必要調用DECLARE_TASK_QUEUE()定義自己的task queue,因為系統已經(jīng)預定義了三個(gè)task queue:

  1. tq_timer,由時(shí)鐘中斷服務(wù)程序啟動(dòng);
  2. tq_immediate,在中斷返回前以及schedule()函數中啟動(dòng);
  3. tq_disk,內存管理模塊內部使用。

一般使用tq_immediate就可以完成大多數異步任務(wù)了。

run_task_queue(task_queue *list)函數可用于啟動(dòng)list中掛接的所有task,可以手動(dòng)調用,也可以?huà)旖釉谏厦嫣岬降腷ottom half向量表中啟動(dòng)。以run_task_queue()作為bh_base[nr]的函數指針,實(shí)際上就是擴充了每個(gè)bottom half的函數句柄數,而對于系統預定義的tq_timer和tq_immediate的確是分別掛接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH沒(méi)有如此使用,但TQUEUE_BH也是在do_timer()中啟動(dòng)的),從而可以用于擴充bottom half的個(gè)數。此時(shí),不需要手工調用run_task_queue()(這原本就不合適),而只需調用mark_bh(IMMEDIATE_BH),讓bottom half機制在合適的時(shí)候調度它。

tasklet

由上看出,task queue以bottom half為基礎;而bottom half在v2.4.x中則以新引入的tasklet為實(shí)現基礎。

之所以引入tasklet,最主要的考慮是為了更好的支持SMP,提高SMP多個(gè)CPU的利用率:不同的tasklet可以同時(shí)運行于不同的CPU上。在它的源碼注釋中還說(shuō)明了幾點(diǎn)特性,歸結為一點(diǎn),就是:同一個(gè)tasklet只會(huì )在一個(gè)CPU上運行。

struct tasklet_struct{struct tasklet_struct *next;	/* 隊列指針 */unsigned long state;		/* tasklet的狀態(tài),按位操作,目前定義了兩個(gè)位的含義:TASKLET_STATE_SCHED(第0位)或TASKLET_STATE_RUN(第1位) */atomic_t count;			/* 引用計數,通常用1表示disabled */void (*func)(unsigned long);	/* 函數指針 */unsigned long data;		/* func(data) */};

把上面的結構與tq_struct比較,可以看出,tasklet擴充了一點(diǎn)功能,主要是state屬性,用于CPU間的同步。

tasklet的使用相當簡(jiǎn)單:

  1. 定義一個(gè)處理函數void my_tasklet_func(unsigned long);
  2. DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /* 定義一個(gè)tasklet結構my_tasklet,與my_tasklet_func(data)函數相關(guān)聯(lián),相當于DECLARE_TASK_QUEUE() */
  3. tasklet_schedule(my_tasklet); /* 登記my_tasklet,允許系統在適當的時(shí)候進(jìn)行調度運行,相當于queue_task(my_task,tq_immediate)和mark_bh(IMMEDIATE_BH) */

可見(jiàn)tasklet的使用比task queue更簡(jiǎn)單,而且,tasklet還能更好的支持SMP結構,因此,在新的2.4.x內核中,tasklet是建議的異步任務(wù)執行機制。除了以上提到的使用步驟外,tasklet機制還提供了另外一些調用接口:

DECLARE_TASKLET_DISABLED(name,function,data); /* 和DECLARE_TASKLET()類(lèi)似,不過(guò)即使被調度到也不會(huì )馬上運行,必須等到enable */
tasklet_enable(struct tasklet_struct *); /* tasklet使能 */
tasklet_disble(struct tasklet_struct *); /* 禁用tasklet,只要tasklet還沒(méi)運行,則會(huì )推遲到它被enable */
tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); /* 類(lèi)似DECLARE_TASKLET() */
tasklet_kill(struct tasklet_struct *); /* 清除指定tasklet的可調度位,即不允許調度該tasklet,但不做tasklet本身的清除 */

前面提到過(guò),在2.4.x內核中,bottom half是利用tasklet機制實(shí)現的,它表現在所有的bottom half動(dòng)作都以一類(lèi)tasklet的形式運行,這類(lèi)tasklet與我們一般使用的tasklet不同。

在2.4.x中,系統定義了兩個(gè)tasklet隊列的向量表,每個(gè)向量對應一個(gè)CPU(向量表大小為系統能支持的CPU最大個(gè)數,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;

另外,對于32個(gè)bottom half,系統也定義了對應的32個(gè)tasklet結構:

struct tasklet_struct bh_task_vec[32];

在軟中斷子系統初始化時(shí),這組tasklet的動(dòng)作被初始化為bh_action(nr),而bh_action(nr)就會(huì )去調用bh_base[nr]的函數指針,從而與bottom half的語(yǔ)義掛鉤。mark_bh(nr)被實(shí)現為調用tasklet_hi_schedule(bh_tasklet_vec+nr),在這個(gè)函數中,bh_tasklet_vec[nr]將被掛接在tasklet_hi_vec[cpu]鏈上(其中cpu為當前cpu編號,也就是說(shuō)哪個(gè)cpu提出了bottom half的請求,則在哪個(gè)cpu上執行該請求),然后激發(fā)HI_SOFTIRQ軟中斷信號,從而在HI_SOFTIRQ的中斷響應中啟動(dòng)運行。

tasklet_schedule(my_tasklet)將把my_tasklet掛接到tasklet_vec[cpu]上,激發(fā)TASKLET_SOFTIRQ,在TASKLET_SOFTIRQ的中斷響應中執行。HI_SOFTIRQ和TASKLET_SOFTIRQ是softirq子系統中的術(shù)語(yǔ),下一節將對它做介紹。


softirq

從前面的討論可以看出,task queue基于bottom half,bottom half基于tasklet,而tasklet則基于softirq。

可以這么說(shuō),softirq沿用的是最早的bottom half思想,但在這個(gè)bottom half機制之上,已經(jīng)實(shí)現了一個(gè)更加龐大和復雜的軟中斷子系統。

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()函數的參數,在執行上,softirq比bottom half的限制更少。

和bottom half類(lèi)似,系統也預定義了幾個(gè)softirq_vec[]結構的用途,通過(guò)以下枚舉表示:

enum{HI_SOFTIRQ=0,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,TASKLET_SOFTIRQ};

HI_SOFTIRQ被用于實(shí)現bottom half,TASKLET_SOFTIRQ用于公共的tasklet使用,NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于網(wǎng)絡(luò )子系統的報文收發(fā)。在軟中斷子系統初始化(softirq_init())時(shí),調用了open_softirq()對HI_SOFTIRQ和TASKLET_SOFTIRQ做了初始化:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

open_softirq()會(huì )填充softirq_vec[nr],將action和data設為傳入的參數。TASKLET_SOFTIRQ填充為tasklet_action(NULL),HI_SOFTIRQ填充為tasklet_hi_action(NULL),在do_softirq()函數中,這兩個(gè)函數會(huì )被調用,分別啟動(dòng)tasklet_vec[cpu]和tasklet_hi_vec[cpu]鏈上的tasklet運行。

static inline void __cpu_raise_softirq(int cpu, int nr)

這個(gè)函數用來(lái)激活軟中斷,實(shí)際上就是第cpu號CPU的第nr號軟中斷的active位置1。在do_softirq()中將判斷這個(gè)active位。tasklet_schedule()和tasklet_hi_schedule()都會(huì )調用這個(gè)函數。

do_softirq()有4個(gè)執行時(shí)機,分別是:從系統調用中返回(arch/i386/kernel/entry.S::ENTRY(ret_from_sys_call))、從異常中返回(arch/i386/kernel/entry.S::ret_from_exception標號)、調度程序中(kernel/sched.c::schedule()),以及處理完硬件中斷之后(kernel/irq.c::do_IRQ())。它將遍歷所有的softirq_vec,依次啟動(dòng)其中的action()。需要注意的是,軟中斷服務(wù)程序,不允許在硬中斷服務(wù)程序中執行,也不允許在軟中斷服務(wù)程序中嵌套執行,但允許多個(gè)軟中斷服務(wù)程序同時(shí)在多個(gè)CPU上并發(fā)。

使用示例

softirq作為一種底層機制,很少由內核程序員直接使用,因此,這里的使用范例僅對其余幾種軟中斷機制。

1.bottom half

原有的bottom half用法在drivers/char/serial.c中還能看到,包括三個(gè)步驟:

init_bh(SERIAL_BH,do_serial_bh);	//在串口設備的初始化函數rs_init()中,do_serial_bh()是處理函數mark_bh(SERIAL_BH);		//在rs_sched_event()中,這個(gè)函數由中斷處理例程調用remove_bh(SERIAL_BH);	   //在串口設備的結束函數rs_fini()中調用

盡管邏輯上還是這么三步,但在do_serial_bh()函數中的動(dòng)作卻是啟動(dòng)一個(gè)task queue:run_task_queue(tq_serial),而在rs_sched_event()中,mark_bh()之前調用的則是queue_task(...,tq_serial),也就是說(shuō)串口bottom half已經(jīng)結合task queue使用了。而那些更通用一些的bottom half,比如IMMEDIATE_BH,更是必須要與task queue結合使用,而且一般情況下,task queue也很少獨立使用,而是與bottom half結合,這在下一節task queue使用示例中可以清楚地看到。

2.task queue

一般來(lái)說(shuō),程序員很少自己定義task queue,而是結合bottom half,直接使用系統預定義的tq_immediate等,尤以tq_immediate使用最頻繁??匆韵麓a段,節選自drivers/block/floppy.c:

static struct tq_struct floppy_tq;	//定義一個(gè)tq_struct結構變量floppy_tq,不需要作其他初始化動(dòng)作static void schedule_bh( void (*handler)(void*) ){floppy_tq.routine = (void *)(void *) handler;	//指定floppy_tq的調用函數為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è)軟中斷來(lái)執行tq_immediate中掛接的各個(gè)函數}

當然,我們還是可以定義并使用自己的task queue,而不用tq_immediate,在drivers/char/serial.c中提到的tq_serial就是串口驅動(dòng)自己定義的:

static DECLARE_TASK_QUEUE(tq_serial);

此時(shí)就需要自行調用run_task_queue(tq_serial)來(lái)啟動(dòng)其中的函數了,因此并不常用。

3.tasklet

這是比task queue和bottom half更加強大的一套軟中斷機制,使用上也相對簡(jiǎn)單,見(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 something7:8:		//reschedule9:		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è)反復執行的tasklet來(lái)完成一定的工作,首先在第3行定義foo_tasklet,與相應的動(dòng)作函數foo_tasklet_action相關(guān)聯(lián),并指定foo_tasklet_action()的參數為0。雖然此處以0為參數,但也同樣可以指定有意義的其他參數值,但需要注意的是,這個(gè)參數值在定義的時(shí)候必須是有固定值的變量或常數(如上例),也就是說(shuō)可以定義一個(gè)全局變量,將其地址作為參數傳給foo_tasklet_action(),例如:

int flags;DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,flags);void foo_tasklet_action(unsigned long t){int flags=*(int *)t;...}

這樣就可以通過(guò)改變flags的值將信息帶入tasklet中。直接在DECLARE_TASKLET處填寫(xiě)flags,gcc會(huì )報initializer element is not constant錯。

第9、10行是一種RESCHEDULE的技術(shù)。我們知道,一個(gè)tasklet執行結束后,它就從執行隊列里刪除了,要想重新讓它轉入運行,必須重新調用tasklet_schedule(),調用的時(shí)機可以是某個(gè)事件發(fā)生的時(shí)候,也可以是像這樣在tasklet動(dòng)作中。而這種reschedule技術(shù)將導致tasklet永遠運行,因此在子系統退出時(shí),應該有辦法停止tasklet。stop_tasklet變量和tasklet_kill()就是干這個(gè)的。



評論


技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>