操作系統(tǒng)是如何一步步發(fā)明中斷機制的?
系統(tǒng)需要頻繁地與磁帶機和打印機等外部設(shè)備交互。然而,這些設(shè)備的響應(yīng)速度遠(yuǎn)遠(yuǎn)低于CPU的處理速度。例如,磁帶機讀取一個數(shù)據(jù)塊需要約100毫秒,而打印機打印一行數(shù)據(jù)更是需要超過600毫秒。
在等待設(shè)備響應(yīng)的過程中,CPU只能不斷地查詢設(shè)備狀態(tài),就像這樣:
int poll_count = 0;
// 輪詢等待打印機就緒
while (1) {
poll_count++;
if (check_printer_status() == PRINTER_READY) {
send_to_printer(print_data);
break;
}
}
這就是所謂輪詢,這個示例程序通過不斷輪詢打印機狀態(tài)來等待設(shè)備就緒,只要打印機不READY你就沒有辦法跳出這個while循環(huán),這導(dǎo)致大量的計算資源被浪費。
靈感時刻
1954年IBM 704的出現(xiàn)給了你靈感,因為這臺機器上出現(xiàn)了一種有趣的特性。
IBM 704 具有一個溢出標(biāo)志位(Overflow Flag, OV),它會在某些算術(shù)運算(如加法、乘法等)導(dǎo)致溢出時被設(shè)置,程序員可以手動檢查這個標(biāo)志位,并根據(jù)需要進行錯誤處理:
ADD MQ // AC = AC + MQ,可能導(dǎo)致溢出
TOV ERROR // 如果 OV 標(biāo)志為 1,則跳轉(zhuǎn)到 ERROR 處理異常
TRA CONTINUE // 否則繼續(xù)執(zhí)行程序
ERROR
// 錯誤處理指令
CONTINUE
// 繼續(xù)執(zhí)行其他指令
你看到后想了一下,為什么要程序員自己手寫匯編來檢查異常呢,實現(xiàn)在CPU硬件層面就好了,出現(xiàn)A錯誤就跳轉(zhuǎn)到X代碼,出現(xiàn)B錯誤就跳轉(zhuǎn)到Y(jié)代碼等等,這樣程序員只需要編寫正常的處理邏輯就好。
以程序除0錯誤為例:
void test_division() {
int a = 10;
int b = 0; // 除數(shù)為零
int result = a / b; // CPU立即觸發(fā)異常處理
// 這行代碼永遠(yuǎn)不會執(zhí)行
printf("結(jié)果是: %d\n", result);
}
當(dāng)CPU執(zhí)行到除法操作時,它能夠立即檢測到除數(shù)為零的情況,并自動跳轉(zhuǎn)到異常處理程序(提前定義好的),而不是等待程序員自己檢查除數(shù)是否為零。
中斷的發(fā)明
這種機制給你帶了新的啟示:實際上這相當(dāng)于軟件出現(xiàn)異常后可以通知CPU去執(zhí)行一段異常處理邏輯,而且整個過程非常絲滑,因為異常處理邏輯是提前定義好的,CPU能根據(jù)異常類型去執(zhí)行不同的異常處理邏輯。
到這里你靈光乍現(xiàn),既然軟件能通知CPU那么外部設(shè)備顯然也可以通知CPU。
圖片
可以把上述機制應(yīng)用在外部設(shè)備上,為此你進行了如下設(shè)計:
- 硬件層面:外部設(shè)備通過特定的信號線連接到CPU
- 信號觸發(fā):設(shè)備就緒時產(chǎn)生電平變化
- CPU響應(yīng):檢測到信號后立即切換到處理程序
- 任務(wù)恢復(fù):處理完成后返回原程序繼續(xù)執(zhí)行
這種設(shè)計可以讓CPU不再需要主動查詢設(shè)備狀態(tài),而是由設(shè)備在就緒時主動通知CPU,從而大大減少了CPU資源的浪費,到這里你發(fā)明了中斷機制。
圖片
中斷的實現(xiàn)
現(xiàn)在CPU不但能響應(yīng)軟件異常也能響應(yīng)外部設(shè)備,這些統(tǒng)統(tǒng)被稱為中斷。
只不過來自軟件的就被稱為軟中斷,比如除零錯誤、內(nèi)存訪問違規(guī)、系統(tǒng)調(diào)用等;來自硬件的就被稱之為硬中斷,比如I/O設(shè)備中斷(如打印機、磁盤完成操作)、時鐘中斷等。
你在自己實現(xiàn)的內(nèi)核中定義了這些中斷類型:
// 中斷類型定義
typedefenum {
// 硬件中斷
INT_PRINTER = 0, // 打印機中斷
INT_DISK = 1, // 磁盤中斷
INT_TIMER = 2, // 時鐘中斷
INT_KEYBOARD = 3, // 鍵盤中斷
// 軟件中斷
INT_DIVIDE_BY_ZERO = 4, // 除零錯誤
INT_PAGE_FAULT = 5, // 頁面錯誤
INT_SYSTEM_CALL = 6, // 系統(tǒng)調(diào)用
MAX_INTERRUPT_TYPE = 7
} InterruptType;
除此之外你還需要實現(xiàn)中斷處理函數(shù),中斷處理函數(shù)應(yīng)該能處理所有類型的中斷,其本質(zhì)就是一個函數(shù)數(shù)組,你將其命名為中斷向量表:
// 中斷處理函數(shù)的類型定義
typedef void (*InterruptHandler)(void);
// 中斷向量表結(jié)構(gòu)
typedef struct {
InterruptHandler handlers[MAX_INTERRUPT_TYPE];
bool enabled[MAX_INTERRUPT_TYPE]; // 中斷使能狀態(tài)
} InterruptVectorTable;
從其定義可以看到:
- 中斷向量表是一個存儲中斷號與對應(yīng)中斷處理程序入口地址映射的表格。
- 每個中斷號對應(yīng)一個特定的事件(如硬件中斷、系統(tǒng)調(diào)用、異常等),中斷向量表中的每個條目通常包含:中斷處理程序的入口地址、可能還包括其他信息(如中斷優(yōu)先級、狀態(tài)標(biāo)志等)。
當(dāng)發(fā)生中斷時,CPU使用中斷號作為索引,查找中斷向量表中的對應(yīng)條目,從而獲取中斷處理程序的入口地址,其本質(zhì)就是:
void handle_interrupt(InterruptVectorTable* ivt, InterruptType type) {
...
ivt->handlers[type]();
...
}
現(xiàn)在CPU不再需要一遍遍檢查設(shè)備狀態(tài)而是可以專注于執(zhí)行正常任務(wù)的機器指令,當(dāng)外部設(shè)備需要CPU關(guān)注時發(fā)起中斷信號,然后CPU將跳轉(zhuǎn)到提前定義好的中斷處理函數(shù)去執(zhí)行。
現(xiàn)在你應(yīng)該對操作系統(tǒng)的中斷機制有所了解了吧。