继续走,start_kernel的583行,sort_main_extable,把编译期间,kbuild设置的异常表,也就是__start___ex_table和__stop___ex_table之中的所有元素进行排序。
584行,调用trap_init函数,重要的函数,初始化中断向量表。该函数来自arch/x86/kernel/traps.c
882void __init trap_init(void) 883{ 884 int i; 885 886#ifdef CONFIG_EISA 887 void __iomem *p = early_ioremap(0x0FFFD9, 4); 888 889 if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24)) 890 EISA_bus = 1; 891 early_iounmap(p, 4); 892#endif 893 894 set_intr_gate(0, ÷_error); 895 set_intr_gate_ist(1, &debug, DEBUG_STACK); 896 set_intr_gate_ist(2, &nmi, NMI_STACK); 897 /* int3 can be called from all */ 898 set_system_intr_gate_ist(3, &int3, DEBUG_STACK); 899 /* int4 can be called from all */ 900 set_system_intr_gate(4, &overflow); 901 set_intr_gate(5, &bounds); 902 set_intr_gate(6, &invalid_op); 903 set_intr_gate(7, &device_not_available); 904#ifdef CONFIG_X86_32 905 set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS); 906#else 907 set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK); 908#endif 909 set_intr_gate(9, &coprocessor_segment_overrun); 910 set_intr_gate(10, &invalid_TSS); 911 set_intr_gate(11, &segment_not_present); 912 set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK); 913 set_intr_gate(13, &general_protection); 914 set_intr_gate(14, &page_fault); 915 set_intr_gate(15, &spurious_interrupt_bug); 916 set_intr_gate(16, &coprocessor_error); 917 set_intr_gate(17, &alignment_check); 918#ifdef CONFIG_X86_MCE 919 set_intr_gate_ist(18, &machine_check, MCE_STACK); 920#endif 921 set_intr_gate(19, &simd_coprocessor_error); 922 923 /* Reserve all the builtin and the syscall vector: */ 924 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++) 925 set_bit(i, used_vectors); 926 927#ifdef CONFIG_IA32_EMULATION 928 set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall); 929 set_bit(IA32_SYSCALL_VECTOR, used_vectors); 930#endif 931 932#ifdef CONFIG_X86_32 933 if (cpu_has_fxsr) { 934 printk(KERN_INFO "Enabling fast FPU save and restore... "); 935 set_in_cr4(X86_CR4_OSFXSR); 936 printk("done./n"); 937 } 938 if (cpu_has_xmm) { 939 printk(KERN_INFO 940 "Enabling unmasked SIMD FPU exception support... "); 941 set_in_cr4(X86_CR4_OSXMMEXCPT); 942 printk("done./n"); 943 } 944 945 set_system_trap_gate(SYSCALL_VECTOR, &system_call); 946 set_bit(SYSCALL_VECTOR, used_vectors); 947#endif 948 949 /* 950 * Should be a barrier for any external CPU state: 951 */ 952 cpu_init(); 953 954 x86_init.irqs.trap_init(); 955} |
我们没有配置CONFIG_EISA编译选项,所以886~892行代码忽略。894~921行,对中断向量表0 - 19号异常设置对应的服务程序。我们看到主要有四种类型的设置函数:
set_intr_gate
set_system_intr_gate
set_system_trap_gate
set_task_gate
其实这里涉及到一个x86体系的“门”概念,这里简单介绍一下:x86保护模式下物理地址段具有4种特级——0到4级——,而Linux只使用了表示内核态的0级和用户态的3级。当异常和中断发生时,必然会引起地址的转换,比如从3级段中,转移到0级段中去执行相应的代码。如何转移?Intel提供了一个审查机制,来保护我们的状态切换,这就是“门”的概念。
X86体系一个提供了四种门,即任务门(Task Gate)、中断门(Interrupt Gate)、陷阱门(Trap Gate)和调用门(Call Gate)。Linux只使用了前三种门的描述符,我们看到上面四种函数分别就对应这些门的设置。这里插一句,内核2.4.0以后的版本进行进程切换的时候就不使用任务门了,因为描述一个进程所需要的信息远远多于一个任务门所能表达的内容,因为后者仅仅是一个硬件上的概念。所以,我们看到905行,内核只有双重故障异常,也就是8号中断向量使用到了任务门,且仅仅是32位x86系统中。
有些函数还加入了_ist后缀,不过不管是set_intr_gate还是set_intr_gate_ist函数,最终都调用_set_gate函数,并把中断号、门类型、处理函数地址、DPL的值、ist和目录段寄存器作为参数传给它:
static inline void _set_gate(int gate, unsigned type, void *addr, unsigned dpl, unsigned ist, unsigned seg) { gate_desc s; pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); /* * does not need to be atomic because it is only done once at * setup time */ write_idt_entry(idt_table, gate, &s); } |
gate_desc结构我们已经见过了,这个结构是一个中断描述符,我们在“初始化中断描述符表”中设置了256个这样的门描述符,idt_table就是门描述符所组成的一个表的首地址,也叫中断描述符表(进入保护模式后,中断向量表IDT的每个表项不再是4个字节,而是8个字节的门描述符gate_desc)。我们看到先是调用pack_gate根据所传进来的参数初始化门描述符gate_desc:
static inline void pack_gate(gate_desc *gate, unsigned char type,
unsigned long base, unsigned dpl, unsigned flags,
unsigned short seg)
{
gate->a = (seg << 16) | (base & 0xffff);
gate->b = (base & 0xffff0000) |
(((0x80 | type | (dpl << 5)) & 0xff) << 8);
}
我们看到,32位的x86系统,传递进来的ist参数是没有用的,所以加不加_ist后缀意义不大,随后调用write_idt_entry设置中断描述符表的某个元素:
#define write_idt_entry(dt, entry, g) /
native_write_idt_entry(dt, entry, g)
static inline void native_write_idt_entry(gate_desc *idt, int entry,
const gate_desc *gate)
{
memcpy(&idt[entry], gate, sizeof(*gate));
}
好了,trap_init函数设置了中断描述符表的前19个表项后,在945行还做了一个及其重要的工作,就是设置一个系统门:
#define IA32_SYSCALL_VECTOR 0x80
#ifdef CONFIG_X86_32
# define SYSCALL_VECTOR 0x80
#endif
这就是Linux的系统调用对应的中断门,我们看到它的中断向量号是0x80号。随后952行调用cpu_init()函数,来自arch/x86/kernel/cpu/common.c:
1200void __cpuinit cpu_init(void) 1201{ 1202 int cpu = smp_processor_id(); 1203 struct task_struct *curr = current; 1204 struct tss_struct *t = &per_cpu(init_tss, cpu); 1205 struct thread_struct *thread = &curr->thread; 1206 1207 if (cpumask_test_and_set_cpu(cpu, cpu_initialized_mask)) { 1208 printk(KERN_WARNING "CPU#%d already initialized!/n", cpu); 1209 for (;;) 1210 local_irq_enable(); 1211 } 1212 1213 printk(KERN_INFO "Initializing CPU#%d/n", cpu); 1214 1215 if (cpu_has_vme || cpu_has_tsc || cpu_has_de) 1216 clear_in_cr4(X86_CR4_VME|X86_CR4_PVI|X86_CR4_TSD|X86_CR4_DE); 1217 1218 load_idt(&idt_descr); 1219 switch_to_new_gdt(cpu); 1220 1221 /* 1222 * Set up and load the per-CPU TSS and LDT 1223 */ 1224 atomic_inc(&init_mm.mm_count); 1225 curr->active_mm = &init_mm; 1226 BUG_ON(curr->mm); 1227 enter_lazy_tlb(&init_mm, curr); 1228 1229 load_sp0(t, thread); 1230 set_tss_desc(cpu, t); 1231 load_TR_desc(); 1232 load_LDT(&init_mm.context); 1233 1234 t->x86_tss.io_bitmap_base = offsetof(struct tss_struct, io_bitmap); 1235 1236#ifdef CONFIG_DOUBLEFAULT 1237 /* Set up doublefault TSS pointer in the GDT */ 1238 __set_tss_desc(cpu, GDT_ENTRY_DOUBLEFAULT_TSS, &doublefault_tss); 1239#endif 1240 1241 clear_all_debug_regs(); 1242 1243 /* 1244 * Force FPU initialization: 1245 */ 1246 if (cpu_has_xsave) 1247 current_thread_info()->status = TS_XSAVE; 1248 else 1249 current_thread_info()->status = 0; 1250 clear_used_math(); 1251 mxcsr_feature_mask_init(); 1252 1253 /* 1254 * Boot processor to setup the FP and extended state context info. 1255 */ 1256 if (smp_processor_id() == boot_cpu_id) 1257 init_thread_xstate(); 1258 1259 xsave_init(); 1260} |
首先获得当前cpu的编号,然后获得0号进程的task_struct和thread_struct以及本cpu上的tss结构。随后1218行通过load_idt函数加载刚刚设置好的idt_descr数组首地址到IDTR寄存器;219行加载本CPU的全局描述符表地址:
void switch_to_new_gdt(int cpu)
{
struct desc_ptr gdt_descr;
gdt_descr.address = (long)get_cpu_gdt_table(cpu);
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);
/* Reload the per-cpu base */
load_percpu_segment(cpu);
}
随后1225行加载0号进程的active_mm指针指向全局变量init_mm;后面还做了一些其他的初始化工作。这个函数的所做的工作很多前面已经完成了,这里只是再次确保这些规定动作没有任何遗漏,充分地显示了Linux内核的完整性和健壮性。
trap_init的最后一行调用x86_init.irqs.trap_init函数,也就是x86_init_noop,是个空函数,所以收工了。我们看到trap_init函数主要是对包括大部分异常的服务程序设置。至于中断门、陷阱门及系统门的相关基础知识,请访问我的博客“中断描述符表”
http://blog.csdn.net/yunsongice/archive/2010/02/11/5306387.aspx。