- 論壇徽章:
- 0
|
最近在看Linux中斷處理的過(guò)程,寫了以下幾點(diǎn)體會(huì),忘高手給予指點(diǎn),以便我去補(bǔ)充,使得這個(gè)知識(shí)點(diǎn)對(duì)于自己更加完整。
首先看一下中斷發(fā)生時(shí)硬件和軟件的大致處理過(guò)程,然后再來(lái)對(duì)每一步做稍微詳細(xì)的介紹。
1.當(dāng)中斷發(fā)生時(shí),cpu control unit會(huì)截獲這一事件,并做一些基本的保留,同時(shí)disable local interrupt,表示不再響應(yīng)中斷。這里涉及到control unit保存的內(nèi)容和來(lái)讀取相應(yīng)idt、gdt的內(nèi)容來(lái)驗(yàn)證中斷源的合法性和interrupt handler的指令地址,接下來(lái)執(zhí)行的第一條軟件指令便是interrupt handler。
2.Interrupt handler的首要?jiǎng)幼鞅闶莝ave all,保存一切有可能會(huì)被中斷處理程序用到的寄存器值,然后執(zhí)行函數(shù)do_irq,do_irq調(diào)用irq_enter增加preemt_count的Hardirq counter數(shù)值。做一些與8k內(nèi)核堆棧相關(guān)的動(dòng)作,然后調(diào)用__do_irq。
3.__do_irq的首要任務(wù)便是acknowledge apic并且mask掉產(chǎn)生中斷的線,使得該線在中斷處理完成之前不再接收再次中斷。此時(shí)local interrupt也是disable的(由1中的control unit關(guān)閉),接下來(lái)調(diào)用isr。
4.在執(zhí)行isr之前,首先根據(jù)該isr的要求,開啟或關(guān)閉local interrupt,一般都是開啟local interrupt,然后依次調(diào)用isr。除非注冊(cè)isr(request_irq)的時(shí)候特別用flag指明必須local interrupt disable,否則一般的isr都是運(yùn)行在產(chǎn)生中斷的那根線disable掉和local interrupt enable的情況下,執(zhí)行完成之后并關(guān)閉local interrupt。
5.退到上層的__do_irq,該函數(shù)會(huì)調(diào)用pic的end函數(shù),使得對(duì)應(yīng)那根線(irq line)的中斷開啟。
6.退到上層的do_irq,此時(shí)會(huì)調(diào)用irq_exit,在irq_exit中首先減少preemt_count中的Hardirq counter,然后調(diào)用了softirq.可知softirq是運(yùn)行在全部的中斷線和local interrupt enable的情況下,意味著此時(shí)內(nèi)核已經(jīng)可以再次響應(yīng)同樣的中斷。
7.完成之后。。。。。。。。。。。。。調(diào)用iret指令。
8.iret指令使得control unit恢復(fù)先前保存的所有東西,enable local interrupt。
上述第一點(diǎn)中,中斷源的合法性比較重要,那么硬件上有哪些機(jī)制保證了該中斷源的合法性呢?要了解此我們先來(lái)看一下當(dāng)中斷發(fā)生時(shí),control unit做了哪些動(dòng)作。首先它會(huì)根據(jù)中斷源和idtr一起來(lái)找到相應(yīng)的idt中的某個(gè)entry,并從該entry當(dāng)中取出segment seletor,由此seletor再加上gdtr寄存器,就可以找到該interrupt handler的segment descriptor,由于該segment descriptor當(dāng)中有個(gè)DPL,它表示了中斷處理程序應(yīng)該在哪個(gè)級(jí)別下運(yùn)行,一般都是在0的level即kernel態(tài)下運(yùn)行interrupt handler。因此如果發(fā)現(xiàn)當(dāng)前進(jìn)程的cs中的低兩位數(shù)值比中斷處理程序的DPL還。〝(shù)值越小,level也高,kernel的數(shù)值為0),那么就直接出現(xiàn)異常,因?yàn)椴豢赡軙?huì)有某個(gè)進(jìn)程它的運(yùn)行級(jí)別會(huì)被interrupt handler還低。經(jīng)過(guò)這部確認(rèn)之后,cs和eip分別被賦值成IDT當(dāng)中的segment selector和offset,做完這一步意味著下一個(gè)執(zhí)行的指令便是interrupt handler中的指令了。
第二步當(dāng)中的interrupt handler的整條命令如下:
pushl $n-256
jmp common_interrupt
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
其實(shí)存在于idt的地址指向的指令便是上述的前兩條語(yǔ)句,接下來(lái)就是統(tǒng)一地保存所有cpu的寄存器,以免丟失。然后就調(diào)用函數(shù)do_IRQ來(lái)執(zhí)行。 do_IRQ如上述所說(shuō),它基本就是增加硬中斷的數(shù)值,然后直接調(diào)用__do_irq,__do_irq基本會(huì)通知中斷控制器已經(jīng)收到中斷信號(hào),告訴它將中斷線的信號(hào)取消,一般此時(shí)會(huì)中斷控制器會(huì)mask掉該跳線的中斷。接下來(lái)__do_irq會(huì)調(diào)用ISR也即驅(qū)動(dòng)程序利用函數(shù) request_irq函數(shù)注冊(cè)進(jìn)去的中斷處理歷程。一般來(lái)說(shuō),在執(zhí)行中斷處理歷程的時(shí)候,kernel會(huì)根據(jù)driver傳進(jìn)來(lái)的flag決定是否開啟 local的中斷,一般來(lái)說(shuō)都是開啟的,但是這里需要我們注意的是對(duì)于8259A這個(gè)中斷控制芯片來(lái)說(shuō),此時(shí)盡管cpu local的中斷是enabled,但是8259A上產(chǎn)生中斷的哪條線還是disable的,意味著這條線上的中斷不會(huì)被硬件覺(jué)察到。對(duì)于intel IO apic還沒(méi)有研究過(guò)。由此可以看出,一般driver的中斷處理歷程都是運(yùn)行l(wèi)ocal interrupt enable的情況下進(jìn)行的,因此會(huì)產(chǎn)生中斷嵌套之說(shuō)。當(dāng)__do_irq執(zhí)行完ISR之后,并enable8259A上的哪條中斷線,意味著將接收該線上產(chǎn)生的中斷。到此,__do_irq算基本完成,退到上層函數(shù)do_irq,do_irq會(huì)執(zhí)行irq_exit,在該函數(shù)中會(huì)減掉由 irq_enter增加的硬中斷數(shù)值,接下來(lái)就判斷是否是中斷context,即如果函數(shù)in_interrupt返回false,且有pending 的軟中斷,那么就去執(zhí)行軟中斷,即中斷的底半步。關(guān)于linux中斷的上半步和底半步就不詳細(xì)說(shuō)明了,這里只說(shuō)明整個(gè)流程是怎么走的。
kernel在系統(tǒng)初始化的時(shí)候注冊(cè)了6個(gè)不同的softirq,分別為HI_SOFTIRQ,TIMER_SOFTIRQ(內(nèi)核定時(shí)器的實(shí)現(xiàn)就是依賴這個(gè)softirq),NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,TASKLET_SOFTIRQ(tasklet的實(shí)現(xiàn)就是依賴于這個(gè)softirq),SCHED_SOFTIRQ,RCU_SOFTIRQ。。。。。當(dāng)中斷處理程序執(zhí)行到irq_exit函數(shù),在該函數(shù)中調(diào)用invoke_softirq即__do_softirq,正是在這個(gè)函數(shù)里,kernel對(duì)每個(gè)有pending的softirq執(zhí)行起其相應(yīng)的函數(shù)。那么哪個(gè)softirq pending是誰(shuí)來(lái)設(shè)置的呢?可以參考函數(shù)raise_softirq_irqoff,這個(gè)函數(shù)傳進(jìn)來(lái)的參數(shù)就是上述6個(gè)softirq的值。softirq會(huì)在每次中斷返回時(shí)會(huì)被檢查是否要調(diào)用,到真正執(zhí)行softirq的各個(gè)函數(shù)時(shí),in_interrupt()值一定不為真。(由于softirq不能嵌套執(zhí)行,因此它在執(zhí)行之前會(huì)local_bh_disable來(lái)提升preemt_count的softirq counter值,使得下次softirq在in_interrupt()的時(shí)候直接返回。那么在什么情況下會(huì)發(fā)生softirq的中斷嵌套呢?因?yàn)閟oftirq的時(shí)候所有中斷都是打開的,因此有可能新的interrupt handler里面又注冊(cè)了這個(gè)softirq,等到嵌套的中斷退出時(shí)(此時(shí)preemt_count中的hardirq counter 為0,請(qǐng)參考do_irq分析),又會(huì)調(diào)到softirq,此時(shí)就實(shí)現(xiàn)了softirq的中斷嵌套。
這里將tasklet(即軟中斷中的一種)做個(gè)簡(jiǎn)單的解析,使用過(guò)tasklet來(lái)幫助我們driver中的實(shí)現(xiàn)中斷底半步的兄弟都知道,一般我們都是通過(guò)如下兩個(gè)步驟來(lái)實(shí)現(xiàn):
tasklet_init
tasklet_schedule。
我們來(lái)看看上述兩個(gè)函數(shù)的代碼實(shí)現(xiàn):
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
tasklet_init很簡(jiǎn)單,它只是將一個(gè)tasklet_struct賦值成想要的函數(shù)和參數(shù),為該tasklet被運(yùn)行做了一些初始化工作。
再來(lái)看
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();
}
#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)
由紅色的代碼我們可以看到函數(shù)tasklet_schedule只是將每個(gè)cpu對(duì)應(yīng)的關(guān)于軟中斷的變量的那個(gè)關(guān)于tasklet的bit置成1,就結(jié)束了。
到此為止,我們?cè)倩叵氘?dāng)中斷處理程序完成之后,它會(huì)根據(jù)當(dāng)前是否有pending的softirq,此時(shí)有,因此函數(shù)tasklet_action就會(huì)被調(diào)用,原因是內(nèi)核初始化的時(shí)候有這么一條語(yǔ)句:open_softirq(TASKLET_SOFTIRQ, tasklet_action);。再看函數(shù)tasklet_action:
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).head;
__get_cpu_var(tasklet_vec).head = NULL;
__get_cpu_var(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;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
紅色的代碼就是我們通過(guò)函數(shù)tasklet_init注冊(cè)進(jìn)去的函數(shù)。
以此類推,我們可以找到kernel timer的實(shí)現(xiàn)原理和其他一些軟中斷的執(zhí)行過(guò)程。 |
|