因爲中斷這塊的知識和代碼都佔較大篇幅,所以分紅兩章來說,上一講 【自制操做系統08】中斷 講述了中斷的理論知識,本講開始上代碼html
爲了讓你們清楚目前的程序進度,畫了到目前爲止的程序流程圖,以下。git
右半部分的時序圖,就是咱們今天要作作的事情,其實一句話就是:初始化中斷描述符表,其中中斷例程很是簡單,只是簡單地將中斷向量號輸出在屏幕上編程
主要代碼數組
1 #include "print.h" 2 #include "init.h" 3 void main(void){ 4 put_str("I am kernel\n"); 5 init_all(); 6 asm volatile("sti"); 7 while(1); 8 }
1 #include "init.h" 2 #include "print.h" 3 #include "interrupt.h" 4 5 // 負責初始化全部模塊 6 void init_all() { 7 put_str("init_all\n"); 8 idt_init(); 9 }
1 #include "interrupt.h" 2 #include "stdint.h" 3 #include "global.h" 4 #include "io.h" 5 6 7 #define PIC_M_CTRL 0x20 //主片控制端口 8 #define PIC_M_DATA 0x21 //主片數據端口 9 #define PIC_S_CTRL 0xa0 //從片控制端口 10 #define PIC_S_DATA 0xa1 //從片數據端口 11 12 #define IDT_DESC_CNT 0x21 //目前總共支持的中斷數 13 14 // 中斷門描述符結構體 15 struct gate_desc{ 16 uint16_t func_offset_low_word; 17 uint16_t selector; 18 uint8_t dcount; 19 uint8_t attribute; 20 uint16_t func_offset_high_word; 21 }; 22 23 // 靜態函數聲明,非必須 24 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function); 25 // 中斷門描述符表的數組 26 static struct gate_desc idt[IDT_DESC_CNT]; 27 // 用於保存異常名 28 char* intr_name[IDT_DESC_CNT]; 29 // 定義中斷處理程序數組,在kernel.asm中定義的intrXXentry。只是中斷處理程序的入口,最終調用idt_table中的處理程序 30 intr_handler idt_table[IDT_DESC_CNT]; 31 // 聲明引用定義在kernel.asm中的中斷處理函數入口數組 32 extern intr_handler intr_entry_table[IDT_DESC_CNT]; 33 // 初始化可編程中斷控制器 8259A 34 static void pic_init(void) { 35 36 /*初始化主片 */ 37 outb (PIC_M_CTRL, 0x11); // ICW1: 邊沿觸發,級聯8259, 須要ICW4 38 outb (PIC_M_DATA, 0x20); // ICW2: 起始中斷向量號爲0x20, 也就是IR[0-7] 爲 0x20 ~ 0x27 39 outb (PIC_M_DATA, 0x04); // ICW3: IR2 接從片 40 outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常EOI 41 42 /*初始化從片 */ 43 outb (PIC_S_CTRL, 0x11); // ICW1: 邊沿觸發,級聯8259, 須要ICW4 44 outb (PIC_S_DATA, 0x28); // ICW2: 起始中斷向量號爲0x28, 也就是IR[8-15]爲0x28 ~ 0x2F 45 outb (PIC_S_DATA, 0x02); // ICW3: 設置從片鏈接到主片的IR2 引腳 46 outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常EOI 47 48 /*打開主片上IR0,也就是目前只接受時鐘產生的中斷 */ 49 outb (PIC_M_DATA, 0xfe); 50 outb (PIC_S_DATA, 0xff); 51 52 put_str(" pic_init done\n"); 53 } 54 55 //建立中斷門描述符 56 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { 57 p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF; 58 p_gdesc->selector = SELECTOR_K_CODE; 59 p_gdesc->dcount = 0; 60 p_gdesc->attribute = attr; 61 p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16; 62 } 63 64 // 初始化中斷描述符表 65 static void idt_desc_init(void) { 66 int i; 67 for(i = 0; i < IDT_DESC_CNT; i++) { 68 make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); 69 } 70 put_str(" idt_desc_init done\n"); 71 } 72 73 // 通用的中斷處理函數,通常用在異常出現時的處理 74 static void general_intr_handler(uint8_t vec_nr) { 75 if(vec_nr == 0x27 || vec_nr == 0x2f) { 76 return; 77 } 78 put_str("int vector:0x"); 79 put_int(vec_nr); 80 put_char('\n'); 81 } 82 83 // 完成通常中斷處理函數註冊及異常名稱註冊 84 static void exception_init(void) { 85 int i; 86 for(i = 0; i < IDT_DESC_CNT; i++) { 87 // 默認爲這個,之後會由 register_handler 來註冊具體處理函數 88 idt_table[i] = general_intr_handler; 89 intr_name[i] = "unknown"; 90 } 91 intr_name[0] = "#DE Divide Error"; 92 intr_name[1] = "#DB Debug Exception"; 93 intr_name[2] = "NMI Interrupt"; 94 intr_name[3] = "#BP Breakpoint Exception"; 95 intr_name[4] = "#OF Overflow Exception"; 96 intr_name[5] = "#BR BOUND Range Exceeded Exception"; 97 intr_name[6] = "#UD Invalid Opcode Exception"; 98 intr_name[7] = "#NM Device Not Available Exception"; 99 intr_name[8] = "#DF Double Fault Exception"; 100 intr_name[9] = "Coprocessor Segment Overrun"; 101 intr_name[10] = "#TS Invalid TSS Exception"; 102 intr_name[11] = "#NP Segment Not Present"; 103 intr_name[12] = "#SS Stack Fault Exception"; 104 intr_name[13] = "#GP General Protection Exception"; 105 intr_name[14] = "#PF Page-Fault Exception"; 106 // intr_name[15] 第 15 項是 intel 保留項,未使用 107 intr_name[16] = "#MF x87 FPU Floating-Point Error"; 108 intr_name[17] = "#AC Alignment Check Exception"; 109 intr_name[18] = "#MC Machine-Check Exception"; 110 intr_name[19] = "#XF SIMD Floating-Point Exception"; 111 } 112 113 114 // 完成有關中斷到全部初始化工做 115 void idt_init() { 116 put_str("idt_init start\n"); 117 idt_desc_init(); // 初始化中斷描述符表 118 exception_init(); // 初始化通用中斷處理函數 119 pic_init(); // 初始化8259A 120 121 // 加載idt 122 uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16))); 123 asm volatile("lidt %0" : : "m" (idt_operand)); 124 put_str("idt_init done\n"); 125 }
1 [bits 32] 2 %define ERROR_CODE nop 3 %define ZERO push 0 4 5 extern idt_table 6 7 section .data 8 global intr_entry_table 9 intr_entry_table: 10 11 %macro VECTOR 2 12 section .text 13 intr%1entry: 14 %2 15 push ds 16 push es 17 push fs 18 push gs 19 pushad 20 21 ;若是是從片上進入到中斷,除了往從片上發送EOI外,還要往主片上發送EOI 22 mov al,0x20 23 out 0xa0,al 24 out 0x20,al 25 26 push %1 27 call [idt_table + %1*4] 28 jmp intr_exit 29 30 section .data 31 dd intr%1entry 32 %endmacro 33 34 section .text 35 global intr_exit 36 intr_exit: 37 add esp,4 38 popad 39 pop gs 40 pop fs 41 pop es 42 pop ds 43 add esp,4 44 iretd 45 46 VECTOR 0X00,ZERO 47 VECTOR 0X01,ZERO 48 VECTOR 0X02,ZERO 49 VECTOR 0X03,ZERO 50 VECTOR 0X04,ZERO 51 VECTOR 0X05,ZERO 52 VECTOR 0X06,ZERO 53 VECTOR 0X07,ZERO 54 VECTOR 0X08,ZERO 55 VECTOR 0X09,ZERO 56 VECTOR 0X0a,ZERO 57 VECTOR 0X0b,ZERO 58 VECTOR 0X0c,ZERO 59 VECTOR 0X0d,ZERO 60 VECTOR 0X0e,ZERO 61 VECTOR 0X0f,ZERO 62 VECTOR 0X10,ZERO 63 VECTOR 0X11,ZERO 64 VECTOR 0X12,ZERO 65 VECTOR 0X13,ZERO 66 VECTOR 0X14,ZERO 67 VECTOR 0X15,ZERO 68 VECTOR 0X16,ZERO 69 VECTOR 0X17,ZERO 70 VECTOR 0X18,ZERO 71 VECTOR 0X19,ZERO 72 VECTOR 0X1a,ZERO 73 VECTOR 0X1b,ZERO 74 VECTOR 0X1c,ZERO 75 VECTOR 0X1d,ZERO 76 VECTOR 0X1e,ERROR_CODE 77 VECTOR 0X1f,ZERO 78 VECTOR 0X20,ZERO
頭文件ide
1 #ifndef __KERNEL_GLOBAL_H 2 #define __KERNEL_GLOBAL_H 3 #include "stdint.h" 4 5 #define RPL0 0 6 #define RPL1 1 7 #define RPL2 2 8 #define RPL3 3 9 10 #define TI_GDT 0 11 #define TI_LDT 1 12 13 #define SELECTOR_K_CODE ((1 << 3) + (TI_GDT << 2) + RPL0) 14 #define SELECTOR_K_DATA ((2 << 3) + (TI_GDT << 2) + RPL0) 15 #define SELECTOR_K_STACK SELECTOR_K_DATA 16 #define SELECTOR_K_GS ((3 << 3) + (TI_GDT << 2) + RPL0) 17 18 /*-------------- IDT 描述符屬性 ------------*/ 19 #define IDT_DESC_P 1 20 #define IDT_DESC_DPL0 0 21 #define IDT_DESC_DPL3 3 22 #define IDT_DESC_32_TYPE 0xE // 32 位的門 23 #define IDT_DESC_16_TYPE 0x6 // 16 位的門,不會用到,定義它只爲和32 位門區分 24 #define IDT_DESC_ATTR_DPL0 ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE) 25 #define IDT_DESC_ATTR_DPL3 ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE) 26 27 #endif
1 #ifndef __KERNEL_INIT_H 2 #define __KERNEL_INIT_H 3 void init_all(void); 4 #endif
1 #ifndef __KERNEL_INTERRUPT_H 2 #define __KERNEL_INTERRUPT_H 3 #include "stdint.h" 4 typedef void* intr_handler; 5 void idt_init(void); 6 7 /* 定義中斷的兩種狀態: 8 * INTR_OFF值爲0,表示關中斷, 9 * INTR_ON值爲1,表示開中斷 */ 10 enum intr_status { // 中斷狀態 11 INTR_OFF, // 中斷關閉 12 INTR_ON // 中斷打開 13 }; 14 15 enum intr_status intr_get_status(void); 16 enum intr_status intr_set_status (enum intr_status); 17 enum intr_status intr_enable (void); 18 enum intr_status intr_disable (void); 19 void register_handler(uint8_t vector_no, intr_handler function); 20 #endif
1 /******************機器模式 ******************* 2 b -- 輸出寄存器QImode 名稱,即寄存器中的最低8 位:[a-d]l 3 w -- 輸出寄存器HImode 名稱,即寄存器中2 個字節的部分,如[a-d]x 4 5 HImode 6 "Half-Integer"模式,表示一個兩字節的整數 7 QImode 8 "Quarter-Integer"模式,表示一個一字節的整數 9 ******************************************************/ 10 11 #ifndef __LIB_IO_H 12 #define __LIB_IO_H 13 #include "stdint.h" 14 15 /* 向端口port 寫入一個字節*/ 16 static inline void outb(uint16_t port, uint8_t data) { 17 /********************************************************* 18 對端口指定N 表示0~255, d 表示用dx 存儲端口號, 19 %b0 表示對應al,%w1 表示對應dx */ 20 asm volatile ( "outb %b0, %w1" : : "a" (data), "Nd" (port)); 21 /******************************************************/ 22 } 23 24 /* 將addr 處起始的word_cnt 個字寫入端口port */ 25 static inline void outsw(uint16_t port, const void* addr, uint32_t word_cnt) { 26 /********************************************************* 27 +表示此限制即作輸入,又作輸出. 28 outsw 是把ds:esi 處的16 位的內容寫入port 端口,咱們在設置段描述符時, 29 已經將ds,es,ss 段的選擇子都設置爲相同的值了,此時不用擔憂數據錯亂。 */ 30 asm volatile ("cld; rep outsw" : "+S" (addr), "+c" (word_cnt) : "d" (port)); 31 /******************************************************/ 32 } 33 34 /* 將從端口port 讀入的一個字節返回 */ 35 static inline uint8_t inb(uint16_t port) { 36 uint8_t data; 37 asm volatile ("inb %w1, %b0" : "=a" (data) : "Nd" (port)); 38 return data; 39 } 40 41 /* 將從端口port 讀入的word_cnt 個字寫入addr */ 42 static inline void insw(uint16_t port, void* addr, uint32_t word_cnt) { 43 /****************************************************** 44 insw 是將從端口port 處讀入的16 位內容寫入es:edi 指向的內存, 45 咱們在設置段描述符時,已經將ds,es,ss 段的選擇子都設置爲相同的值了, 46 此時不用擔憂數據錯亂。 */ 47 asm volatile ("cld; rep insw" : "+D" (addr), "+c" (word_cnt) : "d" (port) : "memory"); 48 /******************************************************/ 49 } 50 51 #endif
這段代碼也是看的我理解了很久很久,但其實作的事情真的很是簡單,請看下圖函數
代碼有幾個關鍵的結構,在此列出來學習
整個代碼即便完成了這樣幾件事:ui
咱們看到,時鐘中斷(向量號爲 0x20)進來,持續輸出咱們中斷處理程序中要輸出的內容 「int vector:中斷向量號」spa
若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。操作系統
《操做系統真相還原》這本書真的贊!強烈推薦
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。
若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。
本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。
目前的系列包括