Linux 中斷子系統(tǒng):中斷處理知識(shí)點(diǎn)大全
Linux 中斷相關(guān)節(jié)點(diǎn)
/proc/interrupts
cat 這個(gè)節(jié)點(diǎn),會(huì)打印系統(tǒng)中所有的中斷信息,如果是多核CPU,每個(gè)核都會(huì)打印出來(lái)。
包括每個(gè)中斷的名字、中斷號(hào) IRQ number、每個(gè)中斷的觸發(fā)次數(shù)、在哪個(gè)CPU核處理的、是邊沿觸發(fā)還是電平觸發(fā),屬于哪個(gè)中斷控制器,都會(huì)打印出來(lái)。
/proc/irq/…
進(jìn)入這個(gè)目錄。會(huì)看到以中斷號(hào)命名的文件夾,每個(gè)中斷號(hào)文件夾下面都有幾個(gè)節(jié)點(diǎn),存儲(chǔ)了這個(gè)中斷的信息,比如 smp_affinity、affinity_hint、spurious等。smp_affinity 代表中斷號(hào)核CPU之間的親緣綁定關(guān)系,也就是如果某個(gè)中斷號(hào)綁定了一個(gè)CPU核,那么這個(gè)中斷就會(huì)一直在這個(gè)CPU上處理。
如何讓某個(gè)中斷在某個(gè)特定的 CPU 處理?
kernel 2.4 以后的版本才支持把不同的硬件中斷請(qǐng)求(IRQs)分配到特定的 CPU 上,這個(gè)綁定技術(shù)被稱為 SMP IRQ Affinity. 更多介紹請(qǐng)參看 Linux 內(nèi)核源代碼自帶的文檔:linux-4.14/Documentation/IRQ-affinity.txt
/proc/irq/{IRQ}/smp_affinity
/proc/irq/{IRQ}/smp_affinity_list
/proc/irq/{IRQ}/smp_affinity 指定給定的 irq 中斷號(hào)源允許哪些CPU執(zhí)行,它是一個(gè)掩碼位,比如是ff,代表11111111,表示這個(gè)中奪冠可以在 8 個(gè) CPU 執(zhí)行,具體在哪一個(gè) CPU 執(zhí)行,靠分配器分配。
如果這個(gè) /proc/irq/{IRQ}/smp_affinity 指定為 00000001,代表這個(gè)IRQ只能在最后一個(gè)CPU核進(jìn)行處理,其他CPU不允許處理,大家可以測(cè)試一下,博主測(cè)試是 OK 的(GIC支持,其他中斷控制器不一定)。
串口手動(dòng)賦值的重啟以后會(huì)消失,可以在代碼中調(diào)用 irq_set_affinity 函數(shù),指定中斷的掩碼,來(lái)達(dá)到某個(gè)中斷被固定CPU處理的需求。
中斷分發(fā)機(jī)制
對(duì)于 GIC-V2 而言,SPI 的分發(fā)是根據(jù) Distributor 中的 Interrupt Processor Targets Registers 來(lái)決定的。對(duì)于任何一個(gè) SPI,其都有在某個(gè) GICD_ITARGETSRn 寄存器中有 8 個(gè)bit標(biāo)識(shí)送達(dá)的 processor,如果只有一個(gè) bit 被 set,那么就很簡(jiǎn)單了,如果該中斷是當(dāng)前優(yōu)先級(jí)最高的中斷,那么 Distributor 就會(huì)送到對(duì)應(yīng)的 CPU interface,該中斷最終會(huì)送達(dá)指定的 CPU。
如果該中斷對(duì)應(yīng)的 Interrupt Processor Targets Registers 中的那 8 個(gè) bit 有多個(gè) bit 被 set 的話,Distributor 如何處理呢?“依次輪著把產(chǎn)生的中斷給各個(gè) CPU,還是說(shuō)看哪個(gè)CPU有空就給哪個(gè)CPU來(lái)著”,讓硬件處理這么復(fù)雜的邏輯有些不合適,實(shí)際上,GIC 的硬件是不會(huì)進(jìn)行任何判斷的,也不會(huì)集成任何的算法,它就是根據(jù)Interrupt Processor Targets Registers的bit設(shè)定情況,忠實(shí)的把中斷送往指定的一個(gè)processor或者多個(gè)processors。
大家可以去看看 gic_set_affinity 這個(gè)函數(shù),這個(gè)函數(shù)確保一個(gè)中斷的 Interrupt Processor Targets Registers 中的那8個(gè)bit只有一個(gè)bit被設(shè)定。
/kernel5.15/drivers/irqchip/irq-gic-v3.c
在 1244 和 1246 行,1246 行就是在 online 的 CPU 中選中一個(gè),1263 行寫入到寄存器中,GIC 會(huì)讀取這個(gè)寄存器,是哪個(gè) CPU,然后將中斷發(fā)給這個(gè)CPU。中間的函數(shù)很簡(jiǎn)單,大家可以自己看。
中斷狀態(tài)機(jī)
對(duì)于 GIC-V2 而言,中斷的狀態(tài)機(jī)由 Distributor 維護(hù),每個(gè)中斷都有一個(gè)狀態(tài)機(jī)。
Inactive :中斷未激活(未發(fā)生)。
Pending:中斷到達(dá) GIC ,等待 CPU 的處理。
Active:中斷得到 CPU 的應(yīng)答,中斷被CPU處理。
Active and pending :某個(gè)中斷正在被 CPU 處理,這時(shí)候該中斷又來(lái)了。
來(lái)看一個(gè)例子:
(a)N 和 M 用來(lái)標(biāo)識(shí)兩個(gè)外設(shè)中斷,N 的優(yōu)先級(jí)大于 M
(b)兩個(gè)中斷都是 SPI 類型,level trigger,active-high
(c)兩個(gè)中斷被配置為去同一個(gè) CPU
(d)都被配置成 group 0,通過(guò) FIQ 觸發(fā)中斷
時(shí)刻 | 事件 |
T0時(shí)刻 | Distributor檢測(cè)到M這個(gè)interrupt source的有效觸發(fā)電平 |
T2時(shí)刻 | Distributor將M這個(gè)interrupt source的狀態(tài)設(shè)定為pending |
T17時(shí)刻 | 大約15個(gè)clock之后,CPU interface拉低nFIQCPU信號(hào)線,向CPU報(bào)告M外設(shè)的中斷請(qǐng)求。這時(shí)候,CPU interface的ack寄存器(GICC_IAR)的內(nèi)容會(huì)修改成M interrupt source對(duì)應(yīng)的ID |
T42時(shí)刻 | Distributor檢測(cè)到N這個(gè)優(yōu)先級(jí)更高的interrupt source的觸發(fā)事件 |
T43時(shí)刻 | Distributor將N這個(gè)interrupt source的狀態(tài)設(shè)定為pending。同時(shí),由于N的優(yōu)先級(jí)更高,因此Distributor會(huì)標(biāo)記當(dāng)前優(yōu)先級(jí)最高的中斷 |
T58時(shí)刻 | 大約15個(gè)clock之后,CPU interface拉低nFIQCPU信號(hào)線,向CPU報(bào)告N外設(shè)的中斷請(qǐng)求。當(dāng)然,由于T17時(shí)刻已經(jīng)assert CPU了,因此實(shí)際的電平信號(hào)仍然保持asserted。這時(shí)候,CPU interface的ack寄存器(GICC_IAR)的內(nèi)容會(huì)被更新成N interrupt source的ID |
T61時(shí)刻 | 軟件通過(guò)讀取 ack 寄存器的內(nèi)容,獲取了當(dāng)前優(yōu)先級(jí)最高的,并且狀態(tài)是pending的interrupt ID(也就是N interrupt source對(duì)應(yīng)的ID),通過(guò)讀該寄存器,CPU也就ack了該interrupt source N。這時(shí)候,Distributor將N這個(gè)interrupt source的狀態(tài)設(shè)定為pending and active(因?yàn)槭请娖接|發(fā),只要外部仍然有asserted的電平信號(hào),那么一定就是pending的,而該中斷是正在被CPU處理的中斷,因此狀態(tài)是pending and active)注意:T61標(biāo)識(shí)CPU開始服務(wù)該中斷 |
T64時(shí)刻 | 3個(gè)clock之后,由于CPU已經(jīng)ack了中斷,因此GIC中CPU interface模塊 deassert nFIQCPU信號(hào)線,解除發(fā)向該CPU的中斷請(qǐng)求 |
T126時(shí)刻 | 由于中斷服務(wù)程序操作了N外設(shè)的控制寄存器(ack外設(shè)的中斷),因此N外設(shè)deassert了其interrupt request signal |
T128時(shí)刻 | Distributor解除N外設(shè)的pending狀態(tài),因此N這個(gè)interrupt source的狀態(tài)設(shè)定為active |
T131時(shí)刻 | 軟件操作End of Interrupt寄存器(向GICC_EOIR寄存器寫入N對(duì)應(yīng)的interrupt ID),標(biāo)識(shí)中斷處理結(jié)束。Distributor將N這個(gè)interrupt source的狀態(tài)修改為idle,注意:T61~T131是CPU服務(wù)N外設(shè)中斷的的時(shí)間區(qū)域,這個(gè)期間,如果有高優(yōu)先級(jí)的中斷pending,會(huì)發(fā)生中斷的搶占(硬件意義的),這時(shí)候CPU interface會(huì)向CPU assert 新的中斷。 |
T146時(shí)刻 | 大約15個(gè)clock之后,Distributor向CPU interface報(bào)告當(dāng)前pending且優(yōu)先級(jí)最高的interrupt source,也就是M了。漫長(zhǎng)的pending之后,M終于迎來(lái)了春天。CPU interface拉低nFIQCPU信號(hào)線,向CPU報(bào)告M外設(shè)的中斷請(qǐng)求。這時(shí)候,CPU interface的ack寄存器(GICC_IAR)的內(nèi)容會(huì)修改成M interrupt source對(duì)應(yīng)的ID |
T211時(shí)刻 | CPU ack M中斷(通過(guò)讀GICC_IAR寄存器),開始處理低優(yōu)先級(jí)的中斷 |
Linux 搶占機(jī)制
GIC 中斷控制器支持中斷優(yōu)先級(jí)搶占,一個(gè)高優(yōu)先級(jí)中斷可以搶占一個(gè)低優(yōu)先級(jí)且處于active狀態(tài)的中斷,即GIC仲裁單元會(huì)記錄和比較當(dāng)前優(yōu)先級(jí)最高的pending狀態(tài),然后去搶占當(dāng)前中斷,并且發(fā)送這個(gè)最高優(yōu)先級(jí)的中斷請(qǐng)求給CPU。
從GIC角度看,GIC 會(huì)發(fā)送高優(yōu)先級(jí)中斷請(qǐng)求給CPU。但是CPU不一定響應(yīng)!!!因?yàn)樵谥袛嗵幚磉^(guò)程中,CPU處于關(guān)中斷狀態(tài)(關(guān)閉本CPU),需要等低優(yōu)先級(jí)中斷處理完畢,直到發(fā)送 EOI 給GIC,然后CPU才會(huì)響應(yīng)pending狀態(tài)中優(yōu)先級(jí)最高的中斷進(jìn)行處理。所以 Linux 下:
1、高優(yōu)先級(jí)中斷無(wú)法搶占正在執(zhí)行的低優(yōu)先級(jí)中斷。
2、同處于 pending 狀態(tài)的中斷,優(yōu)先響應(yīng)高優(yōu)先級(jí)中斷進(jìn)行處理。
3、同優(yōu)先級(jí)同是 pending 狀態(tài)的中斷,選擇硬件中斷號(hào) ID 最小的一個(gè)發(fā)給CPU。
這樣是可以理解的,如果萬(wàn)一中斷大量爆發(fā),中斷如果允許嵌套的話,棧會(huì)越來(lái)越大,會(huì)爆掉,所以為了防止這種情況發(fā)生,Linux中中斷不允許嵌套,單CPU中,在一個(gè)中斷處理完之前,不會(huì)相應(yīng)另外一個(gè)中斷,哪怕優(yōu)先級(jí)比它高。
FreeRTOS 中是允許高優(yōu)先級(jí)中斷搶占正在執(zhí)行的低優(yōu)先級(jí)中斷,不同系統(tǒng)設(shè)定不一樣。
中斷與進(jìn)程
進(jìn)程調(diào)度是一個(gè)復(fù)雜的機(jī)制, 根據(jù)需求的不同,在不同時(shí)刻會(huì)切換調(diào)度機(jī)制,CPU會(huì)根據(jù)進(jìn)程優(yōu)先級(jí)、時(shí)間片等信息,對(duì)不同進(jìn)程進(jìn)行調(diào)度。
中斷可以打斷進(jìn)程的運(yùn)行,任意一個(gè)中斷的優(yōu)先級(jí)都比所有的進(jìn)程高。
在中斷處理過(guò)程中,主要是 GIC 和 CPU 的交互,即便 GIC 支持高優(yōu)先級(jí)中斷搶占正在執(zhí)行的低優(yōu)先級(jí)中斷,發(fā)信號(hào)給 CPU core,但是 CPU core 可以不處理,因?yàn)?Linux 中當(dāng) CPU core 執(zhí)行中斷處理時(shí),是關(guān)中斷和關(guān)搶占的狀態(tài),不再相應(yīng)中斷信號(hào)。
也就意味著,在中斷優(yōu)先級(jí)這個(gè)概念中,只有當(dāng) GIC 同時(shí)存在多個(gè) pending 的中斷,這時(shí)候會(huì)選擇優(yōu)先級(jí)最高的去執(zhí)行,高優(yōu)先級(jí)會(huì)搶占低優(yōu)先級(jí)中斷(哪怕低優(yōu)先級(jí)先來(lái))。如果低優(yōu)先級(jí)中斷處于 active 狀態(tài),是不可以被搶占的,這是前后關(guān)系。搶占只存在于同時(shí)是pending 狀態(tài)的時(shí)候。
Linux 為什么中斷不允許休眠?
所謂的睡眠,就是調(diào)用 schedule 讓出 CPU,調(diào)度器選擇另外個(gè)進(jìn)程繼續(xù)執(zhí)行,這個(gè)過(guò)程涉及進(jìn)程??臻g的切換。
1、假如中斷上下文中調(diào)用 schedule ,此時(shí)獲取的 struct thread info 數(shù)據(jù)結(jié)構(gòu)是發(fā)生中斷時(shí)該進(jìn)程棧信息,而不是中斷上下文調(diào)用 schedule 時(shí)任何信息。這就導(dǎo)致再也無(wú)法返回中斷上下文中調(diào)用 schedule 的地方。
2、中斷上下文處于關(guān)中斷中,需要發(fā)送個(gè) EOI 通知 GIC 中斷處理結(jié)束,GIC 和CPUinterface 才會(huì)進(jìn)入下一次中斷處理。如果中途 schedule,那么整個(gè)系統(tǒng)的中斷都會(huì)被屏蔽掉。
一般進(jìn)入中斷后,需要關(guān)中斷,也會(huì)關(guān)搶占,同時(shí)注意不可以調(diào)用schedule。
unhandled interrupt 和 spurious interrupt
未處理中斷和虛假中斷
在中斷處理的最后,總會(huì)有一段代碼如下:
- irqreturn_t
- handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
- {
- ……
- if (!noirqdebug)
- note_interrupt(irq, desc, retval);
- return retval;
- }
note_interrupt就是進(jìn)行unhandled interrupt和spurious interrupt處理的。對(duì)于這類中斷,linux kernel有一套復(fù)雜的機(jī)制來(lái)處理,你可以通過(guò)command line參數(shù)(noirqdebug)來(lái)控制開關(guān)該功能。
當(dāng)發(fā)生了一個(gè)中斷,但是沒有被處理(有兩種可能,一種是根本沒有注冊(cè)的 specific handler,第二種是有 handler,但是 handler 否認(rèn)是自己對(duì)應(yīng)的設(shè)備觸發(fā)的中斷),怎么辦?毫無(wú)疑問(wèn)這是一個(gè)異常狀況,那么 kernel 是否要立刻采取措施將該 IRQ disable 呢?也不太合適,畢竟 interrupt request 信號(hào)線是允許共享的,直接 disable 該 IRQ 有可能會(huì)下手太狠,kernel 采取了這樣的策略:如果該 IRQ 觸發(fā)了 100,000 次,但是 99,900 次沒有處理,在這種條件下,我們就是 disable 這個(gè) interrupt request line。
中斷線和中斷號(hào)是一個(gè)意思。
相關(guān)的控制數(shù)據(jù)在中斷描述符中,如下:
- struct irq_desc {
- ……
- unsigned int irq_count;--------記錄發(fā)生的中斷的次數(shù),每100,000則回滾
- unsigned long last_unhandled;-----上一次沒有處理的IRQ的時(shí)間點(diǎn)
- unsigned int irqs_unhandled;------沒有處理的次數(shù)
- ……
- }
中斷的生命周期