歡迎您光臨本站 註冊首頁

linux內核中斷、異常 .

←手機掃碼閱讀     火星人 @ 2014-03-03 , reply:0

linux內核中斷、異常 .

linux內核中斷、異常 .


中斷:

•可屏蔽中斷:所有有I/O設備請求的中斷都是,被屏蔽的中斷會一直被CPU 忽略,直到屏蔽位被重置。
•不可屏蔽中斷:非常危險的事件引起(如硬體失敗)。
異常:

•處理器產生的(Fault,Trap,Abort)異常
•programmed exceptions(軟中斷):由程序員通過INT或INT3指令觸發,通常當做trap處理,用處:實現系統調用。
中斷描述符表(IDT):256項,其中的每一項關聯一個中斷/異常處理過程,有三種類型:

1.Task Gate Descriptor. Linux未使用該類型的描述符。
2.Interrupt Gate Descriptor.用於處理中斷。
3.Trap Gate Descriptor. 用於處理異常。
•中斷門: 用於硬體中斷,DPL為0,不允許用戶態直接使用int指令訪問,硬體中斷免去這一判斷,因此可以在用戶態響應中斷,見set_intr_gate
•DPL3 陷阱門: 用於系統調用,DPL為3,允許用戶態直接使用int指令訪問,這樣才能通過int80訪問系統調用,只有80號向量屬於此門,見 set_system_gate
•DPL0陷阱門: 用於CPU異常,不允許用戶態直接使用int指令訪問,硬體中斷免去這一判斷,因此可以在用戶產生CPU異常,見set_trap_gate
在指令執行過程中控制單元檢測是否有中斷/異常發生,如果有,等待該條指令執行完成以後,硬體按如下過程執行:

1.確定 中斷向量的編號i。
2.從IDT表中得到第i個門描述符。(idtr指向IDT)
3.由第i項中的選擇符和gdtr 查到位於GDT中的段描述符,從而得到中斷處理程序的基地址,而偏移量位於門描述符中。
4.做許可權檢查:比較cs中的CPL和GDT中 段描述符的DPL,確保中斷處理程序的特權級不低於調用者。對於programed exception 還需檢查CPL與門描述符的DPL,還應確保CPL大於等於門的DPL。Why?因為INT指令允許用戶態的進程產生中斷信號,其向量值 可以為0到255的任一值,為了避免用戶通過INT指令產生非法中斷,在初始化的時候,將向量值為80H的門描述符(系統調用使用該門)的DPL設為3, 將其他需要避免訪問的門描述符的DPL值設為0,這樣在做許可權檢查的時候就可以檢查出來非法的情況。
5.檢查是否發 生了特權級的變化,一般指是否由用戶態陷入了內核態。如果是由用戶態陷入了內核態,控制單元必須開始使用與新的特權級相關的堆棧a. 讀tr寄存器,訪問運行進程的tss段。why?因為任何進程從用戶態陷入內核態都必須從TSS獲得內核堆棧指針。
b. 用與新特權級相關的棧段和棧指針裝載ss和esp寄存器。這些值可以在進程的tss段中找到。

c. 在新的棧(內核棧)中保存用戶態的ss和esp,這些值指明了用戶態相關棧的邏輯地址。

6.若發生的是故障,用引起異常的指令 地址修改cs和eip寄存器的值,以使得這條指令在異常處理結束后能被再次執行
7.在棧中保存eflags、cs和eip的內容
8.如 果異常帶有一個硬體出錯碼,則將它保存在棧中
9.裝載cs和eip寄存器,其值分別是在GDT中找到的段描述符段基址和IDT表中第i 個門的偏移量。這樣就得到了中斷/異常處理程序第一條指令的邏輯地址。
從中斷/異 常返回:

中斷/異常處理完后,相應的處理程序會執行一條iret指令,做了如下事情:

1)用保存在 棧中的值裝載cs、eip和eflags寄存器。如果一個硬體出錯碼曾被壓入棧中,那麼彈出這個硬體出錯碼

2)檢查處理程序的特權級是 否等於cs中最低兩位的值(這意味著進程在被中斷的時候是運行在內核態還是用戶態)。若是內核態,iret終止執行;否則,轉入3

3) 從棧中裝載ss和esp寄存器。這步意味著返回到與舊特權級相關的棧。

4)檢查ds、es、fs和gs段寄存器的內容,如果其中一個寄 存器包含的選擇符是一個段描述符,並且特權級比當前特權級高,則清除相應的寄存器。這麼做是防止懷有惡意的用戶程序利用這些寄存器訪問內核空間。


關於硬體中斷和異常的原理簡單描述為:當中斷到到來時,由硬體觸發中斷引腳,通過引腳號找到中斷號,然後通過中斷號從中斷描述符表(IDT)中找到對應的項。從gdtr寄存器中獲得GDT的基地址,並在GDT中查找,以讀取IDT表項中的選擇符所標識的段描述符。這個描述符指定中斷或異常處理程序所在段的基地址。許可權檢查。保存現場。裝載cs和eip寄存器,其值分別是IDT表中第i想們描述符的段選擇符和偏移量欄位。這些值給出了中斷或者異常處理程序的第一條指令的邏輯地址。中斷或異常返回后,相應的處理程序必須產生一條iret指令,把控制權轉交給被中斷的進程。

中斷流:



中斷描述符表的初始化

在內核初始化過程中,setup_idt彙編語言函數用同一個中斷門(即指向ignore_int中斷處理程序)來填充所有這256個表項

view plaincopy to clipboardprint?01./*  
02. *  setup_idt  
03. *  
04. *  sets up a idt with 256 entries pointing to  
05. *  ignore_int, interrupt gates. It doesn't actually load  
06. *  idt - that can be done only after paging has been enabled  
07. *  and the kernel moved to PAGE_OFFSET. Interrupts  
08. *  are enabled elsewhere, when we can be relatively  
09. *  sure everything is ok.  
10. *  
11. *  Warning: %esi is live across this function.  
12. */  
13.setup_idt:  
14.    lea ignore_int,%edx  
15.    movl $(__KERNEL_CS << 16),%eax  
16.    movw %dx,%ax        /* selector = 0x0010 = cs */  
17.    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */  
18.  
19.    lea idt_table,%edi  
20.    mov $256,%ecx  
21.rp_sidt:  
22.    movl %eax,(%edi)  
23.    movl %edx,4(%edi)  
24.    addl $8,%edi  
25.    dec %ecx  
26.    jne rp_sidt  
27.  
28..macro  set_early_handler handler,trapno  
29.    lea \handler,%edx  
30.    movl $(__KERNEL_CS << 16),%eax  
31.    movw %dx,%ax  
32.    movw $0x8E00,%dx    /* interrupt gate - dpl=0, present */  
33.    lea idt_table,%edi  
34.    movl %eax,8*\trapno(%edi)  
35.    movl %edx,8*\trapno+4(%edi)  
36..endm  
37.  
38.    set_early_handler handler=early_divide_err,trapno=0  
39.    set_early_handler handler=early_illegal_opcode,trapno=6  
40.    set_early_handler handler=early_protection_fault,trapno=13  
41.    set_early_handler handler=early_page_fault,trapno=14  
42.  
43.    ret  
/*
*  setup_idt
*
*  sets up a idt with 256 entries pointing to
*  ignore_int, interrupt gates. It doesn't actually load
*  idt - that can be done only after paging has been enabled
*  and the kernel moved to PAGE_OFFSET. Interrupts
*  are enabled elsewhere, when we can be relatively
*  sure everything is ok.
*
*  Warning: %esi is live across this function.
*/
setup_idt:
        lea ignore_int,%edx
        movl $(__KERNEL_CS << 16),%eax
        movw %dx,%ax                /* selector = 0x0010 = cs */
        movw $0x8E00,%dx        /* interrupt gate - dpl=0, present */

        lea idt_table,%edi
        mov $256,%ecx
rp_sidt:
        movl %eax,(%edi)
        movl %edx,4(%edi)
        addl $8,%edi
        dec %ecx
        jne rp_sidt

.macro        set_early_handler handler,trapno
        lea \handler,%edx
        movl $(__KERNEL_CS << 16),%eax
        movw %dx,%ax
        movw $0x8E00,%dx        /* interrupt gate - dpl=0, present */
        lea idt_table,%edi
        movl %eax,8*\trapno(%edi)
        movl %edx,8*\trapno+4(%edi)
.endm

        set_early_handler handler=early_divide_err,trapno=0
        set_early_handler handler=early_illegal_opcode,trapno=6
        set_early_handler handler=early_protection_fault,trapno=13
        set_early_handler handler=early_page_fault,trapno=14

        ret 在start_kernel中調用trap_init函數想idt表中添加項(主要是異常處理)

view plaincopy to clipboardprint?01.void __init trap_init(void)  
02.{  
03.    int i;  
04.  
05.#ifdef CONFIG_EISA   
06.    void __iomem *p = early_ioremap(0x0FFFD9, 4);  
07.  
08.    if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))  
09.        EISA_bus = 1;  
10.    early_iounmap(p, 4);  
11.#endif   
12.  
13.    set_intr_gate(0, ÷_error);  
14.    set_intr_gate_ist(1, &debug, DEBUG_STACK);  
15.    set_intr_gate_ist(2, &nmi, NMI_STACK);  
16.    /* int3 can be called from all */  
17.    set_system_intr_gate_ist(3, &int3, DEBUG_STACK);  
18.    /* int4 can be called from all */  
19.    set_system_intr_gate(4, &overflow);  
20.    set_intr_gate(5, &bounds);  
21.    set_intr_gate(6, &invalid_op);  
22.    set_intr_gate(7, &device_not_available);  
23.#ifdef CONFIG_X86_32   
24.    set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);  
25.#else   
26.    set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);  
27.#endif   
28.    set_intr_gate(9, &coprocessor_segment_overrun);  
29.    set_intr_gate(10, &invalid_TSS);  
30.    set_intr_gate(11, &segment_not_present);  
31.    set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);  
32.    set_intr_gate(13, &general_protection);  
33.    set_intr_gate(14, &page_fault);  
34.    set_intr_gate(15, &spurious_interrupt_bug);  
35.    set_intr_gate(16, &coprocessor_error);  
36.    set_intr_gate(17, &alignment_check);  
37.#ifdef CONFIG_X86_MCE   
38.    set_intr_gate_ist(18, &machine_check, MCE_STACK);  
39.#endif   
40.    set_intr_gate(19, &simd_coprocessor_error);  
41.  
42.    /* Reserve all the builtin and the syscall vector: */  
43.    for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)  
44.        set_bit(i, used_vectors);  
45.  
46.#ifdef CONFIG_IA32_EMULATION   
47.    set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);  
48.    set_bit(IA32_SYSCALL_VECTOR, used_vectors);  
49.#endif   
50.  
51.#ifdef CONFIG_X86_32   
52.    if (cpu_has_fxsr) {  
53.        printk(KERN_INFO "Enabling fast FPU save and restore... ");  
54.        set_in_cr4(X86_CR4_OSFXSR);  
55.        printk("done.\n");  
56.    }  
57.    if (cpu_has_xmm) {  
58.        printk(KERN_INFO  
59.            "Enabling unmasked SIMD FPU exception support... ");  
60.        set_in_cr4(X86_CR4_OSXMMEXCPT);  
61.        printk("done.\n");  
62.    }  
63.  
64.    set_system_trap_gate(SYSCALL_VECTOR, &system_call);  
65.    set_bit(SYSCALL_VECTOR, used_vectors);  
66.#endif   
67.  
68.    /*
69.     * Should be a barrier for any external CPU state:
70.     */  
71.    cpu_init();  
72.  
73.    x86_init.irqs.trap_init();  
74.}  
void __init trap_init(void)
{
        int i;

#ifdef CONFIG_EISA
        void __iomem *p = early_ioremap(0x0FFFD9, 4);

        if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
                EISA_bus = 1;
        early_iounmap(p, 4);
#endif

        set_intr_gate(0, ÷_error);
        set_intr_gate_ist(1, &debug, DEBUG_STACK);
        set_intr_gate_ist(2, &nmi, NMI_STACK);
        /* int3 can be called from all */
        set_system_intr_gate_ist(3, &int3, DEBUG_STACK);
        /* int4 can be called from all */
        set_system_intr_gate(4, &overflow);
        set_intr_gate(5, &bounds);
        set_intr_gate(6, &invalid_op);
        set_intr_gate(7, &device_not_available);
#ifdef CONFIG_X86_32
        set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
        set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
#endif
        set_intr_gate(9, &coprocessor_segment_overrun);
        set_intr_gate(10, &invalid_TSS);
        set_intr_gate(11, &segment_not_present);
        set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
        set_intr_gate(13, &general_protection);
        set_intr_gate(14, &page_fault);
        set_intr_gate(15, &spurious_interrupt_bug);
        set_intr_gate(16, &coprocessor_error);
        set_intr_gate(17, &alignment_check);
#ifdef CONFIG_X86_MCE
        set_intr_gate_ist(18, &machine_check, MCE_STACK);
#endif
        set_intr_gate(19, &simd_coprocessor_error);

        /* Reserve all the builtin and the syscall vector: */
        for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
                set_bit(i, used_vectors);

#ifdef CONFIG_IA32_EMULATION
        set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
        set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif

#ifdef CONFIG_X86_32
        if (cpu_has_fxsr) {
                printk(KERN_INFO "Enabling fast FPU save and restore... ");
                set_in_cr4(X86_CR4_OSFXSR);
                printk("done.\n");
        }
        if (cpu_has_xmm) {
                printk(KERN_INFO
                        "Enabling unmasked SIMD FPU exception support... ");
                set_in_cr4(X86_CR4_OSXMMEXCPT);
                printk("done.\n");
        }

        set_system_trap_gate(SYSCALL_VECTOR, &system_call);
        set_bit(SYSCALL_VECTOR, used_vectors);
#endif

        /*
         * Should be a barrier for any external CPU state:
         */
        cpu_init();

        x86_init.irqs.trap_init();
}
異常處理

異常處理程序有一個標準的結構,由以下三部分組成:

1,在內核堆棧中保存大多數寄存器的內容(這部分用彙編語言實現)

例如,對於除0異常的彙編

view plaincopy to clipboardprint?01.ENTRY(divide_error)  
02.    RING0_INT_FRAME  
03.    pushl $0            # no error code  
04.    CFI_ADJUST_CFA_OFFSET 4  
05.    pushl $do_divide_error  
06.    CFI_ADJUST_CFA_OFFSET 4  
07.    jmp error_code  
08.    CFI_ENDPROC  
09.END(divide_error)  
ENTRY(divide_error)
        RING0_INT_FRAME
        pushl $0                        # no error code
        CFI_ADJUST_CFA_OFFSET 4
        pushl $do_divide_error
        CFI_ADJUST_CFA_OFFSET 4
        jmp error_code
        CFI_ENDPROC
END(divide_error) 其中入口divide_error為idt表中對應項的處理函數地址,也就是說,產生異常后首先跳到這裡執行。當異常產生時,如果控制單元沒有自動地把一個硬體出錯代碼插入到棧中,相應的彙編片段會含一條pushl $0指令,在棧中墊上一個空值。然後,把高級c函數的地址壓入棧中,他的名字由異常處理程序名與do_前綴組成。然後跳轉到error_code中執行

view plaincopy to clipboardprint?01.error_code:  
02.    /* the function address is in %gs's slot on the stack */  
03.    pushl %fs  
04.    CFI_ADJUST_CFA_OFFSET 4  
05.    /*CFI_REL_OFFSET fs, 0*/  
06.    pushl %es  
07.    CFI_ADJUST_CFA_OFFSET 4  
08.    /*CFI_REL_OFFSET es, 0*/  
09.    pushl %ds  
10.    CFI_ADJUST_CFA_OFFSET 4  
11.    /*CFI_REL_OFFSET ds, 0*/  
12.    pushl %eax  
13.    CFI_ADJUST_CFA_OFFSET 4  
14.    CFI_REL_OFFSET eax, 0  
15.    pushl %ebp  
16.    CFI_ADJUST_CFA_OFFSET 4  
17.    CFI_REL_OFFSET ebp, 0  
18.    pushl %edi  
19.    CFI_ADJUST_CFA_OFFSET 4  
20.    CFI_REL_OFFSET edi, 0  
21.    pushl %esi  
22.    CFI_ADJUST_CFA_OFFSET 4  
23.    CFI_REL_OFFSET esi, 0  
24.    pushl %edx  
25.    CFI_ADJUST_CFA_OFFSET 4  
26.    CFI_REL_OFFSET edx, 0  
27.    pushl %ecx  
28.    CFI_ADJUST_CFA_OFFSET 4  
29.    CFI_REL_OFFSET ecx, 0  
30.    pushl %ebx  
31.    CFI_ADJUST_CFA_OFFSET 4  
32.    CFI_REL_OFFSET ebx, 0  
33.    cld  
34.    movl $(__KERNEL_PERCPU), %ecx  
35.    movl %ecx, %fs  
36.    UNWIND_ESPFIX_STACK  
37.    GS_TO_REG %ecx  
38.    movl PT_GS(%esp), %edi      # get the function address  
39.    movl PT_ORIG_EAX(%esp), %edx    # get the error code  
40.    movl $-1, PT_ORIG_EAX(%esp) # no syscall to restart  
41.    REG_TO_PTGS %ecx  
42.    SET_KERNEL_GS %ecx  
43.    movl $(__USER_DS), %ecx  
44.    movl %ecx, %ds  
45.    movl %ecx, %es  
46.    TRACE_IRQS_OFF  
47.    movl %esp,%eax          # pt_regs pointer  
48.    call *%edi  
49.    jmp ret_from_exception  
error_code:
        /* the function address is in %gs's slot on the stack */
        pushl %fs
        CFI_ADJUST_CFA_OFFSET 4
        /*CFI_REL_OFFSET fs, 0*/
        pushl %es
        CFI_ADJUST_CFA_OFFSET 4
        /*CFI_REL_OFFSET es, 0*/
        pushl %ds
        CFI_ADJUST_CFA_OFFSET 4
        /*CFI_REL_OFFSET ds, 0*/
        pushl %eax
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET eax, 0
        pushl %ebp
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET ebp, 0
        pushl %edi
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET edi, 0
        pushl %esi
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET esi, 0
        pushl %edx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET edx, 0
        pushl %ecx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET ecx, 0
        pushl %ebx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET ebx, 0
        cld
        movl $(__KERNEL_PERCPU), %ecx
        movl %ecx, %fs
        UNWIND_ESPFIX_STACK
        GS_TO_REG %ecx
        movl PT_GS(%esp), %edi                # get the function address
        movl PT_ORIG_EAX(%esp), %edx        # get the error code
        movl $-1, PT_ORIG_EAX(%esp)        # no syscall to restart
        REG_TO_PTGS %ecx
        SET_KERNEL_GS %ecx
        movl $(__USER_DS), %ecx
        movl %ecx, %ds
        movl %ecx, %es
        TRACE_IRQS_OFF
        movl %esp,%eax                        # pt_regs pointer
        call *%edi
        jmp ret_from_exception
error_code彙編代碼主要完成大部分寄存器的保存,然後調用call *%edi代碼調用上面保存在棧中的c函數執行。
在linux2.6內核中,採用宏的方式定義這類do_函數:

view plaincopy to clipboardprint?01.DO_ERROR_INFO(0, SIGFPE, "divide error", divide_error, FPE_INTDIV, regs->ip)  
02.DO_ERROR(4, SIGSEGV, "overflow", overflow)  
03.DO_ERROR(5, SIGSEGV, "bounds", bounds)  
04.DO_ERROR_INFO(6, SIGILL, "invalid opcode", invalid_op, ILL_ILLOPN, regs->ip)  
05.DO_ERROR(9, SIGFPE, "coprocessor segment overrun", coprocessor_segment_overrun)  
06.DO_ERROR(10, SIGSEGV, "invalid TSS", invalid_TSS)  
07.DO_ERROR(11, SIGBUS, "segment not present", segment_not_present)  
08.#ifdef CONFIG_X86_32   
09.DO_ERROR(12, SIGBUS, "stack segment", stack_segment)  
10.#endif  
DO_ERROR_INFO(0, SIGFPE, "divide error", divide_error, FPE_INTDIV, regs->ip)
DO_ERROR(4, SIGSEGV, "overflow", overflow)
DO_ERROR(5, SIGSEGV, "bounds", bounds)
DO_ERROR_INFO(6, SIGILL, "invalid opcode", invalid_op, ILL_ILLOPN, regs->ip)
DO_ERROR(9, SIGFPE, "coprocessor segment overrun", coprocessor_segment_overrun)
DO_ERROR(10, SIGSEGV, "invalid TSS", invalid_TSS)
DO_ERROR(11, SIGBUS, "segment not present", segment_not_present)
#ifdef CONFIG_X86_32
DO_ERROR(12, SIGBUS, "stack segment", stack_segment)
#endif 我們對上面的宏,看一個

view plaincopy to clipboardprint?01.#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)     \   
02.dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \  
03.{                                   \  
04.    siginfo_t info;                         \  
05.    info.si_signo = signr;                      \  
06.    info.si_errno = 0;                      \  
07.    info.si_code = sicode;                      \  
08.    info.si_addr = (void __user *)siaddr;               \  
09.    if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr)  \  
10.                            == NOTIFY_STOP) \  
11.        return;                         \  
12.    conditional_sti(regs);                      \  
13.    do_trap(trapnr, signr, str, regs, error_code, &info);       \  
14.}  
#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)                \
dotraplinkage void do_##name(struct pt_regs *regs, long error_code)        \
{                                                                        \
        siginfo_t info;                                                        \
        info.si_signo = signr;                                                \
        info.si_errno = 0;                                                \
        info.si_code = sicode;                                                \
        info.si_addr = (void __user *)siaddr;                                \
        if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr)        \
                                                        == NOTIFY_STOP)        \
                return;                                                        \
        conditional_sti(regs);                                                \
        do_trap(trapnr, signr, str, regs, error_code, &info);                \
} 可見最後都調用了do_trap函數來執行。




《解決方案》

謝謝分享

[火星人 ] linux內核中斷、異常 .已經有542次圍觀

http://coctec.com/docs/service/show-post-1294.html