天天看點

重學計算機組成原理(十二) - 異常和中斷1 概覽2 異常:硬體、系統和應用的組合拳3 異常的分類4 異常的處理:上下文切換5 總結推薦閱讀思考參考

1 概覽

完好的程式都滿足以下特征

  • 自動運作

    我們的程式和指令都是一條條順序執行,不需要通過鍵盤或者網絡給這個程式任何輸入

  • 正常運作

    沒有遇到計算溢出之類的程式錯誤。

不過,現實的軟體世界可沒有這麼簡單

  • 程式不僅是簡單的執行指令,更多的還需要和外部的輸入輸出打交道
  • 程式在執行過程中,還會遇到各種異常情況,比如除以0、 溢出,甚至我們自己也可以讓程式抛出異常。

遇到這些情況,計算機是怎麼運轉的呢,也就是說,計算機究竟是如何處理異常的

2 異常:硬體、系統和應用的組合拳

2.1 軟體 還是 硬體 異常?

一提到異常 (Exception),可能你的第一反應就是Java中的Exception。 不過我們今天講的,并不是這些軟體開發過程中遇到的“軟體異常”

而是和硬體、系統相關 的“硬體異常”。

當然,“軟體異常”和“硬體異常”并不是業界使用的專有名詞,隻是我為了友善給你說明,和Java中軟體抛出的Exception進行的人為區分,你明白這個意思就好。

盡管,這裡我把這些硬體和系統相關的異常,叫作“硬體異常”。但是,實際上,這些異常,既有來自硬體的,也有來自軟體層面的。

比如,我們在

  • 硬體層面

    當加法器進行兩個數相加的時候,會遇到算術溢出

或者,你在玩遊戲的時候,按下鍵盤發送了一個信号給到CPU,CPU要去執行一個現有流程之外的指令,這也是 一個“異常”

同樣,來自

  • 軟體層面

    比如我們的程式進行系統調用,發起一個讀檔案的請求。這樣應用程式向系統調用發起請求的情況,一樣是通過“異常”來實作的。

2.2 異常的一生

重學計算機組成原理(十二) - 異常和中斷1 概覽2 異常:硬體、系統和應用的組合拳3 異常的分類4 異常的處理:上下文切換5 總結推薦閱讀思考參考

異常, 其實是一個硬體和軟體組合到一起的處理過程。

  • 異常的前半生

    異常的發生和捕捉,在硬體層面完成

  • 異常的後半生

    異常的處理,其實是由軟體來完成的!

2.3 異常代碼

計算機會為每一種可能會發生的異常,配置設定一個異常代碼(Exception Number)

異常代碼也叫作中斷向量(Interrupt Vector)。

異常發生的時候,通常是CPU檢測到了一個特殊的信号。

比如

  • 你按下鍵盤上的按鍵,輸入裝置就會給CPU發一個信号
  • 正在執行的指令發生了加法溢出,同樣,我們可以有一個進位溢出的信号

這些信号呢,在組成原理,一般叫發生了一個事件(Event)

CPU在檢測到事件的時候,其實也就拿到了對應的異常代碼。

這些異常代碼裡

  • I/O發出的信号的異常代碼,是由作業系統來配置設定的,也就是由軟體來設定的
  • 像加法溢出這樣的異常代碼,則是由CPU預先配置設定好的,也就是由硬體來配置設定的. 這又是另一個軟體和硬體共同組合來處理異常的過程

拿到異常代碼之後,CPU就會觸發異常處理的流程

計算機在記憶體裡,會保留一個異常表 (Exception Table)。

也叫中斷向量表(Interrupt Vector Table),好和上面的中斷向量對應起來。

這個異常表有點兒像我們在之前的GOT表,存放的是不同的異常代碼對應的異常處理程式(Exception Handler)所在的位址

2.4 異常處理程式流程

我們的CPU在拿到了異常碼後

  • 首先, 把目前的程式執行的現場,儲存到程式棧
  • 然後, 根據異常碼查詢,找到對應的異常處理程式
  • 最後, 把後續指令執行的指揮權,交給這個異常處理程式

這樣“檢測異常 => 拿到異常碼 => 再根據異常碼進行查表處理”的模式,在日常開發的過程中是很常 見的。

flowchat
st=>start: 開始
e=>end: 結束
op1=>operation: 檢測異常
op2=>operation: 拿到異常碼
op3=>operation: 再根據異常碼進行查表處理

st->op1->op2->op3
op3->e           
重學計算機組成原理(十二) - 異常和中斷1 概覽2 異常:硬體、系統和應用的組合拳3 異常的分類4 異常的處理:上下文切換5 總結推薦閱讀思考參考

比如說

Web或者App開發

通常都是前後端分離的

  • 前端應用,會向後端發起HTTP請求
  • 當後端遇到了異常,通常會給到前端一個對應的錯誤代碼
  • 前端的應用根據這個錯誤代碼,
    • 在應用層面去進行錯誤處理
    • 在不能處理的時候,它會根據錯誤代碼向使用者顯示錯誤資訊。
      重學計算機組成原理(十二) - 異常和中斷1 概覽2 異常:硬體、系統和應用的組合拳3 異常的分類4 異常的處理:上下文切換5 總結推薦閱讀思考參考

Java裡面

可以設定ExceptionHandler,來處理線程執行中的異常情況

public class LastChanceHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // do something here - log to file and upload to    
        // server/close resources/delete files...
    }
}
 
Thread.setDefaultUncaughtExceptionHandler(new LastChanceHandler());
           

使用一個線程池去運作排程任務的時候

可以指定一個異常處理程式。

對于各個線程在執行任務出現的異常情況,我們是通過異常處理程式進行處理,而不是在實際的任務代碼裡處理。

這樣,我們就把業務處理代碼就和異常處理代碼的流程分開了。

3 異常的分類

異常可以由硬體觸發,也可以由軟體觸發

3.1 中斷(Interrupt)

顧名思義,就是程式在執行到一半的時候,被打斷了。這個打斷執行的信号,來自于CPU外部的I/O裝置。

你在鍵盤上按下一個按鍵,就會對應觸發一個 相應的信号到達CPU裡面。CPU裡面某個開關的值發生了變化,也就觸發了一個中斷類型的異常。

3.2 陷阱(Trap)

程式員“故意“主動觸發的異常。就好像你在程式裡面打了一個斷點,這個斷點就是設下的一個"陷阱"。

當程式的指令執行到這個位置的時候,就掉到了這個陷阱當中。然後,對應的異常處理程式就會來處理這個"陷阱"當中的獵物。

最常見的一類陷阱,應用程式調用系統調用的時候,也就是從使用者态切換到核心态的時候。

可以用Linux下的time指令,去檢視一個程式運作實際花費的時間,裡面有在使用者态花費的時間(user time),也有在核心态發生的時間 (system time)。

應用程式通過系統調用去讀取檔案、建立程序,其實也是通過觸發一次陷阱來進行的。這是因為使用者态的應用程式沒有權限來做這些事情,需要把對應的流程轉交給有權限的異常處理程式來進行。

3.3 故障(Fault)

陷阱是我們開發程式的時候刻意觸發的異常,而故障通常不是。

比如,我們在程式執行的過程中,進行加法計算發生了溢出,其實就是故障類型的異常。

這個異常不是我們在開發的時候計劃内的,也一樣需要有對應的異常處理程式去處理。

故障和陷阱、中斷的重要差別

故障在異常程式處理完成之後,仍然回來處理目前的指 令,而不是去執行程式中的下一條指令。

因為目前的指令因為故障的原因并沒有成功執行完成。

3.4 中止(Abort)

與其說這是一種異常類型,不如說這是故障的一種特殊情況。 當CPU遇到了故障,但是恢複不過來的時候,程式就不得不中止了。

3.5小結
重學計算機組成原理(十二) - 異常和中斷1 概覽2 異常:硬體、系統和應用的組合拳3 異常的分類4 異常的處理:上下文切換5 總結推薦閱讀思考參考

中斷異常的信号來自系統外部,而不是在程式自己執行的過程中,是以我們稱之為“異步”類型的異常。

而陷阱、故障以及中止類型的異常,是在程式執行的過程中發生的,所 以我們稱之為“同步“類型的異常。

在處理異常的過程當中,無論是異步的中斷,還是同步的陷阱和故障,我們都是采用同一套處理流程,也就是上面所說的,“儲存現場、異常代碼查詢、異常處理程式調用“。

而中止類型的異常,其實是在故障類型異常的一種特殊情況。當故障發生,但是我們發現沒有異常處理程式能夠處理這種異常的情況下,程式就不得不進入中止狀态,也就是最終會退出目前的程式執行。

4 異常的處理:上下文切換

在實際的異常處理程式執行之前,CPU需要去做一次“儲存現場”的操作。這個儲存現場的操作, 和函數調用的過程非常相似。

切換到異常處理程式,就好像是去調用一個異常處理函數。指令的控制權被切換到了另外一個"函數",是以我們自然要把目前正在執行的指令去壓棧。

這樣才能在異常處理程式執行完後,重新回到目前的指令繼續往下執行。

不過,切換到異常處理程式,比起函數調用,還是要更複雜一些。原因有下面幾點

  • 異常情況往往發生在程式正常執行的預期之外,比如中斷、故障發生的時候。是以,除了本來程式壓棧要做的事情之外,還需要把CPU内目前運作程式用到的所有寄存器, 都放到棧裡面。最典型的就是條件碼寄存器裡面的内容
  • 像陷阱這樣的異常,涉及程式指令在使用者态和核心态之間的切換。對應壓棧的時候,對應的資料是壓到核心棧裡,而不是程式棧裡。
  • 像故障這樣的異常,在異常處理程式執行完成之後。從棧裡傳回出來,繼續執行的不是順序的下一條指令,而是故障發生的目前指令。因為目前指令因為故障沒有正常執行成功,必須重新去執行一次。

是以,對于異常這樣的處理流程,不像是順序執行的指令間的函數調用關系。而是更像兩個不同的獨立程序之間在CPU層面的切換,是以這個過程我們稱之為上下文切換(Context Switch)。

5 總結

計算機裡的“異常”處理流程。這裡的異常可以分成中斷、陷阱、故障、中止 這樣四種情況。這四種異常,分别對應着I/O裝置的輸入、程式主動觸發的狀态切換、異常情況下的程式出錯以及出錯之後無可挽回的退出程式。

當CPU遭遇了異常的時候,計算機就需要有相應的應對措施。CPU會通過“查表法”來解決這個問 題。在硬體層面和作業系統層面,各自定義了所有CPU可能會遇到的異常代碼,并且通過這個異 常代碼,在異常表裡面查詢相應的異常處理程式。在捕捉異常的時候,我們的硬體CPU在進行相 應的操作,而在處理異常層面,則是由作為軟體的異常處理程式進行相應的操作。

而在實際處理異常之前,計算機需要先去做一個“保留現場”的操作。有了這個操作,我們才能在異常處理完成之後,重新回到之前執行的指令序列裡面來。這個保留現場的操作,和我們之前講 解指令的函數調用很像。但是,因為“異常”和函數調用有一個很大的不同,那就是它的發生時間。函數調用的壓棧操作我們在寫程式的時候完全能夠知道,而“異常”發生的時間卻很不确定。 是以,“異常”發生的時候,我們稱之為發生了一次“上下文切換”(Context Switch)。這個時 候,除了普通需要壓棧的資料外,計算機還需要把所有寄存器資訊都存儲到棧裡面去。

推薦閱讀

關于異常和中斷,《深入了解計算機系統》的第8章“異常控制流”部分,有非常深入和充分的講解,推薦你認真閱讀一下。

思考

很多教科書和網上的文章,會把中斷分成軟中斷和硬中斷。你能用自己的話說一說,什麼是軟中 斷,什麼是硬中斷嗎?它們和我們今天說的中斷、陷阱、故障以及中止又有什麼關系呢?

歡迎留言和我分享你的疑惑和見解。你也可以把今天的内容,分享給你的朋友,和他一起學習和 進步。

參考

https://www.cnblogs.com/luoahong/p/11425628.html#top 深入了解計算機系統(第三版)