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