在完成了最重要和最核心的分段、分页环境的初始化后,还有一个就是初始化中断描述符idt的工作。
341/*
342 * Initialize eflags. Some BIOS's leave bits like NT set. This would
343 * confuse the debugger if this code is traced.
344 * XXX - best to initialize before switching to protected mode.
345 */
346 pushl $0
347 popfl
348
349#ifdef CONFIG_SMP
350 cmpb $0, ready
351 jz 1f /* Initial CPU cleans BSS */
352 jmp checkCPUtype
3531:
354#endif /* CONFIG_SMP */
346、347行初始化CPU的eflags寄存器,对popf指令不熟的人可以重新学学汇编了。继续走:
355
356/*
357 * start system 32-bit setup. We need to re-do some of the things done
358 * in 16-bit mode for the "real" operations.
359 */
360 call setup_idt
361
360行,call setup_idt这里为每一个中断准备最早的处理函数,跳过checkCPUtype的若干行相关代码,我们来到502行:
490/*
491 * setup_idt
492 *
493 * sets up a idt with 256 entries pointing to
494 * ignore_int, interrupt gates. It doesn't actually load
495 * idt - that can be done only after paging has been enabled
496 * and the kernel moved to PAGE_OFFSET. Interrupts
497 * are enabled elsewhere, when we can be relatively
498 * sure everything is ok.
499 *
500 * Warning: %esi is live across this function.
501 */
502setup_idt:
503 lea ignore_int,%edx
504 movl $(__KERNEL_CS << 16),%eax
505 movw %dx,%ax /* selector = 0x0010 = cs */
506 movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
503行,获得ignore_int的地址,ignore_int在575行定义:
575ignore_int:
576 cld
577#ifdef CONFIG_PRINTK
578 pushl %eax
579 pushl %ecx
580 pushl %edx
581 pushl %es
582 pushl %ds
583 movl $(__KERNEL_DS),%eax
584 movl %eax,%ds
585 movl %eax,%es
586 cmpl $2,early_recursion_flag
587 je hlt_loop
588 incl early_recursion_flag
589 pushl 16(%esp)
590 pushl 24(%esp)
591 pushl 32(%esp)
592 pushl 40(%esp)
593 pushl $int_msg
594 call printk
595
596 call dump_stack
597
598 addl $(5*4),%esp
599 popl %ds
600 popl %es
601 popl %edx
602 popl %ecx
603 popl %eax
604#endif
605 iret
以上575~605这么一堆代码,就是ignore_int函数,把函数的入口地址赋给edx寄存器。函数的内容我们不详细讲解了,这里只简单说说,就是在early_recursion_flag不为2的情况下,调用printk内核打印函数,打印int_msg信息:
666int_msg:
667 .asciz "Unknown interrupt or fault at: %p %p %p/n"
回到setup_idt中,504行,重要的__KERNEL_CS登场了。这个宏是内核代码段的选择子,其定义在arch/x86/include/asm/segment.h的185行:
185#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
186#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8)
187#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS* 8 + 3)
188#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS* 8 + 3)
GDT_ENTRY_KERNEL_CS定义在同一个文件的149行,其值为2,那么__KERNEL_CS的值就是0x10,那么setup_idt中movl $(__KERNEL_CS << 16),%eax之后,eax寄存器中的值就是0x100000。150行,将ignore_int地址的低16位赋给ax,这样,eax寄存器中的低16就是ignore_int地址的低16位,高16位就是0x0010。
继续走,%dx的值又被赋成了$0x8E00,暂不管他。
508 lea idt_table,%edi
509 mov $256,%ecx
508行,edi寄存器中存放idt_table表的地址。这个表就是中断门描述符表,其在我们的arch/x86/include/asm/desc.h文件中定义:
34 extern gate_desc idt_table[];
中断门描述符表是一个数组,每一个元素都是一个中断门描述符gate_desc,其在文件arch/x86/include/asm/desc_defs.h中被定义成:
81 typedef struct gate_struct64 gate_desc;
从名字上就能看出,这个门描述符占据8个字节,64位,在同一个文件中定义:
51struct gate_struct64 {
52 u16 offset_low;
53 u16 segment;
54 unsigned ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1;
55 u16 offset_middle;
56 u32 offset_high;
57 u32 zero1;
58} __attribute__((packed));
这里面segment是段选择子,其他字段如上图所示。idt_table就是包含若干这样的门描述符的数组表,每个表项8个字节,表头被edi寄存器指向。
510rp_sidt:
511 movl %eax,(%edi)
512 movl %edx,4(%edi)
513 addl $8,%edi
514 dec %ecx
515 jne rp_sidt
516
随后,511、512行,中断门描述符表的第一项前2个字节被设置成ignore_int地址的低16位作为偏移,中间2个字节被设置成段选择子0x0010,后4个字节被设置成$0x8E00。随后513~515行设置256个这样的门描述符,每个门描述符内容一样,都是调用函数ignore_int作为中断服务程序。
517.macro set_early_handler handler,trapno
518 lea /handler,%edx
519 movl $(__KERNEL_CS << 16),%eax
520 movw %dx,%ax
521 movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
522 lea idt_table,%edi
523 movl %eax,8*/trapno(%edi)
524 movl %edx,8*/trapno+4(%edi)
525.endm
526
527 set_early_handler handler=early_divide_err,trapno=0
528 set_early_handler handler=early_illegal_opcode,trapno=6
529 set_early_handler handler=early_protection_fault,trapno=13
530 set_early_handler handler=early_page_fault,trapno=14
531
532 ret
上面代码改变几个中断/异常处理函数,把0/6/13/14号中断描述符表项设置成相应的处理函数,而527~530就是这些函数:early_divide_err、early_illegal_opcode、early_protection_fault、early_page_fault。这些函数又都是调用的early_fault函数:
552early_fault:
553 cld
554#ifdef CONFIG_PRINTK
555 pusha
556 movl $(__KERNEL_DS),%eax
557 movl %eax,%ds
558 movl %eax,%es
559 cmpl $2,early_recursion_flag
560 je hlt_loop
561 incl early_recursion_flag
562 movl %cr2,%eax
563 pushl %eax
564 pushl %edx /* trapno */
565 pushl $fault_msg
566 call printk
567#endif
568 call dump_stack
569hlt_loop:
570 hlt
571 jmp hlt_loop
572
573/* This is the default interrupt "handler" :-) */
574 ALIGN
主要是打印fault_msg信息:
669fault_msg:
670/* fault info: */
671 .ascii "BUG: Int %d: CR2 %p/n"
672/* pusha regs: */
673 .ascii " EDI %p ESI %p EBP %p ESP %p/n"
674 .ascii " EBX %p EDX %p ECX %p EAX %p/n"
675/* fault frame: */
676 .ascii " err %p EIP %p CS %p flg %p/n"
677 .ascii "Stack: %p %p %p %p %p %p %p %p/n"
678 .ascii " %p %p %p %p %p %p %p %p/n"
679 .asciz " %p %p %p %p %p %p %p %p/n"
当然,如果没有配置打印选项CONFIG_PRINTK,就关机(当然不可能没有设置)。