Linux中断(interrupt)子系统之三:中断流控处理层【转】
轉自:http://blog.csdn.net/droidphone/article/details/7489756
1. 中斷流控層簡介
早期的內(nèi)核版本中,幾乎所有的中斷都是由__do_IRQ函數(shù)進行處理,但是,因為各種中斷請求的電氣特性會有所不同,又或者中斷控制器的特性也不同,這會導致以下這些處理也會有所不同:
何時對中斷控制器發(fā)出ack回應;
mask_irq和unmask_irq的處理;
中斷控制器是否需要eoi回應?
何時打開cpu的本地irq中斷?以便允許irq的嵌套;
中斷數(shù)據(jù)結構的同步和保護;
/*****************************************************************************************************/
聲明:本博內(nèi)容均由http://blog.csdn.net/droidphone原創(chuàng),轉載請注明出處,謝謝!
/*****************************************************************************************************/
為此,通用中斷子系統(tǒng)把幾種常用的流控類型進行了抽象,并為它們實現(xiàn)了相應的標準函數(shù),我們只要選擇相應的函數(shù),賦值給irq所對應的irq_desc結構的handle_irq字段中即可。這些標準的回調函數(shù)都是irq_flow_handler_t類型:
[cpp]view plaincopy
typedefvoid(*irq_flow_handler_t)(unsignedintirq,
structirq_desc*desc);
目前的通用中斷子系統(tǒng)實現(xiàn)了以下這些標準流控回調函數(shù),這些函數(shù)都定義在:kernel/irq/chip.c中,
handle_simple_irq 用于簡易流控處理;
handle_level_irq 用于電平觸發(fā)中斷的流控處理;
handle_edge_irq 用于邊沿觸發(fā)中斷的流控處理;
handle_fasteoi_irq 用于需要響應eoi的中斷控制器;
handle_percpu_irq 用于只在單一cpu響應的中斷;
handle_nested_irq 用于處理使用線程的嵌套中斷;
驅動程序和板級代碼可以通過以下幾個API設置irq的流控函數(shù):
irq_set_handler();
irq_set_chip_and_handler();
irq_set_chip_and_handler_name();
以下這個序列圖展示了整個通用中斷子系統(tǒng)的中斷響應過程,flow_handle一欄就是中斷流控層的生命周期:
圖1.1 通用中斷子系統(tǒng)的中斷響應過程
2. handle_simple_irq
該函數(shù)沒有實現(xiàn)任何實質性的流控操作,在把irq_desc結構鎖住后,直接調用handle_irq_event處理irq_desc中的action鏈表,它通常用于多路復用(類似于中斷控制器級聯(lián))中的子中斷,由父中斷的流控回調中調用。或者用于無需進行硬件控制的中斷中。以下是它的經(jīng)過簡化的代碼:
[cpp]view plaincopy
void
handle_simple_irq(unsignedintirq,structirq_desc*desc)
{
raw_spin_lock(&desc->lock);
......
handle_irq_event(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
3. handle_level_irq
該函數(shù)用于處理電平中斷的流控操作。電平中斷的特點是,只要設備的中斷請求引腳(中斷線)保持在預設的觸發(fā)電平,中斷就會一直被請求,所以,為了避免同一中斷被重復響應,必須在處理中斷前先把mask irq,然后ack irq,以便復位設備的中斷請求引腳,響應完成后再unmask irq。實際的情況稍稍復雜一點,在mask和ack之后,還要判斷IRQ_INPROGRESS標志位,如果該標志已經(jīng)置位,則直接退出,不再做實質性的處理,IRQ_INPROGRESS標志在handle_irq_event的開始設置,在handle_irq_event結束時清除,如果監(jiān)測到IRQ_INPROGRESS被置位,表明該irq正在被另一個CPU處理中,所以直接退出,對電平中斷來說是正確的處理方法。但是我覺得在ARM系統(tǒng)中,這種情況根本就不會發(fā)生,因為在沒有進入handle_level_irq之前,中斷控制器沒有收到ack通知,它不會向第二個CPU再次發(fā)出中斷請求,而當程序進入handle_level_irq之后,第一個動作就是mask irq,然后ack irq(通常是聯(lián)合起來的:mask_ack_irq),這時候就算設備再次發(fā)出中斷請求,也是在handle_irq_event結束,unmask irq之后,這時IRQ_INPROGRESS標志已經(jīng)被清除。我不知道其他像X86之類的體系是否有不同的行為,有知道的朋友請告知我一下。以下是handle_level_irq經(jīng)過簡化之后的代碼:
[cpp]view plaincopy
void
handle_level_irq(unsignedintirq,structirq_desc*desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);
if(unlikely(irqd_irq_inprogress(&desc->irq_data)))
gotoout_unlock;
......
if(unlikely(!desc->action||irqd_irq_disabled(&desc->irq_data)))
gotoout_unlock;
handle_irq_event(desc);
if(!irqd_irq_disabled(&desc->irq_data)&&!(desc->istate&IRQS_ONESHOT))
unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
雖然handle_level_irq對電平中斷的流控進行了必要的處理,因為電平中斷的特性:只要沒有ack irq,中斷線會一直有效,所以我們不會錯過某次中斷請求,但是驅動程序的開發(fā)人員如果對該過程理解不透徹,特別容易發(fā)生某次中斷被多次處理的情況。特別是使用了中斷線程(action->thread_fn)來響應中斷的時候:通常mask_ack_irq只會清除中斷控制器的pending狀態(tài),很多慢速設備(例如通過i2c或spi控制的設備)需要在中斷線程中清除中斷線的pending狀態(tài),但是未等到中斷線程被調度執(zhí)行的時候,handle_level_irq早就返回了,這時已經(jīng)執(zhí)行過unmask_irq,設備的中斷線pending處于有效狀態(tài),中斷控制器會再次發(fā)出中斷請求,結果是設備的一次中斷請求,產(chǎn)生了兩次中斷響應。要避免這種情況,最好的辦法就是不要單獨使用中斷線程處理中斷,而是要實現(xiàn)request_threaded_irq()的第二個參數(shù)irq_handler_t:handler,在handle回調中使用disable_irq()關閉該irq,然后在退出中斷線程回調前再enable_irq()。假設action->handler沒有屏蔽irq,以下這幅圖展示了電平中斷期間IRQ_PROGRESS標志、本地中斷狀態(tài)和觸發(fā)其他CPU的狀態(tài):
圖3.1 電平觸發(fā)中斷狀態(tài)
上圖中顏色分別代表不同的狀態(tài):
| 狀態(tài) | 紅色 | 綠色 |
|---|---|---|
| IRQ_PROGRESS | TRUE | FALSE |
| 是否允許本地cpu中斷 | 禁止 | 允許 |
| 是否允許該設備再次觸發(fā)中斷(可能由其它cpu響應) | 禁止 | 允許 |
4. handle_edge_irq
該函數(shù)用于處理邊沿觸發(fā)中斷的流控操作。邊沿觸發(fā)中斷的特點是,只有設備的中斷請求引腳(中斷線)的電平發(fā)生跳變時(由高變低或者有低變高),才會發(fā)出中斷請求,因為跳變是一瞬間,而且不會像電平中斷能保持住電平,所以處理不當就特別容易漏掉一次中斷請求,為了避免這種情況,屏蔽中斷的時間必須越短越好。內(nèi)核的開發(fā)者們顯然意識到這一點,在正是處理中斷前,判斷IRQ_PROGRESS標志沒有被設置的情況下,只是ack irq,并沒有mask irq,以便復位設備的中斷請求引腳,在這之后的中斷處理期間,另外的cpu可以再次響應同一個irq請求,如果IRQ_PROGRESS已經(jīng)置位,表明另一個CPU正在處理該irq的上一次請求,這種情況下,他只是簡單地設置IRQS_PENDING標志,然后mask_ack_irq后退出,中斷請求交由原來的CPU繼續(xù)處理。因為是mask_ack_irq,所以系統(tǒng)實際上只允許掛起一次中斷。
[cpp]view plaincopy
if(unlikely(irqd_irq_disabled(&desc->irq_data)||
irqd_irq_inprogress(&desc->irq_data)||!desc->action)){
if(!irq_check_poll(desc)){
desc->istate|=IRQS_PENDING;
mask_ack_irq(desc);
gotoout_unlock;
}
}
desc->irq_data.chip->irq_ack(&desc->irq_data);
從上面的分析可以知道,處理中斷期間,另一次請求可能由另一個cpu響應后掛起,所以在處理完本次請求后還要判斷IRQS_PENDING標志,如果被置位,當前cpu要接著處理被另一個cpu“委托”的請求。內(nèi)核在這里設置了一個循環(huán)來處理這種情況,直到IRQS_PENDING標志無效為止,而且因為另一個cpu在響應并掛起irq時,會mask irq,所以在循環(huán)中要再次unmask irq,以便另一個cpu可以再次響應并掛起irq:
[cpp]view plaincopy
do{
......
if(unlikely(desc->istate&IRQS_PENDING)){
if(!irqd_irq_disabled(&desc->irq_data)&&
irqd_irq_masked(&desc->irq_data))
unmask_irq(desc);
}
handle_irq_event(desc);
}while((desc->istate&IRQS_PENDING)&&
!irqd_irq_disabled(&desc->irq_data));
IRQS_PENDING標志會在handle_irq_event中清除。
圖4.1 邊沿觸發(fā)中斷狀態(tài)
上圖中顏色分別代表不同的狀態(tài):
| 狀態(tài) | 紅色 | 綠色 |
| IRQ_PROGRESS | TRUE | FALSE |
| 是否允許本地cpu中斷 | 禁止 | 允許 |
| 是否允許該設備再次觸發(fā)中斷(可能由其它cpu響應) | 禁止 | 允許 |
| 是否處于中斷上下文 | 處于中斷上下文 | 處于進程上下文 |
由圖4.1也可以看出,在處理軟件中斷(softirq)期間,此時仍然處于中斷上下文中,但是cpu的本地中斷是處于打開狀態(tài)的,這表明此時嵌套中斷允許發(fā)生,不過這不要緊,因為重要的處理已經(jīng)完成,被嵌套的也只是軟件中斷部分而已。這個也就是內(nèi)核區(qū)分top和bottom兩個部分的初衷吧。
5. handle_fasteoi_irq
現(xiàn)代的中斷控制器通常會在硬件上實現(xiàn)了中斷流控功能,例如ARM體系中的GIC通用中斷控制器。對于這種中斷控制器,CPU只需要在每次處理完中斷后發(fā)出一個end of interrupt(eoi),我們無需關注何時mask,何時unmask。不過雖然想著很完美,事情總有特殊的時候,所以內(nèi)核還是給了我們插手的機會,它利用irq_desc結構中的preflow_handler字段,在正式處理中斷前會通過preflow_handler函數(shù)調用該回調。
[cpp]view plaincopy
void
handle_fasteoi_irq(unsignedintirq,structirq_desc*desc)
{
raw_spin_lock(&desc->lock);
if(unlikely(irqd_irq_inprogress(&desc->irq_data)))
if(!irq_check_poll(desc))
gotoout;
......
if(unlikely(!desc->action||irqd_irq_disabled(&desc->irq_data))){
desc->istate|=IRQS_PENDING;
mask_irq(desc);
gotoout;
}
if(desc->istate&IRQS_ONESHOT)
mask_irq(desc);
preflow_handler(desc);
handle_irq_event(desc);
out_eoi:
desc->irq_data.chip->irq_eoi(&desc->irq_data);
out_unlock:
raw_spin_unlock(&desc->lock);
return;
......
}
此外,內(nèi)核還提供了另外一個eoi版的函數(shù):handle_edge_eoi_irq,它的處理類似于handle_edge_irq,只是無需實現(xiàn)mask和unmask的邏輯。
6. handle_percpu_irq
該函數(shù)用于smp系統(tǒng),當某個irq只在一個cpu上處理時,我們可以無需用自旋鎖對數(shù)據(jù)進行保護,也無需處理cpu之間的中斷嵌套重入,所以函數(shù)很簡單:
[cpp]view plaincopy
void
handle_percpu_irq(unsignedintirq,structirq_desc*desc)
{
structirq_chip*chip=irq_desc_get_chip(desc);
kstat_incr_irqs_this_cpu(irq,desc);
if(chip->irq_ack)
chip->irq_ack(&desc->irq_data);
handle_irq_event_percpu(desc,desc->action);
if(chip->irq_eoi)
chip->irq_eoi(&desc->irq_data);
}
7. handle_nested_irq
該函數(shù)用于實現(xiàn)其中一種中斷共享機制,當多個中斷共享某一根中斷線時,我們可以把這個中斷線作為父中斷,共享該中斷的各個設備作為子中斷,在父中斷的中斷線程中決定和分發(fā)響應哪個設備的請求,在得出真正發(fā)出請求的子設備后,調用handle_nested_irq來響應中斷。所以,該函數(shù)是在進程上下文執(zhí)行的,我們也無需掃描和執(zhí)行irq_desc結構中的action鏈表。父中斷在初始化時必須通過irq_set_nested_thread函數(shù)明確告知中斷子系統(tǒng):這些子中斷屬于線程嵌套中斷類型,這樣驅動程序在申請這些子中斷時,內(nèi)核不會為它們建立自己的中斷線程,所有的子中斷共享父中斷的中斷線程。
[cpp]view plaincopy
voidhandle_nested_irq(unsignedintirq)
{
......
might_sleep();
raw_spin_lock_irq(&desc->lock);
......
action=desc->action;
if(unlikely(!action||irqd_irq_disabled(&desc->irq_data)))
gotoout_unlock;
irqd_set(&desc->irq_data,IRQD_IRQ_INPROGRESS);
raw_spin_unlock_irq(&desc->lock);
action_ret=action->thread_fn(action->irq,action->dev_id);
raw_spin_lock_irq(&desc->lock);
irqd_clear(&desc->irq_data,IRQD_IRQ_INPROGRESS);
out_unlock:
raw_spin_unlock_irq(&desc->lock);
}
總結
以上是生活随笔為你收集整理的Linux中断(interrupt)子系统之三:中断流控处理层【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php解决跨域问题6,关于php:tp6
- 下一篇: 虚拟机上php5.6安装教程,手把手安装