1. 程式人生 > >《linux核心完全剖析》筆記02-中斷處理

《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,&divide_error);
    ...
}

set_trap_gate(0,&divide_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);
    ...
}

想象一下,如果沒有中斷,那作業系統不是會一直要去查詢各種裝置的狀態而忙死麼

3. 系統呼叫(軟中斷)