Chapter 7:中斷 === :::info 這是讀書筆記 ![679544](https://hackmd.io/_uploads/HJB-ZFKV-l.jpg =30%x) 作者:鄭鋼 出版社:佳魁資訊股份有限公司 出版日期:2017/05/31 ::: >[time=2025-07-02] --- # 中斷簡介 ## 硬體中斷 * maskable interrupt: 可通過在中斷封鎖暫存器(eflags:IF)中設定位遮罩來關閉。 * non-maskable interrupt (NMI): 無法通過在中斷封鎖暫存器中設定位遮罩來關閉。 參考網址:https://zh.wikipedia.org/zh-tw/%E4%B8%AD%E6%96%B7 ## 軟體中斷 * Normal interrupt: * system call. * Exception interrupt: * Fault: 這種例外通常是可以更正的,而且在更正後,程式應該可以繼續無誤地進行。在例外處理完,返回原程式時,會重新執行原來造成 fault 的指令。 e.g. page fault. * Trap: 它的返回位址是被 trap 的指令的下一個指令。在例外返回後,程式可以正確無誤地斷續執行。e.g. 偵錯中斷點. * Abort: 這種例外是非常嚴重的錯誤,沒有辦法返回原程式繼續執行。 # Interrupt Descriptor Table(IDT) ## Real Mode: Interrupt Vector Table(IVT) https://zh.wikipedia.org/zh-tw/BIOS%E4%B8%AD%E6%96%B7%E5%91%BC%E5%8F%AB 0x0 - 0x3FF: 1KB 每個 item 4B,共可以擁有 256 個中斷向量。 ## Protected Mode: Interrupt Descriptor Table(IDT) ![3](https://hackmd.io/_uploads/Hk9r9YlSle.png) ![gwZRF](https://hackmd.io/_uploads/HJ996FgHex.png) IDT limit 16 bits=64KB 每個 item 8B,共可以擁有 8192 個中斷描述符表。 ## 中斷處理過程 ![fig9-4](https://hackmd.io/_uploads/rkMVKYgrxl.gif) # 中斷控制器 8259A ![ganCauJ](https://hackmd.io/_uploads/rJNV9Rkree.png) ![8259a-1](https://hackmd.io/_uploads/ByqCFAJrlx.jpg) ![截圖 2025-06-30 下午5.31.03](https://hackmd.io/_uploads/SJEotCkBgg.png) ## Source code ```c= /* 初始化可编程中断控制器8259A */ static void pic_init(void) { /* 初始化主片 */ outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4. outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27. outb (PIC_M_DATA, 0x04); // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI /* 初始化从片 */ outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4. outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F. outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚 outb (PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI /* 打开主片上IR0,也就是目前只接受时钟产生的中断 */ outb (PIC_M_DATA, 0xfe); outb (PIC_S_DATA, 0xff); put_str(" pic_init done\n"); } ``` # 中斷處理常式 ## Interrupt Service Routine by using assembly ```asm= [bits 32] %define ERROR_CODE nop ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作. %define ZERO push 0 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0 extern put_str ;声明外部函数 section .data intr_str db "interrupt occur!", 0xa, 0 global intr_entry_table intr_entry_table: %macro VECTOR 2 section .text intr%1entry: ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少 %2 push intr_str call put_str add esp,4 ; 跳过参数 ; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20 ; 中断结束命令EOI out 0xa0,al ; 向从片发送 out 0x20,al ; 向主片发送 add esp,4 ; 跨过error_code iret ; 从中断返回,32位下等同指令iretd section .data dd intr%1entry ; 存储各个中断入口程序的地址,形成intr_entry_table数组 %endmacro VECTOR 0x00,ZERO VECTOR 0x01,ZERO VECTOR 0x02,ZERO VECTOR 0x03,ZERO VECTOR 0x04,ZERO VECTOR 0x05,ZERO VECTOR 0x06,ZERO VECTOR 0x07,ZERO VECTOR 0x08,ERROR_CODE VECTOR 0x09,ZERO VECTOR 0x0a,ERROR_CODE VECTOR 0x0b,ERROR_CODE VECTOR 0x0c,ZERO VECTOR 0x0d,ERROR_CODE VECTOR 0x0e,ERROR_CODE VECTOR 0x0f,ZERO VECTOR 0x10,ZERO VECTOR 0x11,ERROR_CODE VECTOR 0x12,ZERO VECTOR 0x13,ZERO VECTOR 0x14,ZERO VECTOR 0x15,ZERO VECTOR 0x16,ZERO VECTOR 0x17,ZERO VECTOR 0x18,ERROR_CODE VECTOR 0x19,ZERO VECTOR 0x1a,ERROR_CODE VECTOR 0x1b,ERROR_CODE VECTOR 0x1c,ZERO VECTOR 0x1d,ERROR_CODE VECTOR 0x1e,ERROR_CODE VECTOR 0x1f,ZERO VECTOR 0x20,ZERO ``` ## 建立 IDT ```c= #include "interrupt.h" #include "stdint.h" #include "global.h" ... #define IDT_DESC_CNT 0x21 // 目前总共支持的中断数 /*中断门描述符结构体*/ struct gate_desc { uint16_t func_offset_low_word; uint16_t selector; uint8_t dcount; //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑 uint8_t attribute; uint16_t func_offset_high_word; }; // 静态函数声明,非必须 static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function); static struct gate_desc idt[IDT_DESC_CNT]; // idt是中断描述符表,本质上就是个中断门描述符数组 extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在kernel.S中的中断处理函数入口数组 ... /* 创建中断门描述符 */ static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF; p_gdesc->selector = SELECTOR_K_CODE; p_gdesc->dcount = 0; p_gdesc->attribute = attr; p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16; } /*初始化中断描述符表*/ static void idt_desc_init(void) { int i; for (i = 0; i < IDT_DESC_CNT; i++) { make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); } put_str(" idt_desc_init done\n"); } /*完成有关中断的所有初始化工作*/ void idt_init() { put_str("idt_init start\n"); idt_desc_init(); // 初始化中断描述符表 pic_init(); // 初始化8259A ... } ``` ## I/O port function by inline assembly insw: https://c9x.me/x86/html/file_module_x86_id_141.html outsw: https://dinlon5566.com/IA32/instruct32_hh/vc220.html ## Setting 8259A ```c= /* 初始化可编程中断控制器8259A */ static void pic_init(void) { /* 初始化主片 */ outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4. outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27. outb (PIC_M_DATA, 0x04); // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI /* 初始化从片 */ outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4. outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F. outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚 outb (PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI /* 打开主片上IR0,也就是目前只接受时钟产生的中断 */ outb (PIC_M_DATA, 0xfe); outb (PIC_S_DATA, 0xff); put_str(" pic_init done\n"); } ``` ## 載入 IDT 開啟中斷 ```c= /* 加载idt */ uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16)); asm volatile("lidt %0" : : "m" (idt_operand)); put_str("idt_init done\n"); ``` ## Source Code https://github.com/yifengyou/os-elephant/tree/master/code/c07/a ## Compile ```sh= # build boot nasm boot/mbr.S \ -o out/mbr.bin \ -I boot/inc/ nasm boot/loader.S \ -o out/loader.bin \ -I boot/inc/ # build library nasm lib/kernel/print.S \ -o out/print.o \ -f elf # build kernel nasm kernel/kernel.S \ -o out/kernel.o \ -f elf x86_64-linux-gnu-gcc kernel/interrupt.c \ -o out/interrupt.o \ -c -m32 \ -fno-stack-protector \ -I lib/inc \ -I lib/kernel/inc/ x86_64-linux-gnu-gcc kernel/init.c \ -o out/init.o \ -c -m32 \ -fno-stack-protector \ -I lib/inc \ -I lib/kernel/inc/ x86_64-linux-gnu-gcc kernel/main.c \ -o out/main.o \ -c -m32 \ -fno-stack-protector \ -I lib/inc \ -I lib/kernel/inc/ x86_64-linux-gnu-ld out/main.o out/print.o out/init.o out/interrupt.o out/kernel.o\ -o out/kernel.bin \ -Ttext 0xc0001500 \ -e main \ -m elf_i386 \ -z noexecstack ``` >[!Warning]hidden symbol `__stack_chk_fail_local' isn't defined >https://blog.csdn.net/weixin_51259834/article/details/128335475 >-fno-stack-protector ## Result ![截圖 2025-07-01 中午12.45.55](https://hackmd.io/_uploads/ryl4_J-Blx.png) # 中斷處理常式 加強版 ## Source Code https://github.com/yifengyou/os-elephant/tree/master/code/c07/a ## Result ![截圖 2025-07-02 上午9.36.44](https://hackmd.io/_uploads/SyxLpZfBgg.png) # 偵錯實例:中斷發生前後,Stack的完整過程 文字敘述有看完理解了,有空再練習吧! # 計時器 8253 ## 簡介 * Programmable Interval Timer(PIT) https://wiki.osdev.org/Programmable_Interval_Timer ![截圖 2025-07-02 上午11.54.15](https://hackmd.io/_uploads/H1NF67MSxe.png) For 0x43 Mode register: ![截圖 2025-07-02 上午11.54.43](https://hackmd.io/_uploads/H1lspmGSxx.png) ![截圖 2025-07-02 下午12.13.17](https://hackmd.io/_uploads/HJBWGNzSex.png) ![截圖 2025-07-02 下午12.15.52](https://hackmd.io/_uploads/rJsiMEGHxl.png) ![截圖 2025-07-02 下午12.16.40](https://hackmd.io/_uploads/Sk5aG4zSeg.png) ![截圖 2025-07-02 下午12.17.17](https://hackmd.io/_uploads/S1WlQ4fBlx.png) ![截圖 2025-07-02 下午12.17.55](https://hackmd.io/_uploads/SylG7EfHlg.png) ![截圖 2025-07-02 下午12.18.24](https://hackmd.io/_uploads/rygNXVfSex.png) ## Source Code https://github.com/yifengyou/os-elephant/tree/master/code/c07/c ## Result ![截圖 2025-07-02 下午1.56.19](https://hackmd.io/_uploads/r1DXcSMBgl.png)