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函數來執行。
《解決方案》
謝謝分享