linux中斷處理淺析最近在研究異步消息處理, 突然想起linux內(nèi)核的中斷處理, 里面由始至終都貫穿著"重要的事馬上做, 不重要的事推后做"的異步處理思想. 于是整理一下~第一階段--獲取中斷號 每個CPU都有響應(yīng)中斷的能力, 每個CPU響應(yīng)中斷時都走相同的流程. 這個流程就是內(nèi)核提供的中斷服務(wù)程序. 在進(jìn)入中斷服務(wù)程序時, CPU已經(jīng)自動禁止了本CPU上的中斷響應(yīng), 因為CPU不能假定中斷服務(wù)程序是可重入的. 中斷處理程序的第一步要做兩件事情: 1. 將中斷號壓入棧中; (不同中斷號的中斷對應(yīng)不同的中斷服務(wù)程序入口) 2. 將當(dāng)前寄存器信息壓入棧中; (以便中斷退出時恢復(fù)) 顯然, 這兩步都是不可重入的(如果在保存寄存器值時被中斷了, 那么另外的操作很可能就把寄存器給改寫了, 現(xiàn)場將無法恢復(fù)), 所以前面說到的CPU進(jìn)入中斷服務(wù)程序時要自動禁止中斷. 棧上的信息被作為函數(shù)參數(shù), 調(diào)用do_IRQ函數(shù). 第二階段--中斷串行化 進(jìn) 入do_IRQ函數(shù), 第一步進(jìn)行中斷的串行化處理, 將多個CPU同時產(chǎn)生的某一中斷進(jìn)行串行化. 其方法是如果當(dāng)前中斷處于"執(zhí)行"狀態(tài)(表明另一個CPU正在處理相同的中斷), 則重新設(shè)置它的"觸發(fā)"標(biāo)記, 然后立即返回. 正在處理同一中斷的那個CPU完成一次處理后, 會再次檢查"觸發(fā)"標(biāo)記, 如果設(shè)置, 則再次觸發(fā)處理過程. 于是, 中斷的處理是一個循環(huán)過程, 每次循環(huán)調(diào)用handle_IRQ_event來處理中斷. 第三階段--關(guān)中斷條件下的中斷處理 進(jìn)入handle_IRQ_event函數(shù), 調(diào)用對應(yīng)的內(nèi)核或內(nèi)核模塊通過request_irq函數(shù)注冊的中斷處理函數(shù). 注 冊的中斷處理函數(shù)有個中斷開關(guān)屬性, 一般情況下, 中斷處理函數(shù)總是在關(guān)中斷的情況下進(jìn)行的. 而調(diào)用request_irq注冊中斷處理函數(shù)時也可以設(shè)置該中斷處理函數(shù)在開中斷的情況下進(jìn)行, 這種情況比較少見, 因為這要求中斷處理代碼必須是可重入的. (另外, 這里如果開中斷, 正在處理的這個中斷一般也是會被阻塞的. 因為正在處理某個中斷的時候, 硬件中斷控制器上的這個中斷并未被ack, 硬件不會發(fā)起下一次相同的中斷.) 中斷處理函數(shù)的過程可能會很長, 如果整個過程都在關(guān)中斷的情況下進(jìn)行, 那么后續(xù)的中斷將被阻塞很長的時間. 于是, 有了soft_irq. 把不可重入的一部分在中斷處理程序中(關(guān)中斷)去完成, 然后調(diào)用raise_softirq設(shè)置一個軟中斷, 中斷處理程序結(jié)束. 后面的工作將放在soft_irq里面去做. 第四階段--開中斷條件下的軟中斷 上一階段循環(huán)調(diào)用完當(dāng)前所有被觸發(fā)的中斷處理函數(shù)后, do_softirq函數(shù)被調(diào)用, 開始處理軟件中斷. 在 軟中斷機制中, 為每個CPU維護(hù)了一個若干位的掩碼集, 每位掩碼代表一個中斷號. 在上一階段的中斷處理函數(shù)中, 調(diào)用raise_softirq設(shè)置了對應(yīng)的軟中斷, 到了這里, 軟中斷對應(yīng)的處理函數(shù)就會被調(diào)用(處理函數(shù)由open_softirq函數(shù)來注冊). 可以看出, 軟中斷與中斷的模型很類似, 每個CPU有一組中斷號, 中斷有其對應(yīng)的優(yōu)先級, 每個CPU處理屬于自己的中斷. 最大的不同是開中斷與關(guān)中斷. 于是, 一個中斷處理過程被分成了兩部分, 第一部分在中斷處理函數(shù)里面關(guān)中斷的進(jìn)行, 第二部分在軟中斷處理函數(shù)里面開中斷的進(jìn)行.
由 于這一步是在開中斷條件下進(jìn)行的,,這里還可能發(fā)生新的中斷(中斷嵌套),,然后新中斷對應(yīng)的中斷處理又將開始一個新的第一階段~第三階段。在新的這個第三階 段中,,可能又會觸發(fā)新的軟中斷,。但是這個新的中斷處理過程并不會進(jìn)入第四階段,而是當(dāng)它發(fā)現(xiàn)自己是嵌套的中斷時,,完成第三階段之后就會退出了,。也就是說, 只有第一層中斷處理過程會進(jìn)入第四階段,,嵌套發(fā)生的中斷處理過程只執(zhí)行到第三階段,。 然而嵌套發(fā)生的中斷處理過程也可能會觸發(fā)軟中斷,所以第一層中斷處理過程在第四階段需要是一個循環(huán)的過程,,需要循環(huán)處理嵌套發(fā)生的所有軟中斷,。為什么要這樣做呢?因為這樣可以按軟中斷觸發(fā)的順序來執(zhí)行這些軟中斷,,否則后來的軟中斷可能就會先執(zhí)行完成了,。 極端情況下,嵌套發(fā)生的軟中斷可能非常多,,全部處理完可能需要很長的時間,,于是內(nèi)核會在處理完一定數(shù)量的軟中斷后,將剩下未處理的軟中斷推給一個叫ksoftirqd的內(nèi)核線程來處理,然后結(jié)束本次中斷處理過程,。 第五階段--開中斷條件下的tasklet 實際上, 軟中斷很少直接被使用. 而第二部分開中斷情況下的進(jìn)行的處理過程一般是由tasklet機制來完成的. tasklet是由軟中斷引出的, 內(nèi)核定義了兩個軟中斷掩碼HI_SOFTIRQ和TASKLET_SOFTIRQ(兩者優(yōu)先級不同), 這兩個掩碼對應(yīng)的軟中斷處理函數(shù)作為入口, 進(jìn)入tasklet處理過程. 于 是, 在第三階段的中斷處理函數(shù)中, 完成關(guān)中斷的部分后, 然后調(diào)用tasklet_schedule/tasklet_hi_schedule標(biāo)記一個tasklet, 然后中斷處理程序結(jié)束. 后面的工作由HI_SOFTIRQ/TASKLET_SOFTIRQ對應(yīng)的軟中斷處理程序去處理被標(biāo)記的tasklet(每個tasklet在其初始化時 都設(shè)置了處理函數(shù)). 看上去, tasklet只不過是在softirq的基礎(chǔ)上多了一層調(diào)用, 其作用是什么呢? 前面說過, softirq是與CPU相對應(yīng)的, 每個CPU處理自己的softirq. 這些softirq的處理函數(shù)需要設(shè)計為可重入的, 因為它們可能在多個CPU上同時運行. 而tasklet則是在多個CPU間被串行化執(zhí)行的, 其處理函數(shù)不必考慮可重入的事情. 然而, softirq畢竟還是要比tasklet少繞點彎路, 所以少數(shù)實時性要求相對較高的處理過程還是在精心設(shè)計之后, 直接使用softirq了. 比如: 時鐘中斷處理過程, 網(wǎng)絡(luò)發(fā)送/接收處理過程. 結(jié)尾階段 CPU接收到中斷以后, 以歷以上五個階段, 中斷處理完成. 最后需要恢復(fù)第一階段中被保存在棧上的寄存器信息. 中斷處理結(jié)束. 關(guān)于調(diào)度 上面的流程中, 還隱含了一個問題, 整個處理過程是持續(xù)占有CPU的(除了開中斷情況下可能被新的中斷打斷以外). 并且, 中斷處理的這幾個階段中, 程序不能夠讓出CPU! 這是由內(nèi)核的設(shè)計決定的, 中斷服務(wù)程序沒有自己的task結(jié)構(gòu)(即操作系統(tǒng)教科書上說的進(jìn)程控制塊), 所以它不能被內(nèi)核調(diào)度. 通常說一個進(jìn)程讓出CPU, 在之后如果滿足某種條件, 內(nèi)核會通過它的task結(jié)構(gòu)找到它, 并調(diào)度其運行. 這里可能存在兩方面的問題: 1. 連續(xù)的低優(yōu)先的中斷可能持續(xù)占有CPU, 而高優(yōu)先的某些進(jìn)程則無法獲得CPU; 2. 中斷處理的這幾個階段中不能調(diào)用可能導(dǎo)致睡眠的函數(shù)(包括分配內(nèi)存); 對 于第一個問題, 較新的linux內(nèi)核增加了ksoftirqd內(nèi)核線程, 如果持續(xù)處理的softirq超過一定數(shù)量, 則結(jié)束中斷處理過程, 然后喚醒ksoftirqd, 讓它來繼續(xù)處理. 雖然softirq可能被推后到ksoftirqd內(nèi)核線程去處理, 但是還是不能在softirq處理過程中睡眠, 因為不能保證softirq一定在ksoftirqd內(nèi)核線程中被處理. 據(jù)說在montavista(一種嵌入式實時linux)中, 將內(nèi)核的中斷機制做了修改. (某些中斷的)中斷處理過程被賦予了task結(jié)構(gòu), 能夠被內(nèi)核調(diào)度. 解決了上述兩個問題. (montavista的目標(biāo)是實時性, 這樣的做法犧牲了一定的整體性能.) 工作隊列 linux基線版本的內(nèi)核在解決上述問題上, 提供了workqueue機制. 定義一個work結(jié)構(gòu)(包含了處理函數(shù)), 然后在上述的中斷處理的幾個階段的某一步中調(diào)用schedule_work函數(shù), work便被添加到workqueue中, 等待處理. 工作隊列有著自己的處理線程, 這些work被推遲到這些線程中去處理. 處理過程只可能發(fā)生在這些工作線程中, 所以這里可以睡眠. 內(nèi)核默認(rèn)啟動了一個工作隊列, 對應(yīng)一組工作線程events/n(n代表處理器編號, 這樣的線程有n個). 驅(qū)動程序可以直接向這個工作隊列添加任務(wù). 某些驅(qū)動程序還可能會創(chuàng)建并使用屬于自己的工作隊列. |
|