從"取外賣(mài)"看中斷
中斷是系統(tǒng)用來(lái)響應(yīng)硬件設(shè)備請(qǐng)求的一種機(jī)制,它會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行胁塞,然后調(diào)用內(nèi)核中的中斷處理程序來(lái)響應(yīng)設(shè)備的請(qǐng)求。
你可能要問(wèn)了压语,為什么要有中斷呢啸罢?我可以舉個(gè)生活中的例子,讓感受一下中斷的魅力胎食。
比如你訂了一份外賣(mài)扰才,但是不確定外賣(mài)什么時(shí)候送到,也沒(méi)有別的方法了解外賣(mài)的進(jìn)度厕怜,但是衩匣,配送員送外賣(mài)是不等人的蕾总,到了你這兒沒(méi)人取的話(huà),就直接走人了琅捏,所以你只能苦苦等著生百,時(shí)不時(shí)去門(mén)口看看外賣(mài)送到?jīng)],而不能干其他事情柄延。
不過(guò)呢蚀浆,如果在訂外賣(mài)的時(shí)候,你就跟配送員約定好搜吧,讓他送到后給你打個(gè)電話(huà)市俊,那你就不用苦苦等待了,就可以去忙別的事情滤奈,直到電話(huà)一響摆昧,接電話(huà)、取外賣(mài)就可以了僵刮。
這里的“打電話(huà)”据忘,其實(shí)就是一個(gè)中斷。沒(méi)接到電話(huà)的時(shí)候搞糕,你可以做其他的事情勇吊;只有接到了電話(huà)(也就是發(fā)生中斷),你才要進(jìn)行另一個(gè)動(dòng)作:取外賣(mài)窍仰。
這個(gè)例子你就可以發(fā)現(xiàn)汉规,中斷其實(shí)是一種異步的事件處理機(jī)制,可以提高系統(tǒng)的并發(fā)處理能力驹吮。
由于中斷處理程序會(huì)打斷其他進(jìn)程的運(yùn)行针史,所以,為了減少對(duì)正常進(jìn)程運(yùn)行調(diào)度的影響碟狞,中斷處理程序就需要盡可能快地運(yùn)行啄枕。如果中斷本身要做的事情不多,那么處理起來(lái)也不會(huì)有太大問(wèn)題族沃;但如果中斷要處理的事情很多频祝,中斷服務(wù)程序就有可能要運(yùn)行很長(zhǎng)時(shí)間。
特別是脆淹,中斷處理程序在響應(yīng)中斷時(shí)常空,還會(huì)臨時(shí)關(guān)閉中斷。這就會(huì)導(dǎo)致上一次中斷處理完成之前盖溺,其他中斷都不能響應(yīng)漓糙,也就是說(shuō)中斷有可能會(huì)丟失。
那么還是以取外賣(mài)為例烘嘱。假如你訂了 2 份外賣(mài)昆禽,一份主食和一份飲料蝗蛙,并且是由 2 個(gè)不同的配送員來(lái)配送。這次你不用時(shí)時(shí)等待著醉鳖,兩份外賣(mài)都約定了電話(huà)取外賣(mài)的方式歼郭。但是,問(wèn)題又來(lái)了辐棒。
當(dāng)?shù)谝环萃赓u(mài)送到時(shí)病曾,配送員給你打了個(gè)長(zhǎng)長(zhǎng)的電話(huà),商量發(fā)票的處理方式漾根。與此同時(shí)泰涂,第二個(gè)配送員也到了,也想給你打電話(huà)辐怕。
但是很明顯逼蒙,因?yàn)殡娫?huà)占線(xiàn)(也就是關(guān)閉了中斷響應(yīng)),第二個(gè)配送員的電話(huà)是打不通的寄疏。所以是牢,第二個(gè)配送員很可能試幾次后就走掉了(也就是丟失了一次中斷)。
軟中斷
如果你弄清楚了“取外賣(mài)”的模式陕截,那對(duì)系統(tǒng)的中斷機(jī)制就很容易理解了驳棱。事實(shí)上,為了解決中斷處理程序執(zhí)行過(guò)長(zhǎng)和中斷丟失的問(wèn)題农曲,Linux 將中斷處理過(guò)程分成了兩個(gè)階段社搅,也就是上半部和下半部:
- 上半部用來(lái)快速處理中斷,它在中斷禁止模式下運(yùn)行乳规,主要處理跟硬件緊密相關(guān)的或時(shí)間敏感的工作形葬。
- 下半部用來(lái)延遲處理上半部未完成的工作,通常以?xún)?nèi)核線(xiàn)程的方式運(yùn)行暮的。
比如說(shuō)前面取外賣(mài)的例子笙以,上半部就是你接聽(tīng)電話(huà),告訴配送員你已經(jīng)知道了冻辩,其他事兒見(jiàn)面再說(shuō)猖腕,然后電話(huà)就可以?huà)鞌嗔耍幌掳氩坎攀侨⊥赓u(mài)的動(dòng)作微猖,以及見(jiàn)面后商量發(fā)票處理的動(dòng)作谈息。
這樣缘屹,第一個(gè)配送員不會(huì)占用你太多時(shí)間凛剥,當(dāng)?shù)诙€(gè)配送員過(guò)來(lái)時(shí),照樣能正常打通你的電話(huà)轻姿。
除了取外賣(mài)犁珠,我再舉個(gè)最常見(jiàn)的網(wǎng)卡接收數(shù)據(jù)包的例子逻炊,讓你更好地理解。
網(wǎng)卡接收到數(shù)據(jù)包后犁享,會(huì)通過(guò)硬件中斷的方式余素,通知內(nèi)核有新的數(shù)據(jù)到了。這時(shí)炊昆,內(nèi)核就應(yīng)該調(diào)用中斷處理程序來(lái)響應(yīng)它桨吊。你可以自己先想一下,這種情況下的上半部和下半部分別負(fù)責(zé)什么工作呢凤巨?
對(duì)上半部來(lái)說(shuō)视乐,既然是快速處理,其實(shí)就是要把網(wǎng)卡的數(shù)據(jù)讀到內(nèi)存中敢茁,然后更新一下硬件寄存器的狀態(tài)(表示數(shù)據(jù)已經(jīng)讀好了)佑淀,最后再發(fā)送一個(gè)軟中斷信號(hào),通知下半部做進(jìn)一步的處理彰檬。
而下半部被軟中斷信號(hào)喚醒后伸刃,需要從內(nèi)存中找到網(wǎng)絡(luò)數(shù)據(jù),再按照網(wǎng)絡(luò)協(xié)議棧逢倍,對(duì)數(shù)據(jù)進(jìn)行逐層解析和處理捧颅,直到把它送給應(yīng)用程序。
所以较雕,這兩個(gè)階段你也可以這樣理解:
- 上半部直接處理硬件請(qǐng)求隘道,也就是我們常說(shuō)的硬中斷,特點(diǎn)是快速執(zhí)行郎笆;
- 而下半部則是由內(nèi)核觸發(fā)谭梗,也就是我們常說(shuō)的軟中斷,特點(diǎn)是延遲執(zhí)行宛蚓。
實(shí)際上激捏,上半部會(huì)打斷 CPU 正在執(zhí)行的任務(wù),然后立即執(zhí)行中斷處理程序凄吏。而下半部以?xún)?nèi)核線(xiàn)程的方式執(zhí)行远舅,并且每個(gè) CPU 都對(duì)應(yīng)一個(gè)軟中斷內(nèi)核線(xiàn)程,名字為 “ksoftirqd/CPU 編號(hào)”痕钢,比如說(shuō)图柏, 0 號(hào) CPU 對(duì)應(yīng)的軟中斷內(nèi)核線(xiàn)程的名字就是 ksoftirqd/0。
不過(guò)要注意的是任连,軟中斷不只包括了剛剛所講的硬件設(shè)備中斷處理程序的下半部蚤吹,一些內(nèi)核自定義的事件也屬于軟中斷,比如內(nèi)核調(diào)度和 RCU 鎖(Read-Copy Update 的縮寫(xiě),RCU 是 Linux 內(nèi)核中最常用的鎖之一)等裁着。
查看軟中斷和內(nèi)核線(xiàn)程
不知道你還記不記得繁涂,前面提到過(guò)的 proc 文件系統(tǒng)。它是一種內(nèi)核空間和用戶(hù)空間進(jìn)行通信的機(jī)制二驰,可以用來(lái)查看內(nèi)核的數(shù)據(jù)結(jié)構(gòu)扔罪,或者用來(lái)動(dòng)態(tài)修改內(nèi)核的配置。其中:
- /proc/softirqs 提供了軟中斷的運(yùn)行情況桶雀;
- /proc/interrupts 提供了硬中斷的運(yùn)行情況矿酵。
運(yùn)行下面的命令,查看 /proc/softirqs 文件的內(nèi)容矗积,你就可以看到各種類(lèi)型軟中斷在不同 CPU 上的累積運(yùn)行次數(shù):
$ cat /proc/softirqs
CPU0 CPU1
HI: 0 0
TIMER: 811613 1972736
NET_TX: 49 7
NET_RX: 1136736 1506885
BLOCK: 0 0
IRQ_POLL: 0 0
TASKLET: 304787 3691
SCHED: 689718 1897539
HRTIMER: 0 0
RCU: 1330771 1354737
在查看 /proc/softirqs 文件內(nèi)容時(shí)坏瘩,你要特別注意以下這兩點(diǎn)。
第一漠魏,要注意軟中斷的類(lèi)型倔矾,也就是這個(gè)界面中第一列的內(nèi)容。從第一列你可以看到柱锹,軟中斷包括了 10 個(gè)類(lèi)別哪自,分別對(duì)應(yīng)不同的工作類(lèi)型。比如 NET_RX 表示網(wǎng)絡(luò)接收中斷禁熏,而 NET_TX 表示網(wǎng)絡(luò)發(fā)送中斷壤巷。
第二,要注意同一種軟中斷在不同 CPU 上的分布情況瞧毙,也就是同一行的內(nèi)容胧华。正常情況下,同一種中斷在不同 CPU 上的累積次數(shù)應(yīng)該差不多宙彪。比如這個(gè)界面中矩动,NET_RX 在 CPU0 和 CPU1 上的中斷次數(shù)基本是同一個(gè)數(shù)量級(jí),相差不大释漆。
不過(guò)你可能發(fā)現(xiàn)悲没,TASKLET 在不同 CPU 上的分布并不均勻。TASKLET 是最常用的軟中斷實(shí)現(xiàn)機(jī)制男图,每個(gè) TASKLET 只運(yùn)行一次就會(huì)結(jié)束 示姿,并且只在調(diào)用它的函數(shù)所在的 CPU 上運(yùn)行。
因此逊笆,使用 TASKLET 特別簡(jiǎn)便栈戳,當(dāng)然也會(huì)存在一些問(wèn)題,比如說(shuō)由于只在一個(gè) CPU 上運(yùn)行導(dǎo)致的調(diào)度不均衡难裆,再比如因?yàn)椴荒茉诙鄠€(gè) CPU 上并行運(yùn)行帶來(lái)了性能限制子檀。
另外,剛剛提到過(guò),軟中斷實(shí)際上是以?xún)?nèi)核線(xiàn)程的方式運(yùn)行的命锄,每個(gè) CPU 都對(duì)應(yīng)一個(gè)軟中斷內(nèi)核線(xiàn)程,這個(gè)軟中斷內(nèi)核線(xiàn)程就叫做 ksoftirqd/CPU 編號(hào)偏化。那要怎么查看這些線(xiàn)程的運(yùn)行狀況呢脐恩?
其實(shí)用 ps 命令就可以做到,比如執(zhí)行下面的指令:
$ ps aux | grep softirq
root 7 0.0 0.0 0 0 ? S Oct10 0:01 [ksoftirqd/0]
root 16 0.0 0.0 0 0 ? S Oct10 0:01 [ksoftirqd/1]
注意侦讨,這些線(xiàn)程的名字外面都有中括號(hào)驶冒,這說(shuō)明 ps 無(wú)法獲取它們的命令行參數(shù)(cmline)。一般來(lái)說(shuō)韵卤,ps 的輸出中骗污,名字括在中括號(hào)里的,一般都是內(nèi)核線(xiàn)程沈条。
小結(jié)
Linux 中的中斷處理程序分為上半部和下半部:
上半部對(duì)應(yīng)硬件中斷需忿,用來(lái)快速處理中斷。
下半部對(duì)應(yīng)軟中斷蜡歹,用來(lái)異步處理上半部未完成的工作屋厘。
Linux 中的軟中斷包括網(wǎng)絡(luò)收發(fā)、定時(shí)月而、調(diào)度汗洒、RCU 鎖等各種類(lèi)型,可以通過(guò)查看 /proc/softirqs 來(lái)觀(guān)察軟中斷的運(yùn)行情況父款。