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