初始化异常服务

    技术2024-07-20  60

    5.7 初始化异常服务

    继续走,start_kernel583行,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, &divide_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种特级——04级——,而Linux只使用了表示内核态的0级和用户态的3级。当异常和中断发生时,必然会引起地址的转换,比如从3级段中,转移到0级段中去执行相应的代码。如何转移?Intel提供了一个审查机制,来保护我们的状态切换,这就是“门”的概念。

     

    X86体系一个提供了四种门,即任务门(Task Gate)、中断门(Interrupt Gate)、陷阱门(Trap Gate)和调用门(Call Gate)。Linux只使用了前三种门的描述符,我们看到上面四种函数分别就对应这些门的设置。这里插一句,内核2.4.0以后的版本进行进程切换的时候就不使用任务门了,因为描述一个进程所需要的信息远远多于一个任务门所能表达的内容,因为后者仅仅是一个硬件上的概念。所以,我们看到905行,内核只有双重故障异常,也就是8号中断向量使用到了任务门,且仅仅是32x86系统中。

     

    有些函数还加入了_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_structthread_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

     

    最新回复(0)