Bran的內核開發教程(bkerndev)-07 中斷描述符表(IDT)
- 2019 年 11 月 4 日
- 筆記
中斷描述符表(IDT)
中斷描述符表(IDT)用於告訴處理器調用哪個中斷服務程式(ISR)來處理異常或彙編中的"int"指令。每當設備完成請求並需要服務事, 中斷請求也會調用IDT條目。異常和ISR將在下一節進行詳細的說明。
每一項IDT都與GDT相似, 兩者都有一個基地址, 一個訪問標誌, 而且都長64bits。這兩類描述符表最主要的區別在於這些欄位的含義: 在IDT中的基地址是中斷時應調用的ISR的地址。IDT也沒有邊界(limit), 而是需要一個指定的段, 該段與給定的ISR所在段相同。這讓處理器即使處於不同級別的Ring中, 在發生中斷時也能將控制權交給內核。
IDT條目的訪問標誌位也和GDT相似。需要一個欄位說明描述符是否存在。描述符特權級別(DPL)用於說明哪個Ring是給定中斷允許使用的最高級別。主要區別在於訪問位元組的低5位始終為二進位01110, 也就是十進位中的14。下面這張表讓你更好地理解IDT訪問位元組。
- P – 段是否存在? (1 = Yes)
- DPL – 哪個Ring (0~3)
在你的自製內核目錄下創建一個新文件"idt.c"。編輯"build.bat"文件, 添加新的一行gcc命令編譯"idt.c"。最後添加"idt.o"到鏈接文件列表中。"idt.c"中將會聲明一個結構體用於定義每個IDT條目, 和一個用於載入IDT的特殊IDT指針結構體(類似於載入GDT, 但工作量更少), 並聲明一個256大小的IDT數組: 這將成為我們的IDT。
idt.c
#include <system.h> /* 定義IDT條目 */ struct idt_entry { unsigned short base_lo; unsigned short sel; /* 我們的內核段在這裡 */ unsigned char always0; /* 這將始終為0! */ unsigned char flags; /* 根據上表進行設置! */ unsigned short base_hi; } __attribute__((packed)); // 不進行對齊優化 struct idt_ptr { unsigned short limit; unsigned int base; } __attribute__((packed)); /* 聲明一個有256個條目的IDT, 儘管在本教程中我們只會使用前32個。 * 剩下的存在一點小陷阱, 如果任何未定義的IDT被集中, * 將會導致"未處理的中斷(Unhandled Interrupt)"異常, * 描述符的"presence"位如果為0, 將生成"未處理的中斷"異常。*/ struct idt_entry idt[256]; struct idt_ptr idtp; /*該函數在"start.asm"中定義, 用於載入我們的IDT */ extern void idt_load();
idt_load
函數的函數定義在其他文件中, 和gdt_flash
一樣是使用彙編語言編寫的。我們之後將在idt_install
中使用創建的IDT指針來調用lidt
彙編操作碼。打開"start.asm"文件, 把下面幾行添加到_gdt_flush
的re
後面。
start.asm
; 載入idtp指針所指的IDT到處理器中 ; 這在C文件中聲明為"extern void idt_load();" global _idt_load extern _idtp _idt_load: lidt [_idtp] ret
設置IDT條目比GDT簡單得多。我們又一個idt_set_gate
函數用於接收IDT索引號、中斷服務程式基地址、內核程式碼段以及上表中提到的訪問標誌。同樣, 我們又一個idt_install
函數用來設置IDT指針, 並將IDT初始化為默認清除狀態。最後, 我們將通過調用idt_load
來載入IDT。在載入IDT後, 我們可以隨時將ISR添加到IDT中。本教程將在下一節介紹ISR。下面是"idt.c"文件的剩餘部分, 請嘗試弄明白idt_set_gate
函數, 它其實很簡單。
idt.c
/* 使用該函數來設置每項IDT*/ void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags) { /* 該函數的程式碼將留給你來實現: * 將參數"base"分為高16位和低16位, * 將它們存儲在idt[num].base_hi和idt[num].base_lo中 * 剩下的需要設置idt[num]的其他成員的值 */ } /* 安裝IDT */ void idt_install() { /* 設置IDT指針 */ idtp.limit = (sizeof (struct idt_entry) * 256) - 1; idtp.base = &idt; /* 清空整個IDT, 並初始化該片區域為0 */ memset(&idt, 0, sizeof(struct idt_entry) * 256); /* 使用idt_set_gate將ISR添加到IDT中 */ /* 將處理器的內部暫存器指向新的IDT */ idt_load(); }
最後, 確保在"system.h"中添加idt_set_gate
和idt_install
作為函數原型, 因為我們需要從其他文件(例如"main.c")中調用這些函數。在main()
函數調用了gdt_install
後立即調用idt_install
。這是你應該可以成功編譯你的內核。嘗試使用一下你的新內核, 在進行除零之類的非法操作時, 電腦將重置。我們可以通過在新的IDT中安裝ISR來不活這些異常。
如果你不知道怎麼編寫idt_set_gate
, 則可以在此處找到本教程的解決方案。
idt.c
void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags) { /* 中斷程式的基地址 */ idt[num].base_lo = (base & 0xFFFF); idt[num].base_hi = (base >> 16) & 0xFFFF; /* 該IDT使用的段或區域以及訪問標誌位將在此設置 */ idt[num].sel = sel; idt[num].always0 = 0; idt[num].flags = flags; }