基本思想
一個(gè)線程不應(yīng)該由其他線程來(lái)強(qiáng)制中斷或停止,而是應(yīng)該由線程自己自行停止。 中斷,只是一個(gè)協(xié)作通知信號(hào)量。好比是家里的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎么注意身體則完全取決于自己。
- 因之前的思想與現(xiàn)在的相悖,故Thread.stop, Thread.suspend, Thread.resume 都已經(jīng)被廢棄了。
- 現(xiàn)在的思想是采用Thread.interrupt ,其作用其實(shí)也不是中斷線程,而是「通知線程應(yīng)該中斷了」,具體到底中斷還是繼續(xù)運(yùn)行,應(yīng)該由被通知的線程自己處理。
具體來(lái)說(shuō),當(dāng)對(duì)一個(gè)線程,調(diào)用 interrupt() 時(shí),
- 如果線程處于被阻塞狀態(tài)(例如處于sleep, wait, join 等狀態(tài)),那么線程將立即退出被阻塞狀態(tài),并拋出一個(gè)InterruptedException異常。僅此而已。
- 如果線程處于正?;顒?dòng)狀態(tài),那么會(huì)將該線程的中斷標(biāo)志設(shè)置為 true,僅此而已。被設(shè)置中斷標(biāo)志的線程將繼續(xù)正常運(yùn)行,不受影響。
interrupt() 并不能真正的中斷線程,需要被調(diào)用的線程自己進(jìn)行配合才行。也就是說(shuō),一個(gè)線程如果有被中斷的需求,那么就可以這樣做。
- 在正常運(yùn)行任務(wù)時(shí),經(jīng)常檢查本線程的中斷標(biāo)志位,如果被設(shè)置了中斷標(biāo)志就自行停止線程。
- 在調(diào)用阻塞方法時(shí)正確處理InterruptedException異常。
(例如,catch異常后就結(jié)束線程。) - 當(dāng)你的捕獲到一個(gè)InterruptedException異常后,亦可以處理它,或者向上拋出。
- 拋出時(shí)要注意:當(dāng)你捕獲到InterruptedException異常后,當(dāng)前線程的中斷狀態(tài)已經(jīng)被修改為false(表示線程未被中斷);此時(shí)你若能夠處理中斷,則不用理會(huì)該值;但如果你繼續(xù)向上拋InterruptedException異常,你需要再次調(diào)用interrupt方法,將當(dāng)前線程的中斷狀態(tài)設(shè)為true。
- 注意:絕對(duì)不能“吞掉中斷”!即捕獲了InterruptedException而不作任何處理。這樣違背了中斷機(jī)制的規(guī)則,別人想讓你線程中斷,然而你自己不處理,也不將中斷請(qǐng)求告訴調(diào)用者,調(diào)用者一直以為沒(méi)有中斷請(qǐng)求。
相關(guān)方法
| 方法 | 說(shuō)明 |
|---|---|
| public static boolean interrupted | 測(cè)試當(dāng)前線程是否已經(jīng)中斷。線程的中斷狀態(tài) 由該方法清除。換句話說(shuō),如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線程再次中斷的情況除外)。 |
| public boolean isInterrupted() | 測(cè)試線程是否已經(jīng)中斷。線程的中斷狀態(tài)不受該方法的影響。 |
| public void interrupt() | 中斷線程。 |
Linux 中斷
什么是中斷,操作系統(tǒng)級(jí)別的定義
Linux 內(nèi)核需要對(duì)連接到計(jì)算機(jī)上的所有硬件設(shè)備進(jìn)行管理,毫無(wú)疑問(wèn)這是它的份內(nèi)事。如果要管理這些設(shè)備,首先得和它們互相通信才行,一般有兩種方案可實(shí)現(xiàn)這種功能:
- 輪詢(polling)** 讓內(nèi)核定期對(duì)設(shè)備的狀態(tài)進(jìn)行查詢,然后做出相應(yīng)的處理;
- 中斷(interrupt)** 讓硬件在需要的時(shí)候向內(nèi)核發(fā)出信號(hào)(變內(nèi)核主動(dòng)為硬件主動(dòng))。
第一種方案會(huì)讓內(nèi)核做不少的無(wú)用功,因?yàn)檩喸兛倳?huì)周期性的重復(fù)執(zhí)行,大量地耗用 CPU 時(shí)間,因此效率及其低下,所以一般都是采用第二種方案
從物理學(xué)的角度看,中斷是一種電信號(hào),由硬件設(shè)備產(chǎn)生,并直接送入中斷控制器(如 8259A)的輸入引腳上,然后再由中斷控制器向處理器發(fā)送相應(yīng)的信號(hào)。處理器一經(jīng)檢測(cè)到該信號(hào),便中斷自己當(dāng)前正在處理的工作,轉(zhuǎn)而去處理中斷。此后,處理器會(huì)通知 OS 已經(jīng)產(chǎn)生中斷。這樣,OS 就可以對(duì)這個(gè)中斷進(jìn)行適當(dāng)?shù)奶幚怼?strong>不同的設(shè)備對(duì)應(yīng)的中斷不同,而每個(gè)中斷都通過(guò)一個(gè)唯一的數(shù)字標(biāo)識(shí),這些值通常被稱為中斷請(qǐng)求線。
一、最簡(jiǎn)單的中斷機(jī)制
最簡(jiǎn)單的中斷機(jī)制就是像芯片手冊(cè)上講的那樣,在中斷向量表中填入跳轉(zhuǎn)到對(duì)應(yīng)處理函數(shù)的指令,然后在處理函數(shù)中實(shí)現(xiàn)需要的功能。類似下圖:

這種方式在原來(lái)的單片機(jī)課程中常常用到,一些簡(jiǎn)單的單片機(jī)系統(tǒng)也是這樣用。
它的好處很明顯,簡(jiǎn)單,直接。
二、下半部:分離中斷接收與中斷處理過(guò)程
中斷處理函數(shù)所作的第一件事情是什么?答案是屏蔽中斷(或者是什么都不做,因?yàn)槌3J侨绻磺宄齀F位,就等于屏蔽中斷了),當(dāng)然只屏蔽同一種中斷。之所以要屏蔽中斷,是因?yàn)樾碌闹袛鄷?huì)再次調(diào)用中斷處理函數(shù),導(dǎo)致原來(lái)中斷處理現(xiàn)場(chǎng)的破壞。即,破壞了 interrupt context。
隨著系統(tǒng)的不斷復(fù)雜,中斷處理函數(shù)要做的事情也越來(lái)越多,多到都來(lái)不及接收新的中斷了。于是發(fā)生了中斷丟失,這顯然不行,于是產(chǎn)生了新的機(jī)制:分離中斷接收與中斷處理過(guò)程。中斷接收在屏蔽中斷的情況下完成;中斷處理在使能中斷的情況下完成,這部分被稱為中斷下半部。

從上圖中看,只看int0的處理。Func0為中斷接收函數(shù)。中斷只能簡(jiǎn)單的觸發(fā)func0,而func0則能做更多的事情,它與funcA之間可以使用隊(duì)列等緩存機(jī)制。當(dāng)又有中斷發(fā)生時(shí),func0被觸發(fā),然后發(fā)送一個(gè)中斷請(qǐng)求到緩存隊(duì)列,然后讓funcA去處理。
由于func0做的事情是很簡(jiǎn)單的,所以不會(huì)影響int0的再次接收。而且在func0返回時(shí)就會(huì)使能int0,因此funcA執(zhí)行時(shí)間再長(zhǎng)也不會(huì)影響int0的接收。
三、軟中斷(統(tǒng)一調(diào)度器)
下面看看linux中斷處理。作為一個(gè)操作系統(tǒng)顯然不能任由每個(gè)中斷都各自為政,統(tǒng)一管理是必須的。
我們不可中斷部分的共同部分放在函數(shù)do_IRQ中,需要添加中斷處理函數(shù)時(shí),通過(guò)request_irq實(shí)現(xiàn)。下半部放在do_softirq中,也就是軟中斷,通過(guò)open_softirq添加對(duì)應(yīng)的處理函數(shù)。

四、tasklet
舊事物跟不上歷史的發(fā)展時(shí),總會(huì)有新事物出現(xiàn)。
隨著中斷數(shù)的不停增加,軟中斷不夠用了,于是下半部又做了進(jìn)化。
軟中斷用輪詢的方式處理。假如正好是最后一種中斷,則必須循環(huán)完所有的中斷類型,才能最終執(zhí)行對(duì)應(yīng)的處理函數(shù)。顯然當(dāng)年開(kāi)發(fā)人員為了保證輪詢的效率,于是限制中斷個(gè)數(shù)為32個(gè)。
為了提高中斷處理數(shù)量,順道改進(jìn)處理效率,于是產(chǎn)生了tasklet機(jī)制。
Tasklet采用無(wú)差別的隊(duì)列機(jī)制,有中斷時(shí)才執(zhí)行,免去了循環(huán)查表之苦。

總結(jié)下tasklet的優(yōu)點(diǎn):
(1)無(wú)類型數(shù)量限制;
(2)效率高,無(wú)需循環(huán)查表;
(3)支持SMP機(jī)制;
五、工作隊(duì)列
前面的機(jī)制不論如何折騰,有一點(diǎn)是不會(huì)變的。它們都在中斷上下文中。什么意思?說(shuō)明它們不可掛起。而且由于是串行執(zhí)行,因此只要有一個(gè)處理時(shí)間較長(zhǎng),則會(huì)導(dǎo)致其他中斷響應(yīng)的延遲。為了完成這些不可能完成的任務(wù),于是出現(xiàn)了工作隊(duì)列。工作隊(duì)列說(shuō)白了就是一組內(nèi)核線程,作為中斷守護(hù)線程來(lái)使用。多個(gè)中斷可以放在一個(gè)線程中,也可以每個(gè)中斷分配一個(gè)線程。
工作隊(duì)列對(duì)線程作了封裝,使用起來(lái)更方便。
因?yàn)楣ぷ麝?duì)列是線程,所以我們可以使用所有可以在線程中使用的方法。
Tasklet其實(shí)也不一定是在中斷上下文中執(zhí)行,它也有可能在線程中執(zhí)行。
假如中斷數(shù)量很多,而且這些中斷都是自啟動(dòng)型的(中斷處理函數(shù)會(huì)導(dǎo)致新的中斷產(chǎn)生),則有可能cpu一直在這里執(zhí)行中斷處理函數(shù),會(huì)導(dǎo)致用戶進(jìn)程永遠(yuǎn)得不到調(diào)度時(shí)間。
為了避免這種情況,linux發(fā)現(xiàn)中斷數(shù)量過(guò)多時(shí),會(huì)把多余的中斷處理放到一個(gè)單獨(dú)的線程中去做,就是ksoftirqd線程。這樣又保證了中斷不多時(shí)的響應(yīng)速度,又保證了中斷過(guò)多時(shí)不會(huì)把用戶進(jìn)程餓死。
問(wèn)題是我們不能保證我們的tasklet或軟中斷處理函數(shù)一定會(huì)在線程中執(zhí)行,所以還是不能使用進(jìn)程才能用的一些方法,如放棄調(diào)度、長(zhǎng)延時(shí)等。
Tasklet機(jī)制分析
有了軟中斷,linux內(nèi)核為什么要引入tasklet機(jī)制呢?主要原因:
- 軟中斷的pending標(biāo)志位也就32位,一般情況是不隨意增加軟中斷處理的。
- 內(nèi)核沒(méi)有提供通用的增加軟中斷的接口。
- 軟中斷處理函數(shù)要求可重入,需要考慮到競(jìng)爭(zhēng)條件比較多,要求比較高的編程技巧。所以內(nèi)核提供了tasklet這樣的一種通用的機(jī)制。
把細(xì)節(jié)的東西說(shuō)明白,會(huì)越寫(xiě)越多。這樣做的好處是能真正理解其中的機(jī)制。但是,內(nèi)容太多的一個(gè)壞處就是難道記憶。所以先把精髓總結(jié)出來(lái)。Tasklet的特點(diǎn),也是tasklet的精髓就是:
- tasklet不能休眠
- 同一個(gè)tasklet不能在兩個(gè)CPU上同時(shí)運(yùn)行
- 不同tasklet可能在不同CPU上同時(shí)運(yùn)行
- 需要注意共享數(shù)據(jù)的保護(hù)。
軟中斷和tasklet的總結(jié)
-
軟中斷:
1、軟中斷是在編譯期間靜態(tài)分配的。
2、最多可以有32個(gè)軟中斷。
3、軟中斷不會(huì)搶占另外一個(gè)軟中斷,唯一可以搶占軟中斷的是中斷處理程序。
4、可以并發(fā)運(yùn)行在多個(gè)CPU上(即使同一類型的也可以)。所以軟中斷必須設(shè)計(jì)為可重入的函數(shù)(允許多個(gè)CPU同時(shí)操作),因此也需要使用自旋鎖來(lái)保護(hù)其數(shù)據(jù)結(jié)構(gòu)。
5、目前只有兩個(gè)子系直接使用軟中斷:網(wǎng)絡(luò)和SCSI。
6、執(zhí)行時(shí)間有:從硬件中斷代碼返回時(shí)、在ksoftirqd內(nèi)核線程中和某些顯示檢查并執(zhí)行軟中斷的代碼中。
-
tasklet:
1、tasklet是使用兩類軟中斷實(shí)現(xiàn)的:HI_SOFTIRQ和TASKLET_SOFTIRQ。本質(zhì)上沒(méi)有什么區(qū)別,只不過(guò)HI_SOFTIRQ的優(yōu)先級(jí)更高一些,建立在HI_SOFTIRQ上的tasklet會(huì)早于TASKLET_SOFTIRQ執(zhí)行。
2、可以動(dòng)態(tài)增加減少,沒(méi)有數(shù)量限制。
3、同一類的tasklet不能并發(fā)執(zhí)行。
4、不同類的tasklet可以并發(fā)執(zhí)行。
5、大部分情況下推薦使用tasklet。
Ref:
https://www.zhihu.com/question/41048032
http://www.infoq.com/cn/articles/java-interrupt-mechanism
https://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html
http://blog.jobbole.com/105524/
http://unicornx.github.io/2016/02/12/20160212-lk-drv-tasklet/