聊聊中斷機制~

  • 2020 年 5 月 17 日
  • 筆記

什麼是中斷


中斷其實是一種「中斷」事件,中斷具體代表什麼意思需要考慮它所處的上下文環境參照對象是誰。考慮事件,我們可以簡單把中斷抽象為這樣一種模型:

當我們分析某種中斷事件時,我們需要搞清楚這四個對象:

中斷源

  • 中斷源是誰
  • 中斷源在什麼條件下觸發中斷
  • 中斷源如何觸發

中斷訊號

  • 訊號具體指的是什麼
  • 訊號是否需要存儲
  • 如何存儲

中斷控制器

  • 中斷訊號的管理

比如說中斷源發送的訊號是否屏蔽,訊號是否可被中斷處理器重複處理,訊號的處理是否有優先順序…

中斷處理器

  • 如何獲取到訊號
  • 拿到訊號做什麼樣的操作
  • 處理完訊號後做什麼樣的操作

在實際的中斷事件中,並不一定剛好有上面提到的這四類對象,可能更複雜可能更簡單化。但是當我們考慮中斷事件時,需要明確應該有類似功能的「對象」承擔這這樣的邏輯。

下面我們主要圍繞作業系統的中斷機制Java的中斷機制如何設計一個非同步執行緒間的中斷系統這三部分簡單探討下。

作業系統的中斷機制


與作業系統有關的中斷,通常是指:程式在執行過程中,遇到急需處理的事件時,暫時中止CPU上現行程式的運行, 轉去執行相應的事件處理程式,待處理完成 後再返回原程式被中斷處或調度其他程式執行的過程。

按照中斷事件本身的不同,可以劃分為處理器之外的中斷事件異常,系統異常

處理器之外的中斷事件

指由外圍設備發出的訊號引起的,與當前運行指令無關的中斷事件。示意圖如下:

我們分別以上述四個對象來看:

  • 中斷源

中斷源:外部設備,如印表機,鍵盤,滑鼠等。

觸發條件:如外圍設備報告I/O狀態的I/O中斷;外圍設備發出的對應訊號中斷,如時鐘中斷,鍵盤/滑鼠對應訊號的中斷,關機/重啟動中斷等。

觸發方式:由外部設備向中斷控制器發出中斷請求IRQ。

  • 中斷訊號

也就是說中斷源通知給中斷控制器的是什麼。

可以是通過一條訊號線上產生特定的電平(利用高低電平表示是否中斷兩種狀態),也可以在匯流排上發送特定消息或者消息序列,也可以是在中斷暫存器中設置已發生的中斷狀態等。

  • 中斷控制器

CPU中的一個控制部件,包括 中斷控制邏輯線路和中斷暫存器。負責中斷的發現和響應。

也就是說負責檢查中斷暫存器中的中斷訊號,當發現中斷時讓CPU切換當前進程程式,去處理中斷程式。響應示意圖如下:

  • 中斷處理器

指的是CPU接收到不同的中斷訊號該怎麼處理。包括「中斷處理過程」和「恢復正常操作」兩部分。

1.中斷處理過程

首先CPU需要將當前運行進程的上下文保存,從中斷進程中分析PSW,確定對應的中斷源和執行對應的中斷處理程式。

小貼士:PSW(Program Status Word): 是指在電腦中,一段包含被作業系統使用的程式狀態資訊的記憶體或硬體區域。一般用一個專門的暫存器來指示處理器狀態。可以理解為我們上面提到的中斷訊號存儲裝置.

2.恢復正常操作

當中斷程式執行完畢,接下來執行哪個進程由進程調度決定,由調度策略決定是否調度到中斷執行前的進程。

較為完整的中斷響應流程圖如下:

異常系統異常 這兩類中斷事件主要屬於處理器執行特定的指令引起的中斷事件。和上述硬體外圍設備引起的中斷事件的中斷源不同,中斷的發起,控制和處理主要是由作業系統的指令邏輯和線路來承擔。是一種同步的處理操作,而外部中斷是由外部設備發起,是一種非同步的處理操作。下面我們簡要介紹下。

異常

異常指當前運行指令引起的中斷事件。包括錯誤情況引起的故障,如除零算數錯誤,缺頁異常;也包括不可恢復的致命錯誤導致的終止,通常是一些硬體錯誤。

  • 異常的處理

對於故障的處理,根據故障是否能夠被恢復,故障處理程式要麼重新執行引起故障的指令,要麼終止。

對於終止的處理,處理程式將控制返回給一個abort常式,該常式會終止這個應用程式。

系統異常

系統異常指執行陷入指令而觸發系統調用引起的中斷事件,如請求設備、請求I/O、創建進程等。

  • 系統調用的處理

這種有意的異常,稱為陷阱處理。處理完成後陷阱程式會將控制返回給應用程式控制流的下一條指令。

總結一下,作業系統的中斷類別行為如下:

好了,大頭總算完了。因為小姐姐主要是Java碼農,下面將主要介紹和Java相關的中斷語義是什麼。

Java的中斷機制


理解了上面作業系統的中斷之後,Java的中斷機制就很easy了 😄

Java中斷指的是A執行緒發送中斷訊號給B執行緒,B執行緒再根據自己當前執行程式中的中斷處理邏輯決定如何響應。嗯,就這麼簡單~

我們來稍微分析一下中斷事件中的「四個對象」:

  • 中斷源

中斷源:A執行緒

中斷觸發條件:A執行緒說了算

中斷源觸發方式:A執行緒中調用threadB#interrupt()方法.

實現機制也不難,扯淡之前我們先思考兩個問題:

問:

問題1: 執行緒之間如何通訊,A執行緒的中斷訊號怎麼才能傳給執行緒B?

問題2: 執行緒的狀態有Running,Blocked,Waiting等,當執行緒B處在不同的狀態下,如何響應中斷訊號?

答:

問題1:這種情況下執行緒之間通訊用共享記憶體就可以了。只需要給每個執行緒都設置一個中斷標示位, 這樣A執行緒中調用threadB#interrupt()方法,實際操作是把B執行緒的中斷標示位設置為true。訊號就算傳遞過去了

問題2:當B執行緒處於非阻塞狀態時,B執行緒可以在自己需要處理中斷邏輯的地方判斷中斷標示位是否為true,就可以響應處理中斷。

但是當B執行緒處於阻塞狀態時,這特么怎麼查自己的中斷標示位啊?

JVM幫幫忙,當B執行緒阻塞在Object#wait(),Thread#join(),Thread#sleep(),實現了InterruptibleChannel介面的IO操作 和Selector介面的select()這些操作時,JVM會讓B執行緒馬上拋出異常或被喚醒,從而讓B執行緒可以選擇是否響應中斷。

因為是Java實現的中斷機制,中斷標示位的設置也是JVM幫做的。

  • 中斷訊號

訊號:執行緒的中斷標示位。

存儲方式:JVM說了算。

  • 中斷控制器

JVM控制了訊號的存儲和讓執行緒B及時喚醒。
執行緒B控制了自己的中斷響應邏輯,何時響應,如何響應。

  • 中斷處理器

獲取訊號:B執行緒可通過調用threadB#isInterrupted()方法得知自己是否被中斷,也就是通過自己主動拉取訊號(poll方式)。

如何處理訊號:B執行緒說了算。

處理完訊號後做什麼:B執行緒說了算。

Java的執行緒中斷機制設計的比較靈活,使用者可以決定中斷處理的較多事情。

總結下Java中和中斷有關的方法:

在JDK中,執行緒池的ThreadPoolExecutor#shutdownNow()方法就是調用workers執行緒數組中每個worker執行緒的interrupt()方法來關閉執行緒池。

這樣暴力關閉執行緒會存在一個問題,執行緒池並不知道worker執行緒的中斷執行情況,如果worker執行緒忽略了中斷訊號,那可能導致當前任務還在執行,發生意想不到的結果。

設計一個非同步執行緒間的中斷系統


我們再來看Java的中斷機制,它其實只是提供了A執行緒給B執行緒發送中斷訊號。

  • A執行緒並不能知道B執行緒的中斷處理結果。
  • 如果A執行緒拿不到B執行緒的thread對象時,也就沒法發送中斷訊號。

考慮這麼一種場景:
當我們執行一個大任務Task1時,它太大了。我們把它分為Task2Task3,丟進執行緒池中處理。它們同樣很大,我們把他們分別分為Task4Task5Task6Task7,同樣丟進執行緒池中處理。

如果此時我們想取消task1的執行,如何保證圖中所有的worker都成功取消對應task的執行?

  • 需求分析

當我們取消task1時,想要做的是取消所有task程式的繼續運行,並且能夠獲得所有task程式的取消結果

為什麼要強調task程式呢?因為worker可能並不是只為一個task工作啊..比如task2的worker,它把task4和task5丟進執行緒池,就算完事了。如果我們把取消task1變為取消task1的worker執行緒,可能會導致worker執行緒當前運行的非task1程式的失敗。

我們不太容易知道所有task程式當前運行的執行緒,我們還需要知道所有task程式的運行結果。

  • 設計思路

只用Java的中斷機制是滿足不了我們的需求的,但是我們可以借鑒它的思路:

1.它用中斷標示位記錄執行緒是否應該中斷

2.當執行緒阻塞時可以拋出異常

我們這裡要終止的是所有task程式的執行,所以我們需要設計與task 強綁定的中斷標示位,可以有未中斷,中斷中,中斷成功中斷失敗四種 狀態。為了讓所有的執行緒都可以訪問到,定義成全局共享變數就可以。

中斷源和中斷處理器之間通過task的中斷標示位來通訊就可以。如果運行task程式的執行緒一直在阻塞,怎麼喚醒它讓它判斷中斷狀態 呢?

對於我們這個場景,我們很難知道當前運行task程式的阻塞執行緒是誰。。能做的只是多安插中斷判斷點,這樣當阻塞執行緒醒來後,再次判斷task 的中斷標示位,就可以響應中斷了。

另:

喚醒一個執行緒只有Java的中斷機制可以做,但是如果當前worker不是你能管理的執行緒池,那麼它的中斷處理邏輯就控制不了。

如果你能控制運行task的所有worker,而且worker在執行task時是同步獲得結果的。那麼可以結合與task強綁定的中斷標示位Java中斷機制來做,這裡前者的作用更多是充當獲取到任務的中斷結果的作用。

後記


  • 小姐姐覺得像是「事件處理」這種場景在執行緒池,消息中間件,流式處理等很多地方有共通之處,比如說:如何保證事件的exactly once,推拉模型,調度等等。

  • 在寫這篇文章時,特別是作業系統的中斷機制,小姐姐也是現學現賣,並且參考了資料大部分內容。文章中有理解錯誤或者難懂的地方還請小夥伴幫我指出,一起交流進步。

  • 最後的技術部分討論「如何設計一個非同步執行緒間的中斷系統」,這是小姐姐目前工作中遇到的一個問題。這個問題和任務調度組件的取消任務很相似,只是我們目前還沒有用任務調度組件管理起所有的任務工作執行緒。小夥伴有更好方案的也請告知小姐姐。


最後,聊一件生活中的小事~

近來小姐姐發現了B站的一個寶藏優(U)批(P)主:天真的和感傷的小說家。最近的更新影片他聊到了余華的《活著》,原來當年讓小姐姐哭的稀里嘩啦的著作裡面還有這麼一個段子啊,模仿著說給你聽 😄

一一進來了順手點了個關注。22君 看完忍不住送了個在看,33醬 覺得這麼贊的小姐姐怎麼著也得幫分享到朋友圈吧。66君醬 我看到你讚賞了,來就來了還那麼客氣幹嘛~

證明你不是一個人,一連二連三連全連一下下,救救小姐姐吧:)

參考資料:
[1].//www.icourse163.org/course/NJU-1001571004

[2].《深入理解電腦系統》

[3].//www.zhihu.com/question/47862508/answer/110694813

[4].//zhuanlan.zhihu.com/p/26524241