首页 > 代码库 > 中断(一)——中断描述符表的定义和初始化 (基于3.16-rc4)
中断(一)——中断描述符表的定义和初始化 (基于3.16-rc4)
1.中断描述符表的定义(arch/x86/kernel/traps.c)
1 gate_desc debug_idt_table[NR_VECTORS] __page_aligned_bss;
定义的描述符表为一个结构体数组,数组元素类型为gate_desc,大小为8B。NR_VECTORS宏为256,即描述符表大小为256*8B。
2.idt_descr变量的定义(arch/x86/kernel/head_32.S)
1 idt_descr:2 .word IDT_ENTRIES*8-1 # idt contains 256 entries3 .long idt_table4 5 # boot GDT descriptor (later on used by CPU#0):6 .word 0 # 32 bit align gdt_desc.address
这是内核定义的一个全局变量,存放有中断描述符表的大小和首地址。该变量将存放在idtr寄存器中。
3.中断描述符初步的初始化(arch/x86/kernel/head_32.S)
1 __INIT 2 setup_once: 3 /* 4 * Set up a idt with 256 entries pointing to ignore_int, 5 * interrupt gates. It doesn‘t actually load idt - that needs 6 * to be done on each CPU. Interrupts are enabled elsewhere, 7 * when we can be relatively sure everything is ok. 8 */ 9 10 movl $idt_table,%edi11 movl $early_idt_handlers,%eax12 movl $NUM_EXCEPTION_VECTORS,%ecx13 1:14 movl %eax,(%edi)15 movl %eax,4(%edi)16 /* interrupt gate, dpl=0, present */17 movl $(0x8E000000 + __KERNEL_CS),2(%edi)18 addl $9,%eax19 addl $8,%edi20 loop 1b21 22 movl $256 - NUM_EXCEPTION_VECTORS,%ecx23 movl $ignore_int,%edx24 movl $(__KERNEL_CS << 16),%eax25 movw %dx,%ax /* selector = 0x0010 = cs */26 movw $0x8E00,%dx /* interrupt gate - dpl=0, present */27 2:28 movl %eax,(%edi)29 movl %edx,4(%edi)30 addl $8,%edi31 loop 2b32 ...33 ...
这段代码是对中断描述符表的初步初始化,14-20行是对前32个中断描述符进行初始化,让所有描述符指向early_idt_handlers处理函数。22-31行是对后256-32=224个中断描述符进行初始化,使之指向ignore_int处理函数。省略号以后是对GDT描述符表的初始化,这里不予讨论。
4.中断描述符表最终的初始化(arch/x86/kernel/traps.c)
1 void __init trap_init(void) 2 { 3 int i; 4 5 #ifdef CONFIG_EISA 6 void __iomem *p = early_ioremap(0x0FFFD9, 4); 7 8 if (readl(p) == ‘E‘ + (‘I‘<<8) + (‘S‘<<16) + (‘A‘<<24)) 9 EISA_bus = 1;10 early_iounmap(p, 4);11 #endif12 13 set_intr_gate(X86_TRAP_DE, divide_error);14 set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);15 /* int4 can be called from all */16 set_system_intr_gate(X86_TRAP_OF, &overflow);17 set_intr_gate(X86_TRAP_BR, bounds);18 set_intr_gate(X86_TRAP_UD, invalid_op);19 set_intr_gate(X86_TRAP_NM, device_not_available);20 #ifdef CONFIG_X86_3221 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);22 #else23 set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);24 #endif25 set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);26 set_intr_gate(X86_TRAP_TS, invalid_TSS);27 set_intr_gate(X86_TRAP_NP, segment_not_present);28 set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);29 set_intr_gate(X86_TRAP_GP, general_protection);30 set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);31 set_intr_gate(X86_TRAP_MF, coprocessor_error);32 set_intr_gate(X86_TRAP_AC, alignment_check);33 #ifdef CONFIG_X86_MCE34 set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);35 #endif36 set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);37 38 /* Reserve all the builtin and the syscall vector: */39 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)40 set_bit(i, used_vectors);41 42 #ifdef CONFIG_IA32_EMULATION43 set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);44 set_bit(IA32_SYSCALL_VECTOR, used_vectors);45 #endif46 47 #ifdef CONFIG_X86_3248 set_system_trap_gate(SYSCALL_VECTOR, &system_call);FIRST_EXTERNAL_VECTOR49 set_bit(SYSCALL_VECTOR, used_vectors);50 #endif51 52 /*53 * Set the IDT descriptor to a fixed read-only location, so that the54 * "sidt" instruction will not leak the location of the kernel, and55 * to defend the IDT against arbitrary memory write vulnerabilities.56 * It will be reloaded in cpu_init() */57 __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);58 idt_descr.address = fix_to_virt(FIX_RO_IDT);59 60 /*61 * Should be a barrier for any external CPU state:62 */63 cpu_init();64 65 x86_init.irqs.trap_init();66 67 #ifdef CONFIG_X86_6468 memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);69 set_nmi_gate(X86_TRAP_DB, &debug);70 set_nmi_gate(X86_TRAP_BP, &int3);71 #endif72 }
该函数对中断描述表的进行了部分初始化,13-36行对系统已分配的异常和非屏蔽中断进行初始化,中断向量号为0-19。接着,39-40行在中断位图表中对已初始化的中断所对应的位进行标记。接着,43和48行又出始化了两个中断,一个是系统中断门,中断向量号为0x80,一个是系统陷阱门,中断向量号为2。
在该函数中,大家可以看出,对中断进行初始化的函数有如下几个:
1 set_intr_gate()2 set_system_intr_gate()3 set_system_trap_gate()
4 set_task_gate()
这几个函数也在arch/x86/kernel/traps.c中定义。分别是对中断门,系统中断门,系统陷阱门,任务门描述符的初始化。进一步深入可发现,这几个函数都调用了如下的函数:
1 static inline void _set_gate(int gate, unsigned type, void *addr, 2 unsigned dpl, unsigned ist, unsigned seg) 3 { 4 gate_desc s; 5 6 pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); 7 /* 8 * does not need to be atomic because it is only done once at 9 * setup time10 */11 write_idt_entry(idt_table, gate, &s);12 write_trace_idt_entry(gate, &s);13 }
该函数定义在arch/x86/include/asm/desc.h文件中。在该函数中定义了一个gate_desc类型变量s,并将s的指针传递给pack_gate函数,把要初始化的描述符各个字段的值临时存放在s中。下边分析下pack_gate函数,在分析该函数之前,我们先看下gate_desc结构体。
1 struct desc_struct { 2 union { 3 struct { 4 unsigned int a; 5 unsigned int b; 6 }; 7 struct { 8 u16 limit0; 9 u16 base0;10 unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;11 unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;12 };13 };14 } __attribute__((packed));
typedef struct desc_struct gate_desc
该结构体定义位于arch/x86/include/asm/desc_defs.h中。该结构体中包含了一个共用体,共用体中又包含了两个结构体。我们知道,共用体在分配内存单元时,并不为每个成员都分配,而是为最大的成员来分配。可以看出该共用体的两个结构体成员大小相等,都是8B,因此整个gate_desc结构体大小就为8B。我们可以使用共用体中的任意一个结构体成员来为这个gate_desc赋值,也就是说我们既可以将gate_desc看成是struct { unsigned int a; unsigned int b; };也可以看成是struct { u16 limit0; u16 base0; .... };下面在分析pack_gate函数过程中将看到赋值过程,我们将gate_desc看作是struct { unsigned int a; unsigned int b; };。
1 static inline void pack_gate(gate_desc *gate, unsigned char type,2 unsigned long base, unsigned dpl, unsigned flags,3 unsigned short seg)4 {5 gate->a = (seg << 16) | (base & 0xffff);6 gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8);7 }
该函数也定义在arch/x86/include/asm/desc.h文件中。在该函数中为gate所指向的gate_desc描述符进行初始化。gate->a是描述符的0-31位,gate->b是描述符的32-63位。描述符的如下所示:
接着,我们分析_set_gate()中的11行,write_idt_entry()调用。
1 static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)2 {3 memcpy(&idt[entry], gate, sizeof(*gate));4 }
#define write_idt_entry() native_write_idt_entry() //粗略的写了下,大家能明白就行
该函数定义在arch/x86/include/asm/desc.h中。在该函数中,使用memcpy()函数将gate中的字段复制到&idt[entry]所指向的各个字段中。很显然,idt[]数组就是内核中定义的中断描述符表,我们在文章开头给大家看过该定义。gate就是我们在_set_gate()中定义的临时变量s,在这里我们将s中的字段值赋给idt[]数组的对应元素,至此一个描述符的初始化工作就全部完成了,s变量的用途也就结束了,另外,entry变量中存放的是要初始化的中断向量号,用该号来定位idt数组的元素。
最后,再补充说明一点东西,回头看下第4点中的trap_init()函数,在该函数中对中断描述符表进行初始化,使用了很多初始化函数比如set_intr_gate()或set_system_intr_gate()等等,我们拿第一个初始化函数set_intr_gate(X86_TRAP_DE, divide_error)来做说明。X86_TRAP_DE是枚举类型参数,代表的是中断向量号,定义在arch/x86/include/asm/traps.h文件中。这种枚举类型其实有很多。
1 /* Interrupts/Exceptions */ 2 enum { 3 X86_TRAP_DE = 0, /* 0, Divide-by-zero */ 4 X86_TRAP_DB, /* 1, Debug */ 5 X86_TRAP_NMI, /* 2, Non-maskable Interrupt */ 6 X86_TRAP_BP, /* 3, Breakpoint */ 7 X86_TRAP_OF, /* 4, Overflow */ 8 X86_TRAP_BR, /* 5, Bound Range Exceeded */ 9 X86_TRAP_UD, /* 6, Invalid Opcode */10 X86_TRAP_NM, /* 7, Device Not Available */11 X86_TRAP_DF, /* 8, Double Fault */12 X86_TRAP_OLD_MF, /* 9, Coprocessor Segment Overrun */13 X86_TRAP_TS, /* 10, Invalid TSS */14 X86_TRAP_NP, /* 11, Segment Not Present */15 X86_TRAP_SS, /* 12, Stack Segment Fault */16 X86_TRAP_GP, /* 13, General Protection Fault */17 X86_TRAP_PF, /* 14, Page Fault */18 X86_TRAP_SPURIOUS, /* 15, Spurious Interrupt */19 X86_TRAP_MF, /* 16, x87 Floating-Point Exception */20 X86_TRAP_AC, /* 17, Alignment Check */21 X86_TRAP_MC, /* 18, Machine Check */22 X86_TRAP_XF, /* 19, SIMD Floating-Point Exception */23 X86_TRAP_IRET = 32, /* 32, IRET Exception */24 };
第二个参数,是汇编函数的函数名(在这里作为函数指针来使用),该函数为内核原先就定义好的中断或异常处理程序。这种类型的函数有很多,都定义在arch/x86/kernel/entry_32.S文件中,下边我们列举几个给大家看看,有兴趣自己去查。
1 ENTRY(segment_not_present) 2 RING0_EC_FRAME 3 ASM_CLAC 4 pushl_cfi $do_segment_not_present 5 jmp error_code 6 CFI_ENDPROC 7 END(segment_not_present) 8 9 ENTRY(stack_segment)10 RING0_EC_FRAME11 ASM_CLAC12 pushl_cfi $do_stack_segment13 jmp error_code14 CFI_ENDPROC15 END(stack_segment)16 17 ENTRY(alignment_check)18 RING0_EC_FRAME19 ASM_CLAC20 pushl_cfi $do_alignment_check21 jmp error_code22 CFI_ENDPROC23 END(alignment_check)24 25 ENTRY(divide_error)26 RING0_INT_FRAME27 ASM_CLAC28 pushl_cfi $0 # no error code29 pushl_cfi $do_divide_error30 jmp error_code31 CFI_ENDPROC32 END(divide_error)
这些汇编代码只是异常处理程序的开头一部分,可以看到每一个汇编段中,都有一条pushl_cfi $do_***的指令,该$do_***才是真正的异常处理程序(函数名,也是函数指针),现将该函数名压入栈中,然后通过jmp error_code指令跳转到$do_***函数中。error_code其实也是一段汇编代码,在下篇博文中断(二)中,我们将分析该段代码。
至此,中断描述符的初始化工作就告一段落。文中有问题的地方希望大家指正。qq:1193533825
中断(一)——中断描述符表的定义和初始化 (基于3.16-rc4)