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_flushre後面。

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_gateidt_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;  }