Chapter 7:中斷
===
:::info
這是讀書筆記

作者:鄭鋼
出版社:佳魁資訊股份有限公司
出版日期: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)


IDT limit 16 bits=64KB
每個 item 8B,共可以擁有 8192 個中斷描述符表。
## 中斷處理過程

# 中斷控制器 8259A



## 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

# 中斷處理常式 加強版
## Source Code
https://github.com/yifengyou/os-elephant/tree/master/code/c07/a
## Result

# 偵錯實例:中斷發生前後,Stack的完整過程
文字敘述有看完理解了,有空再練習吧!
# 計時器 8253
## 簡介
* Programmable Interval Timer(PIT)
https://wiki.osdev.org/Programmable_Interval_Timer

For 0x43 Mode register:







## Source Code
https://github.com/yifengyou/os-elephant/tree/master/code/c07/c
## Result
