《linux核心完全剖析》筆記02-中斷處理
1. 從硬體故障中斷處理理解
要理解中斷系統從理解一段程式碼開始
no_error_code:
xchgl %eax,(%esp) #將eax的內容和esp所指堆疊的內容相交換
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
push %ds
push %es
push %fs
pushl $0 # 第二個引數
lea 44(%esp),%edx
pushl %edx #第一個引數
movl $0x10,%edx #轉到核心態
mov %dx,%ds
mov %dx,%es
mov %dx,%fs
call *%eax #呼叫中斷處理函式
addl $8,%esp
pop %fs
pop %es
pop %ds
popl %ebp
popl %esi
popl %edi
popl %edx
popl %ecx
popl %ebx
popl %eax
iret #返回被中斷程式
以上內容是linux/kernel/asm.s中實現中斷呼叫最核心的程式碼
完整的中斷呼叫程式如下
_divide_error:
pushl $_do_divide_error
jmp no_error_code
_divide_error是代表在核心中的一個地址,在彙編中代表定義了標號,在c語言中代表void divide_error(void);函式
然後我們再來看中斷的初始化操作linux/kernel/traps.c中的trap_init(void)函式的實現
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);
...
}
set_trap_gate(0,÷_error)就是將中斷向量表中第1項的中斷處理程式設定為divide_error,也就是asm.s中定義的_divide_error標號,而在_divide_error中push $_do_divide_error變數是一個C函式,這個函式在traps.c中定義
void do_divide_error(long esp, long error_code)
{
die("divide error",esp,error_code);
}
以上設定中斷處理函式要特別注意中斷向量號,上面為0,是什麼意思?
這些就是Intel保留的中斷向量號的定義,也就是從CPU發出的俗成約定,這個地方我理解了很久:!
中斷號 | 名稱 | 型別 | 訊號 | 說明 |
---|---|---|---|---|
0 | Devide error | 故障 | SIGFPE | 當進行除以零的操作時產生 |
1 | Debug | 陷阱故障 | SIGTRAP | |
2 | nmi | 硬體 | 由不可遮蔽中斷NMI產生 | |
3 | Breakpoint | 陷阱 | SIGTRAP | 由斷點指令int3產生,與debug處理相同 |
4 | Overflow | 陷阱 | SIGSEGV | eflags的溢位標誌OF 引起 |
5 | Bounds check | 故障 | SIGSEGV | 定址到有效地址意外時引起 |
6 | Invalid Opcode | 故障 | SIGILL | CPU執行時發現一個無效的指令操作碼 |
7 | Device not available | 故障 | SIGSEGV | |
8 | Double fault | 異常中止 | SIGSEGV | 雙故障出錯 |
9 | Coprocessor segment overrun | 異常中止 | SIGFPE | 協處理器段超出 |
10 | Invalid TSS | 故障 | SIGSEGV | CPU切換時發覺TSS無效 |
11 | Segment not present | 故障 | SIGBUS | 描述符所指的段不存在 |
12 | Stack segment | 故障 | SIGBUS | 堆疊段不存在或者定址越出堆疊段 |
13 | General protection | 故障 | SIGSEGV | 沒有符合80386保護機制的操作引起 |
14 | Page fault | 故障 | SIGSEGV | 頁不再記憶體 |
15 | Reserved | |||
16 | Coprocessor error | 故障 | SIGPE | 協處理器發出的出錯訊號引起 |
以上內容就是大部分硬體故障的中斷處理函式的處理過程,剩下的部分就是對8259A中斷控制器的中斷響應處理和系統呼叫(俗稱軟中斷)
2. 8259A中斷處理
理解8259A的中斷處理過程,從理解一段程式碼開始:
mov al,#0x11 #(ICW1設定)中斷請求邊沿觸發多片8259級聯並最需傳送ICW4
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb #0x00eb跳轉到下一句的機器碼
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 #(ICW2設定)主片中斷號範圍從0x20開始
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 #從片中斷號範圍從0x28開始
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 #(ICW3設定)設定主晶片
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 #設定從晶片
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 #(ICW4設定):普通EOI,非緩衝切需傳送指令來複位的模式
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
#8259A中斷控制器初始化結束
.word 0x00eb,0x00eb
mov al,#0xFF #遮蔽所有中斷請求
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
讀取以上程式碼有困難的話,需要複習一下8259A中斷控制器的相關知識,這是微機介面原理的主要內容,不過閱讀上面程式碼的重點是告訴我們8259的中斷向量是從0x20開始的,要記住這一點,不然時鐘中斷,硬碟中斷等的中斷向量號是怎麼來的,你就不從知曉,可以參考一下列表:
現在來看具體的中斷處理向量的設定,它們分散在不同的地方
1. 時鐘中斷向量設定
timer_interrupt這就是作業系統的心跳函式
linux/kernel/sched.c
void sched_init(void)
{
...
set_intr_gate(0x20,&timer_interrupt);
...
}
2. 硬碟中斷向量設定
linux/kernel/blk_drv/hd.c
void hd_init(void)
{
...
set_intr_gate(0x2E,&hd_interrupt);
...
}
3. 鍵盤中斷向量設定
linux/kernel/chr_drv/console.c
void con_init(void)
{
...
set_trap_gate(0x21,&keyboard_interrupt);
...
}
想象一下,如果沒有中斷,那作業系統不是會一直要去查詢各種裝置的狀態而忙死麼