重學計算機組成原理(十二) - 異常和中斷
1 概覽
完好的程序都滿足以下特征
自動運行
我們的程序和指令都是一條條順序執行,不需要通過鍵盤或者網絡給這個程序任何輸入
正常運行
沒有遇到計算溢出之類的程序錯誤。
不過,現實的軟件世界可沒有這么簡單
程序不僅是簡單的執行指令,更多的還需要和外部的輸入輸出打交道
程序在執行過程中,還會遇到各種異常情況,比如除以0、 溢出,甚至我們自己也可以讓程序拋出異常。
遇到這些情況,計算機是怎么運轉的呢,也就是說,計算機究竟是如何處理異常的
2 異常:硬件、系統和應用的組合拳
2.1 軟件 還是 硬件 異常?
一提到異常 (Exception),可能你的第一反應就是Java中的Exception。 不過我們今天講的,并不是這些軟件開發過程中遇到的“軟件異常”
而是和硬件、系統相關 的“硬件異常”。
當然,“軟件異常”和“硬件異常”并不是業界使用的專有名詞,只是我為了方便給你說明,和Java中軟件拋出的Exception進行的人為區分,你明白這個意思就好。
盡管,這里我把這些硬件和系統相關的異常,叫作“硬件異常”。但是,實際上,這些異常,既有來自硬件的,也有來自軟件層面的。
比如,我們在
硬件層面
當加法器進行兩個數相加的時候,會遇到算術溢出
或者,你在玩游戲的時候,按下鍵盤發送了一個信號給到CPU,CPU要去執行一個現有流程之外的指令,這也是 一個“異常”
同樣,來自
軟件層面
比如我們的程序進行系統調用,發起一個讀文件的請求。這樣應用程序向系統調用發起請求的情況,一樣是通過“異常”來實現的。
2.2 異常的一生
異常, 其實是一個硬件和軟件組合到一起的處理過程。
異常的前半生
異常的發生和捕捉,在硬件層面完成
異常的后半生
異常的處理,其實是由軟件來完成的!
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在拿到了異常碼后
首先, 把當前的程序執行的現場,保存到程序棧
然后, 根據異常碼查詢,找到對應的異常處理程序
最后, 把后續指令執行的指揮權,交給這個異常處理程序
這樣“檢測異常 => 拿到異常碼 => 再根據異常碼進行查表處理”的模式,在日常開發的過程中是很常 見的。
Created with Rapha?l 2.2.0
開始
檢測異常
拿到異常碼
再根據異常碼進行查表處理
結束
比如說
Web或者App開發
通常都是前后端分離的
前端應用,會向后端發起HTTP請求
當后端遇到了異常,通常會給到前端一個對應的錯誤代碼
前端的應用根據這個錯誤代碼,
在應用層面去進行錯誤處理
在不能處理的時候,它會根據錯誤代碼向用戶顯示錯誤信息。
可以設定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());
1
2
3
4
5
6
7
8
9
10
11
可以指定一個異常處理程序。
對于各個線程在執行任務出現的異常情況,我們是通過異常處理程序進行處理,而不是在實際的任務代碼里處理。
這樣,我們就把業務處理代碼就和異常處理代碼的流程分開了。
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小結
中斷異常的信號來自系統外部,而不是在程序自己執行的過程中,所以我們稱之為“異步”類型的異常。
而陷阱、故障以及中止類型的異常,是在程序執行的過程中發生的,所 以我們稱之為“同步“類型的異常。
在處理異常的過程當中,無論是異步的中斷,還是同步的陷阱和故障,我們都是采用同一套處理流程,也就是上面所說的,“保存現場、異常代碼查詢、異常處理程序調用“。
而中止類型的異常,其實是在故障類型異常的一種特殊情況。當故障發生,但是我們發現沒有異常處理程序能夠處理這種異常的情況下,程序就不得不進入中止狀態,也就是最終會退出當前的程序執行。
4 異常的處理:上下文切換
在實際的異常處理程序執行之前,CPU需要去做一次“保存現場”的操作。這個保存現場的操作, 和函數調用的過程非常相似。
切換到異常處理程序,就好像是去調用一個異常處理函數。指令的控制權被切換到了另外一個"函數",所以我們自然要把當前正在執行的指令去壓棧。
這樣才能在異常處理程序執行完后,重新回到當前的指令繼續往下執行。
不過,切換到異常處理程序,比起函數調用,還是要更復雜一些。原因有下面幾點
異常情況往往發生在程序正常執行的預期之外,比如中斷、故障發生的時候。所以,除了本來程序壓棧要做的事情之外,還需要把CPU內當前運行程序用到的所有寄存器, 都放到棧里面。最典型的就是條件碼寄存器里面的內容
像陷阱這樣的異常,涉及程序指令在用戶態和內核態之間的切換。對應壓棧的時候,對應的數據是壓到內核棧里,而不是程序棧里。
像故障這樣的異常,在異常處理程序執行完成之后。從棧里返回出來,繼續執行的不是順序的下一條指令,而是故障發生的當前指令。因為當前指令因為故障沒有正常執行成功,必須重新去執行一次。
所以,對于異常這樣的處理流程,不像是順序執行的指令間的函數調用關系。而是更像兩個不同的獨立進程之間在CPU層面的切換,所以這個過程我們稱之為上下文切換(Context Switch)。
5 總結
計算機里的“異常”處理流程。這里的異常可以分成中斷、陷阱、故障、中止 這樣四種情況。這四種異常,分別對應著I/O設備的輸入、程序主動觸發的狀態切換、異常情況下的程序出錯以及出錯之后無可挽回的退出程序。
當CPU遭遇了異常的時候,計算機就需要有相應的應對措施。CPU會通過“查表法”來解決這個問 題。在硬件層面和操作系統層面,各自定義了所有CPU可能會遇到的異常代碼,并且通過這個異 常代碼,在異常表里面查詢相應的異常處理程序。在捕捉異常的時候,我們的硬件CPU在進行相 應的操作,而在處理異常層面,則是由作為軟件的異常處理程序進行相應的操作。
而在實際處理異常之前,計算機需要先去做一個“保留現場”的操作。有了這個操作,我們才能在異常處理完成之后,重新回到之前執行的指令序列里面來。這個保留現場的操作,和我們之前講 解指令的函數調用很像。但是,因為“異常”和函數調用有一個很大的不同,那就是它的發生時間。函數調用的壓棧操作我們在寫程序的時候完全能夠知道,而“異常”發生的時間卻很不確定。 所以,“異常”發生的時候,我們稱之為發生了一次“上下文切換”(Context Switch)。這個時 候,除了普通需要壓棧的數據外,計算機還需要把所有寄存器信息都存儲到棧里面去。
推薦閱讀
關于異常和中斷,《深入理解計算機系統》的第8章“異常控制流”部分,有非常深入和充分的講解,推薦你認真閱讀一下。
再續中斷分類
軟中斷和硬中斷
硬中斷
由硬件發出或產生的中斷稱為硬中斷,按硬中斷事件的來源和實現手段可將中斷劃分為外中斷和內中斷:
外中斷
又稱為中斷或異步中斷,是指來自處理器以外的中斷信號,包括時鐘中斷、鍵盤中斷、外部設備中斷等。外中斷又分為可屏蔽中斷和不可屏蔽中斷,各個中斷具有不同的優先級,表示事件的緊急程度,在處理高一級中斷時,往往會部分或全部屏蔽低等級中斷。
內中斷
又稱為異常或同步中斷(產生時必須考慮與處理器時鐘同步),是指 來自處理器內部的中斷信號,通常是由于程序執行過程中,發現與當前指令關聯的、不正常的或錯誤的事件。內中斷可以細分為:
訪管中斷,由執行系統調用而引起的
硬件故障中斷,如電源失效、總線超時等
程序性中斷,如非法操作、地址越界、除數為0和浮點溢出等。
軟中斷
是一條CPU指令,用以自陷一個中斷。由于 軟中斷指令通常要運行一個切換CPU至內核態(Kernel Mode/Ring 0)的子例程,它常被用作實現系統調用(System call)。
處理器通常含有一個內部中斷屏蔽位,并允許通過軟件來設定。一旦被設定,所有外部中斷都將被系統忽略。這個屏蔽位的訪問速度顯然快于中斷控制器上的中斷屏蔽寄存器,因此可提供更快速地中斷屏蔽控制。
中斷風暴(interrupt storm)
中斷盡管可以提高計算機處理性能,但 過于密集的中斷請求/響應反而會影響系統性能。這類情形被稱作
參考
https://www.cnblogs.com/luoahong/p/11425628.html#top
深入理解計算機系統(第三版)
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。