作業系統實現-外中斷

部落格網址:www.shicoder.top
微信:18223081347
歡迎加群聊天 :452380935

這一次我們來對中斷中的外中斷進行講解,先給下中斷的分類和中斷號分配把。

image-20220516124531798

中斷號 分類
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,打開中斷。

image-20220516144911457

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

image-20220516125953124

下面是設置了send_eoi。

image-20220516114700444

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

image-20220516145301442