一文完全讀懂 | Linux中斷處理
什么是中斷
中斷?是為了解決外部設備完成某些工作后通知CPU的一種機制(譬如硬盤完成讀寫操作后通過中斷告知CPU已經(jīng)完成)。早期沒有中斷機制的計算機就不得不通過輪詢來查詢外部設備的狀態(tài),由于輪詢是試探查詢的(也就是說設備不一定是就緒狀態(tài)),所以往往要做很多無用的查詢,從而導致效率非常低下。由于中斷是由外部設備主動通知CPU的,所以不需要CPU進行輪詢?nèi)ゲ樵?,效率大大提升?/p>
從物理學的角度看,中斷是一種電信號,由硬件設備產(chǎn)生,并直接送入中斷控制器(如 8259A)的輸入引腳上,然后再由中斷控制器向處理器發(fā)送相應的信號。處理器一經(jīng)檢測到該信號,便中斷自己當前正在處理的工作,轉(zhuǎn)而去處理中斷。此后,處理器會通知 OS 已經(jīng)產(chǎn)生中斷。這樣,OS 就可以對這個中斷進行適當?shù)奶幚?。不同的設備對應的中斷不同,而每個中斷都通過一個唯一的數(shù)字標識,這些值通常被稱為中斷請求線。
中斷控制器
X86計算機的 CPU 為中斷只提供了兩條外接引腳:NMI 和 INTR。其中 NMI 是不可屏蔽中斷,它通常用于電源掉電和物理存儲器奇偶校驗;INTR是可屏蔽中斷,可以通過設置中斷屏蔽位來進行中斷屏蔽,它主要用于接受外部硬件的中斷信號,這些信號由中斷控制器傳遞給 CPU。
常見的中斷控制器有兩種:
可編程中斷控制器8259A
傳統(tǒng)的 PIC(Programmable Interrupt Controller,可編程中斷控制器)是由兩片 8259A 風格的外部芯片以“級聯(lián)”的方式連接在一起。每個芯片可處理多達 8 個不同的 IRQ。因為從 PIC 的 INT 輸出線連接到主 PIC 的 IRQ2 引腳,所以可用 IRQ 線的個數(shù)達到 15 個,如圖下所示。

高級可編程中斷控制器(APIC)
8259A 只適合單 CPU 的情況,為了充分挖掘 SMP 體系結(jié)構(gòu)的并行性,能夠把中斷傳遞給系統(tǒng)中的每個 CPU 至關(guān)重要?;诖死碛?,Intel 引入了一種名為 I/O 高級可編程控制器的新組件,來替代老式的 8259A 可編程中斷控制器。該組件包含兩大組成部分:一是“本地 APIC”,主要負責傳遞中斷信號到指定的處理器;舉例來說,一臺具有三個處理器的機器,則它必須相對的要有三個本地 APIC。另外一個重要的部分是 I/O APIC,主要是收集來自 I/O 裝置的 Interrupt 信號且在當那些裝置需要中斷時發(fā)送信號到本地 APIC,系統(tǒng)中最多可擁有 8 個 I/O APIC。
每個本地 APIC 都有 32 位的寄存器,一個內(nèi)部時鐘,一個本地定時設備以及為本地中斷保留的兩條額外的 IRQ 線 LINT0 和 LINT1。所有本地 APIC 都連接到 I/O APIC,形成一個多級 APIC 系統(tǒng),如圖下所示。

目前大部分單處理器系統(tǒng)都包含一個 I/O APIC 芯片,可以通過以下兩種方式來對這種芯片進行配置:
- 作為一種標準的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 連接到 CPU,兩條 LINT0 和 LINT1 分別連接到 INTR 和 NMI 引腳。
- 作為一種標準外部 I/O APIC。本地 APIC 被激活,且所有的外部中斷都通過 I/O APIC 接收。
辨別一個系統(tǒng)是否正在使用 I/O APIC,可以在命令行輸入如下命令:
#?cat?/proc/interrupts
???????????CPU0???????
??0:??????90504????IO-APIC-edge??timer
??1:????????131????IO-APIC-edge??i8042
??8:??????????4????IO-APIC-edge??rtc
??9:??????????0????IO-APIC-level??acpi
?12:????????111????IO-APIC-edge??i8042
?14:???????1862????IO-APIC-edge??ide0
?15:?????????28????IO-APIC-edge??ide1
177:??????????9????IO-APIC-level??eth0
185:??????????0????IO-APIC-level??via82cxxx
...
如果輸出結(jié)果中列出了 IO-APIC,說明您的系統(tǒng)正在使用 APIC。如果看到 XT-PIC,意味著您的系統(tǒng)正在使用 8259A 芯片。
中斷分類
中斷可分為同步(synchronous)中斷和異步(asynchronous)中斷:
- 同步中斷是當指令執(zhí)行時由 CPU 控制單元產(chǎn)生,之所以稱為同步,是因為只有在一條指令執(zhí)行完畢后 CPU 才會發(fā)出中斷,而不是發(fā)生在代碼指令執(zhí)行期間,比如系統(tǒng)調(diào)用。
- 異步中斷是指由其他硬件設備依照 CPU 時鐘信號隨機產(chǎn)生,即意味著中斷能夠在指令之間發(fā)生,例如鍵盤中斷。
根據(jù) Intel 官方資料,同步中斷稱為異常(exception),異步中斷被稱為中斷(interrupt)。
中斷可分為?可屏蔽中斷(Maskable interrupt)和?非屏蔽中斷(Nomaskable interrupt)。異常可分為?故障(fault)、陷阱(trap)、終止(abort)三類。
從廣義上講,中斷可分為四類:中斷、故障、陷阱、終止。這些類別之間的異同點請參看 表。
表:中斷類別及其行為
| 類別 | 原因 | 異步/同步 | 返回行為 |
|---|---|---|---|
| 中斷 | 來自I/O設備的信號 | 異步 | 總是返回到下一條指令 |
| 陷阱 | 有意的異常 | 同步 | 總是返回到下一條指令 |
| 故障 | 潛在可恢復的錯誤 | 同步 | 返回到當前指令 |
| 終止 | 不可恢復的錯誤 | 同步 | 不會返回 |
X86 體系結(jié)構(gòu)的每個中斷都被賦予一個唯一的編號或者向量(8 位無符號整數(shù))。非屏蔽中斷和異常向量是固定的,而可屏蔽中斷向量可以通過對中斷控制器的編程來改變。
中斷處理 - 上半部(硬中斷)
由于?APIC中斷控制器?有點小復雜,所以本文主要通過?8259A中斷控制器?來介紹Linux對中斷的處理過程。
中斷處理相關(guān)結(jié)構(gòu)
前面說過,8259A中斷控制器?由兩片 8259A 風格的外部芯片以?級聯(lián)?的方式連接在一起,每個芯片可處理多達 8 個不同的 IRQ(中斷請求),所以可用 IRQ 線的個數(shù)達到 15 個。如下圖:

在內(nèi)核中每條IRQ線由結(jié)構(gòu)體?irq_desc_t?來描述,irq_desc_t?定義如下:
typedef?struct?{
????unsigned?int?status;????????/*?IRQ?status?*/
????hw_irq_controller?*handler;
????struct?irqaction?*action;???/*?IRQ?action?list?*/
????unsigned?int?depth;?????????/*?nested?irq?disables?*/
????spinlock_t?lock;
}?irq_desc_t;
下面介紹一下?irq_desc_t?結(jié)構(gòu)各個字段的作用:
status: IRQ線的狀態(tài)。handler: 類型為?hw_interrupt_type?結(jié)構(gòu),表示IRQ線對應的硬件相關(guān)處理函數(shù),比如?8259A中斷控制器?接收到一個中斷信號時,需要發(fā)送一個確認信號才會繼續(xù)接收中斷信號的,發(fā)送確認信號的函數(shù)就是?hw_interrupt_type?中的?ack?函數(shù)。action: 類型為?irqaction?結(jié)構(gòu),中斷信號的處理入口。由于一條IRQ線可以被多個硬件共享,所以?action?是一個鏈表,每個?action?代表一個硬件的中斷處理入口。depth: 防止多次開啟和關(guān)閉IRQ線。lock: 防止多核CPU同時對IRQ進行操作的自旋鎖。
hw_interrupt_type?這個結(jié)構(gòu)與硬件相關(guān),這里就不作介紹了,我們來看看?irqaction?這個結(jié)構(gòu):
struct?irqaction?{
????void?(*handler)(int,?void?*,?struct?pt_regs?*);
????unsigned?long?flags;
????unsigned?long?mask;
????const?char?*name;
????void?*dev_id;
????struct?irqaction?*next;
};
下面說說?irqaction?結(jié)構(gòu)各個字段的作用:
handler: 中斷處理的入口函數(shù),handler?的第一個參數(shù)是中斷號,第二個參數(shù)是設備對應的ID,第三個參數(shù)是中斷發(fā)生時由內(nèi)核保存的各個寄存器的值。flags: 標志位,用于表示?irqaction?的一些行為,例如是否能夠與其他硬件共享IRQ線。name: 用于保存中斷處理的名字。dev_id: 設備ID。next: 每個硬件的中斷處理入口對應一個?irqaction?結(jié)構(gòu),由于多個硬件可以共享同一條IRQ線,所以這里通過?next?字段來連接不同的硬件中斷處理入口。
irq_desc_t?結(jié)構(gòu)關(guān)系如下圖:

注冊中斷處理入口
在內(nèi)核中,可以通過?setup_irq()?函數(shù)來注冊一個中斷處理入口。setup_irq()?函數(shù)代碼如下:
int?setup_irq(unsigned?int?irq,?struct?irqaction?*?new)
{
????int?shared?=?0;
????unsigned?long?flags;
????struct?irqaction?*old,?**p;
????irq_desc_t?*desc?=?irq_desc?+?irq;
????...
????spin_lock_irqsave(&desc->lock,flags);
????p?=?&desc->action;
????if?((old?=?*p)?!=?NULL)?{
????????if?(!(old->flags?&?new->flags?&?SA_SHIRQ))?{
????????????spin_unlock_irqrestore(&desc->lock,flags);
????????????return?-EBUSY;
????????}
????????do?{
????????????p?=?&old->next;
????????????old?=?*p;
????????}?while?(old);
????????shared?=?1;
????}
????*p?=?new;
????if?(!shared)?{
????????desc->depth?=?0;
????????desc->status?&=?~(IRQ_DISABLED?|?IRQ_AUTODETECT?|?IRQ_WAITING);
????????desc->handler->startup(irq);
????}
????spin_unlock_irqrestore(&desc->lock,flags);
????register_irq_proc(irq);?//?注冊proc文件系統(tǒng)
????return?0;
}
setup_irq()?函數(shù)比較簡單,就是通過?irq?號來查找對應的?irq_desc_t?結(jié)構(gòu),并把新的?irqaction?連接到?irq_desc_t?結(jié)構(gòu)的?action?鏈表中。要注意的是,如果設備不支持共享IRQ線(也即是?flags?字段沒有設置?SA_SHIRQ?標志),那么就返回?EBUSY?錯誤。
我們看看?時鐘中斷處理入口?的注冊實例:
static?struct?irqaction?irq0??=?{?timer_interrupt,?SA_INTERRUPT,?0,?"timer",?NULL,?NULL};
void?__init?time_init(void)
{
????...
????setup_irq(0,?&irq0);
}
可以看到,時鐘中斷處理入口的IRQ號為0,處理函數(shù)為?timer_interrupt(),并且不支持共享IRQ線(flags?字段沒有設置?SA_SHIRQ?標志)。
處理中斷請求
當一個中斷發(fā)生時,中斷控制層會發(fā)送信號給CPU,CPU收到信號會中斷當前的執(zhí)行,轉(zhuǎn)而執(zhí)行中斷處理過程。中斷處理過程首先會保存寄存器的值到棧中,然后調(diào)用?do_IRQ()?函數(shù)進行進一步的處理,do_IRQ()?函數(shù)代碼如下:
asmlinkage?unsigned?int?do_IRQ(struct?pt_regs?regs)
{
????int?irq?=?regs.orig_eax?&?0xff;?/*?獲取IRQ號??*/
????int?cpu?=?smp_processor_id();
????irq_desc_t?*desc?=?irq_desc?+?irq;
????struct?irqaction?*?action;
????unsigned?int?status;
????kstat.irqs[cpu][irq]++;
????spin_lock(&desc->lock);
????desc->handler->ack(irq);
????status?=?desc->status?&?~(IRQ_REPLAY?|?IRQ_WAITING);
????status?|=?IRQ_PENDING;?/*?we?_want_?to?handle?it?*/
????action?=?NULL;
????if?(!(status?&?(IRQ_DISABLED?|?IRQ_INPROGRESS)))?{?//?當前IRQ不在處理中
????????action?=?desc->action;????//?獲取?action?鏈表
????????status?&=?~IRQ_PENDING;???//?去除IRQ_PENDING標志,?這個標志用于記錄是否在處理IRQ請求的時候又發(fā)生了中斷
????????status?|=?IRQ_INPROGRESS;?//?設置IRQ_INPROGRESS標志,?表示正在處理IRQ
????}
????desc->status?=?status;
????if?(!action)??//?如果上一次IRQ還沒完成,?直接退出
????????goto?out;
????for?(;;)?{
????????spin_unlock(&desc->lock);
????????handle_IRQ_event(irq,?®s,?action);?//?處理IRQ請求
????????spin_lock(&desc->lock);
????????
????????if?(!(desc->status?&?IRQ_PENDING))?//?如果在處理IRQ請求的時候又發(fā)生了中斷,?繼續(xù)處理IRQ請求
????????????break;
????????desc->status?&=?~IRQ_PENDING;
????}
????desc->status?&=?~IRQ_INPROGRESS;
out:
????desc->handler->end(irq);
????spin_unlock(&desc->lock);
????if?(softirq_active(cpu)?&?softirq_mask(cpu))
????????do_softirq();?//?中斷下半部處理
????return?1;
}
do_IRQ()?函數(shù)首先通過IRQ號獲取到其對應的?irq_desc_t?結(jié)構(gòu),注意的是同一個中斷有可能發(fā)生多次,所以要判斷當前IRQ是否正在被處理當中(判斷?irq_desc_t?結(jié)構(gòu)的?status?字段是否設置了?IRQ_INPROGRESS?標志),如果不是處理當前,那么就獲取到?action?鏈表,然后通過調(diào)用?handle_IRQ_event()?函數(shù)來執(zhí)行 action 鏈表中的中斷處理函數(shù)。
如果在處理中斷的過程中又發(fā)生了相同的中斷(irq_desc_t?結(jié)構(gòu)的?status?字段被設置了?IRQ_INPROGRESS?標志),那么就繼續(xù)對中斷進行處理。處理完中斷后,調(diào)用?do_softirq()?函數(shù)來對中斷下半部進行處理(下面會說)。
接下來看看?handle_IRQ_event()?函數(shù)的實現(xiàn):
int?handle_IRQ_event(unsigned?int?irq,?struct?pt_regs?*?regs,?struct?irqaction?*?action)
{
????int?status;
????int?cpu?=?smp_processor_id();
????irq_enter(cpu,?irq);
????status?=?1;?/*?Force?the?"do?bottom?halves"?bit?*/
????if?(!(action->flags?&?SA_INTERRUPT))?//?如果中斷處理能夠在打開中斷的情況下執(zhí)行,?那么就打開中斷
????????__sti();
????do?{
????????status?|=?action->flags;
????????action->handler(irq,?action->dev_id,?regs);
????????action?=?action->next;
????}?while?(action);
????if?(status?&?SA_SAMPLE_RANDOM)
????????add_interrupt_randomness(irq);
????__cli();
????irq_exit(cpu,?irq);
????return?status;
}
handle_IRQ_event()?函數(shù)非常簡單,就是遍歷 action 鏈表并且執(zhí)行其中的處理函數(shù),比如對于?時鐘中斷?就是調(diào)用?timer_interrupt()?函數(shù)。這里要注意的是,如果中斷處理過程能夠開啟中斷的,那么就把中斷打開(因為CPU接收到中斷信號時會關(guān)閉中斷)。
中斷處理 - 下半部(軟中斷)
由于中斷處理一般在關(guān)閉中斷的情況下執(zhí)行,所以中斷處理不能太耗時,否則后續(xù)發(fā)生的中斷就不能實時地被處理。鑒于這個原因,Linux把中斷處理分為兩個部分,上半部?和?下半部,上半部?在前面已經(jīng)介紹過,接下來就介紹一下?下半部?的執(zhí)行。
一般中斷?上半部?只會做一些最基礎的操作(比如從網(wǎng)卡中復制數(shù)據(jù)到緩存中),然后對要執(zhí)行的中斷?下半部?進行標識,標識完調(diào)用?do_softirq()?函數(shù)進行處理。
softirq機制
中斷下半部?由?softirq(軟中斷)?機制來實現(xiàn)的,在Linux內(nèi)核中,有一個名為?softirq_vec?的數(shù)組,如下:
static?struct?softirq_action?softirq_vec[32];
其類型為?softirq_action?結(jié)構(gòu),定義如下:
struct?softirq_action
{
????void????(*action)(struct?softirq_action?*);
????void????*data;
};
softirq_vec?數(shù)組是?softirq?機制的核心,softirq_vec?數(shù)組每個元素代表一種軟中斷。但在Linux中只定義了四種軟中斷,如下:
enum
{
????HI_SOFTIRQ=0,
????NET_TX_SOFTIRQ,
????NET_RX_SOFTIRQ,
????TASKLET_SOFTIRQ
};
HI_SOFTIRQ?是高優(yōu)先級tasklet,而?TASKLET_SOFTIRQ?是普通tasklet,tasklet是基于softirq機制的一種任務隊列(下面會介紹)。NET_TX_SOFTIRQ?和?NET_RX_SOFTIRQ?特定用于網(wǎng)絡子模塊的軟中斷(不作介紹)。
注冊softirq處理函數(shù)
要注冊一個softirq處理函數(shù),可以通過?open_softirq()?函數(shù)來進行,代碼如下:
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 ????????softirq_mask(i)?|=?(1< ????spin_unlock_irqrestore(&softirq_mask_lock,?flags);
}
open_softirq()?函數(shù)的主要工作就是向?softirq_vec?數(shù)組添加一個softirq處理函數(shù)。
Linux在系統(tǒng)初始化時注冊了兩種softirq處理函數(shù),分別為?TASKLET_SOFTIRQ?和?HI_SOFTIRQ:
void?__init?softirq_init()
{
????...
????open_softirq(TASKLET_SOFTIRQ,?tasklet_action,?NULL);
????open_softirq(HI_SOFTIRQ,?tasklet_hi_action,?NULL);
}
處理softirq
處理softirq是通過?do_softirq()?函數(shù)實現(xiàn),代碼如下:
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:
????????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();
????return;
retry:
????goto?restart;
}
前面說了?softirq_vec?數(shù)組有32個元素,每個元素對應一種類型的softirq,那么Linux怎么知道哪種softirq需要被執(zhí)行呢?在Linux中,每個CPU都有一個類型為?irq_cpustat_t?結(jié)構(gòu)的變量,irq_cpustat_t?結(jié)構(gòu)定義如下:
typedef?struct?{
????unsigned?int?__softirq_active;
????unsigned?int?__softirq_mask;
????...
}?irq_cpustat_t;
其中?__softirq_active?字段表示有哪種softirq觸發(fā)了(int類型有32個位,每一個位代表一種softirq),而?__softirq_mask?字段表示哪種softirq被屏蔽了。Linux通過?__softirq_active?這個字段得知哪種softirq需要執(zhí)行(只需要把對應位設置為1)。
所以,do_softirq()?函數(shù)首先通過?softirq_mask(cpu)?來獲取當前CPU對應被屏蔽的softirq,而?softirq_active(cpu) & mask?就是獲取需要執(zhí)行的softirq,然后就通過對比?__softirq_active?字段的各個位來判斷是否要執(zhí)行該類型的softirq。
tasklet機制
前面說了,tasklet機制是基于softirq機制的,tasklet機制其實就是一個任務隊列,然后通過softirq執(zhí)行。在Linux內(nèi)核中有兩種tasklet,一種是高優(yōu)先級tasklet,一種是普通tasklet。這兩種tasklet的實現(xiàn)基本一致,唯一不同的就是執(zhí)行的優(yōu)先級,高優(yōu)先級tasklet會先于普通tasklet執(zhí)行。
tasklet本質(zhì)是一個隊列,通過結(jié)構(gòu)體?tasklet_head?存儲,并且每個CPU有一個這樣的隊列,我們來看看結(jié)構(gòu)體?tasklet_head?的定義:
struct?tasklet_head
{
????struct?tasklet_struct?*list;
};
struct?tasklet_struct
{
????struct?tasklet_struct?*next;
????unsigned?long?state;
????atomic_t?count;
????void?(*func)(unsigned?long);
????unsigned?long?data;
};
從?tasklet_head?的定義可以知道,tasklet_head?結(jié)構(gòu)是?tasklet_struct?結(jié)構(gòu)隊列的頭部,而?tasklet_struct?結(jié)構(gòu)的?func?字段正式任務要執(zhí)行的函數(shù)指針。Linux定義了兩種的tasklet隊列,分別為?tasklet_vec?和?tasklet_hi_vec,定義如下:
struct?tasklet_head?tasklet_vec[NR_CPUS];
struct?tasklet_head?tasklet_hi_vec[NR_CPUS];
可以看出,tasklet_vec?和?tasklet_hi_vec?都是數(shù)組,數(shù)組的元素個數(shù)為CPU的核心數(shù),也就是每個CPU核心都有一個高優(yōu)先級tasklet隊列和一個普通tasklet隊列。
調(diào)度tasklet
如果我們有一個tasklet需要執(zhí)行,那么高優(yōu)先級tasklet可以通過?tasklet_hi_schedule()?函數(shù)調(diào)度,而普通tasklet可以通過?tasklet_schedule()?調(diào)度。這兩個函數(shù)基本一樣,所以我們只分析其中一個:
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);
????}
}
函數(shù)參數(shù)的類型是?tasklet_struct?結(jié)構(gòu)的指針,表示需要執(zhí)行的tasklet結(jié)構(gòu)。tasklet_hi_schedule()?函數(shù)首先判斷這個tasklet是否已經(jīng)被添加到隊列中,如果不是就添加到?tasklet_hi_vec?隊列中,并且通過調(diào)用?__cpu_raise_softirq(cpu, HI_SOFTIRQ)?來告訴softirq需要執(zhí)行?HI_SOFTIRQ?類型的softirq,我們來看看?__cpu_raise_softirq()?函數(shù)的實現(xiàn):
static?inline?void?__cpu_raise_softirq(int?cpu,?int?nr)
{
????softirq_active(cpu)?|=?(1< }
可以看出,__cpu_raise_softirq()?函數(shù)就是把?irq_cpustat_t?結(jié)構(gòu)的?__softirq_active?字段的?nr位?設置為1。對于?tasklet_hi_schedule()?函數(shù)就是把?HI_SOFTIRQ?位(0位)設置為1。
前面我們也介紹過,Linux在初始化時會注冊兩種softirq,TASKLET_SOFTIRQ?和?HI_SOFTIRQ:
void?__init?softirq_init()
{
????...
????open_softirq(TASKLET_SOFTIRQ,?tasklet_action,?NULL);
????open_softirq(HI_SOFTIRQ,?tasklet_hi_action,?NULL);
}
所以當把?irq_cpustat_t?結(jié)構(gòu)的?__softirq_active?字段的?HI_SOFTIRQ?位(0位)設置為1時,softirq機制就會執(zhí)行?tasklet_hi_action()?函數(shù),我們來看看?tasklet_hi_action()?函數(shù)的實現(xiàn):
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);??//?調(diào)用tasklet處理函數(shù)
????????????????tasklet_unlock(t);
????????????????continue;
????????????}
????????????tasklet_unlock(t);
????????}
????????...
????}
}
tasklet_hi_action()?函數(shù)非常簡單,就是遍歷?tasklet_hi_vec?隊列并且執(zhí)行其中tasklet的處理函數(shù)。
