作業系統實現-外中斷
部落格網址:www.shicoder.top
微信:18223081347
歡迎加群聊天 :452380935
這一次我們來對中斷中的外中斷進行講解,先給下中斷的分類和中斷號分配把。

| 中斷號 | 分類 |
|---|---|
| 0x0-0x20 | 異常和非屏蔽中斷 |
| 0x20-0x30 | I/O設備引發的可屏蔽中斷,比如鍵盤、時鐘等外中斷 |
| 0x30-0xff | 軟中斷,比如常說的int 80系統調用 |
前面對異常進行了講解,這次對外中斷進行說明。我們下面以時鐘中斷舉例,首先我們要知道的是,時鐘中斷是作業系統自己發生的,比如我們在執行一段程式碼時候,可能不知道啥時候,就會觸發時鐘中斷,我們為了體現觸發了時鐘中斷,我們就列印一句話就行。
同樣我們給每個外中斷號定義一個入口函數
INTERRUPT_HANDLER 0x20, 0; clock 時鐘中斷
INTERRUPT_HANDLER 0x21, 0; 鍵盤中斷
INTERRUPT_HANDLER 0x22, 0
INTERRUPT_HANDLER 0x23, 0
INTERRUPT_HANDLER 0x24, 0
INTERRUPT_HANDLER 0x25, 0
INTERRUPT_HANDLER 0x26, 0
INTERRUPT_HANDLER 0x27, 0
INTERRUPT_HANDLER 0x28, 0
INTERRUPT_HANDLER 0x29, 0
INTERRUPT_HANDLER 0x2a, 0
INTERRUPT_HANDLER 0x2b, 0
INTERRUPT_HANDLER 0x2c, 0
INTERRUPT_HANDLER 0x2d, 0
INTERRUPT_HANDLER 0x2e, 0
INTERRUPT_HANDLER 0x2f, 0
在這個函數中,同樣設置一個具體的函數
void idt_init()
{
// 初始化ENTRY_SIZE個中斷處理函數
for (size_t i = 0; i < ENTRY_SIZE; i++)
{
gate_t *gate = &idt[i];
// gate->offset0 = (u32)interrupt_handler & 0xffff;
// gate->offset1 = ((u32)interrupt_handler >> 16) & 0xffff;
handler_t handler = handler_entry_table[i];
gate->offset0 = (u32)handler & 0xffff;
gate->offset1 = ((u32)handler >> 16) & 0xffff;
gate->selector = 1 << 3; // 程式碼段
gate->reserved = 0; // 保留不用
gate->type = 0b1110; // 中斷門
gate->segment = 0; // 系統段
gate->DPL = 0; // 內核態
gate->present = 1; // 有效
}
// 異常的具體處理函數
for (size_t i = 0; i < 0x20; i++)
{
handler_table[i] = exception_handler;
}
// 外中斷的具體處理函數
for (size_t i = 0x20; i < ENTRY_SIZE; i++)
{
handler_table[i] = default_handler;
}
idt_ptr.base = (u32)idt;
idt_ptr.limit = sizeof(idt) - 1;
asm volatile("lidt idt_ptr\n");
}
void default_handler(int vector)
{
send_eoi(vector);
LOGK("[%d] default interrupt called %d...\n", vector, counter++);
}
注意我們有一個send_eoi函數,這是發送結束中斷的函數,因為我們比如時鐘中斷產生,那麼首先是結束中斷,然後才是對其進行處理,不然後續就不會再次發生中斷,其程式碼如下
void send_eoi(int vector)
{
if (vector >= 0x20 && vector < 0x28)
{
outb(PIC_M_CTRL, PIC_EOI);
}
if (vector >= 0x28 && vector < 0x30)
{
outb(PIC_M_CTRL, PIC_EOI);
outb(PIC_S_CTRL, PIC_EOI);
}
}
最後就是main函數的編寫
void kernel_init()
{
console_init();
gdt_init();
interrupt_init();
// asm volatile(
// "sti\n"
// "movl %eax, %eax\n");
asm volatile("sti");
u32 counter = 0;
while (true)
{
DEBUGK("looping in kernel init %d...\n", counter++);
delay(100000000);
}
}
其中最主要的就是asm volatile("sti"),這行表示打開中斷,否則不會觸發,還有一個關鍵,怎麼讓cpu知道是時鐘中斷呢,這就要中斷控制器進行設置,下面是設置程式碼
// 初始化中斷控制器
void pic_init()
{
outb(PIC_M_CTRL, 0b00010001); // ICW1: 邊沿觸發, 級聯 8259, 需要ICW4.
outb(PIC_M_DATA, 0x20); // ICW2: 起始埠號 0x20
outb(PIC_M_DATA, 0b00000100); // ICW3: IR2接從片.
outb(PIC_M_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI
outb(PIC_S_CTRL, 0b00010001); // ICW1: 邊沿觸發, 級聯 8259, 需要ICW4.
outb(PIC_S_DATA, 0x28); // ICW2: 起始埠號 0x28
outb(PIC_S_DATA, 2); // ICW3: 設置從片連接到主片的 IR2 引腳
outb(PIC_S_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI
outb(PIC_M_DATA, 0b11111110); // 關閉所有中斷,打開時鐘中斷
outb(PIC_S_DATA, 0b11111111); // 關閉所有中斷
}
主要看最後兩行,設置打開哪些中斷,下面是支援的中斷圖,可以看到有支援15個中斷,上面的outb(PIC_M_DATA, 0b11111110)表示打開時鐘中斷,當值為0,打開中斷。

下面是實驗的結果圖,第一張圖是沒有設置send_eoi,可以看到只接收到了一次中斷

下面是設置了send_eoi。

我們下面來嘗試下鍵盤中斷,將outb(PIC_M_DATA, 0b11111110);設置為outb(PIC_M_DATA, 0b11111101);,開啟程式,我們按下鍵盤,可以看到,當按下鍵盤觸發,但是我們再次按下,卻發現沒有用,這是因為針對鍵盤中斷,我們要編寫對應的處理函數,比如處理收到的字元,然後編寫對應的中斷結束函數,才能再次接受中斷


