• 中断——中断描述符表的定义和初始化(一) (基于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 entries
    3     .long idt_table
    4 
    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,%edi
    11     movl $early_idt_handlers,%eax12     movl $NUM_EXCEPTION_VECTORS,%ecx
    13 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,%eax
    19     addl $8,%edi
    20     loop 1b
    21 
    22     movl $256 - NUM_EXCEPTION_VECTORS,%ecx
    23     movl $ignore_int,%edx
    24     movl $(__KERNEL_CS << 16),%eax
    25     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,%edi
    31     loop 2b
    32         ...
    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 #endif
    12 
    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_32
    21     set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
    22 #else
    23     set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
    24 #endif
    25     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_MCE
    34     set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
    35 #endif
    36     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_EMULATION
    43     set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
    44     set_bit(IA32_SYSCALL_VECTOR, used_vectors);
    45 #endif
    46 
    47 #ifdef CONFIG_X86_32
    48     set_system_trap_gate(SYSCALL_VECTOR, &system_call);FIRST_EXTERNAL_VECTOR
    49     set_bit(SYSCALL_VECTOR, used_vectors);
    50 #endif
    51 
    52     /*
    53      * Set the IDT descriptor to a fixed read-only location, so that the
    54      * "sidt" instruction will not leak the location of the kernel, and
    55      * 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_64
    68     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 #endif
    72 }

    该函数对中断描述表的进行了部分初始化,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 time
    10      */
    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; };也可以看成是structu16 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_FRAME
    11     ASM_CLAC
    12     pushl_cfi $do_stack_segment
    13     jmp error_code
    14     CFI_ENDPROC
    15 END(stack_segment)
    16 
    17 ENTRY(alignment_check)
    18     RING0_EC_FRAME
    19     ASM_CLAC
    20     pushl_cfi $do_alignment_check
    21     jmp error_code
    22     CFI_ENDPROC
    23 END(alignment_check)
    24 
    25 ENTRY(divide_error)
    26     RING0_INT_FRAME
    27     ASM_CLAC
    28     pushl_cfi $0            # no error code
    29     pushl_cfi $do_divide_error
    30     jmp error_code
    31     CFI_ENDPROC
    32 END(divide_error)

    这些汇编代码只是异常处理程序的开头一部分,可以看到每一个汇编段中,都有一条pushl_cfi $do_***的指令,该$do_***才是真正的异常处理程序(函数名,也是函数指针),现将该函数名压入栈中,然后通过jmp error_code指令跳转到$do_***函数中。error_code其实也是一段汇编代码,如下所示:

     1 error_code:
     2     /* the function address is in %gs's slot on the stack */
     3     pushl_cfi %fs
     4     /*CFI_REL_OFFSET fs, 0*/
     5     pushl_cfi %es
     6     /*CFI_REL_OFFSET es, 0*/
     7     pushl_cfi %ds
     8     /*CFI_REL_OFFSET ds, 0*/
     9     pushl_cfi %eax
    10     CFI_REL_OFFSET eax, 0
    11     pushl_cfi %ebp
    12     CFI_REL_OFFSET ebp, 0
    13     pushl_cfi %edi
    14     CFI_REL_OFFSET edi, 0
    15     pushl_cfi %esi
    16     CFI_REL_OFFSET esi, 0
    17     pushl_cfi %edx
    18     CFI_REL_OFFSET edx, 0
    19     pushl_cfi %ecx
    20     CFI_REL_OFFSET ecx, 0
    21     pushl_cfi %ebx
    22     CFI_REL_OFFSET ebx, 0
    23     cld
    24     movl $(__KERNEL_PERCPU), %ecx
    25     movl %ecx, %fs
    26     UNWIND_ESPFIX_STACK
    27     GS_TO_REG %ecx
    28     movl PT_GS(%esp), %edi        # get the function address
    29     movl PT_ORIG_EAX(%esp), %edx    # get the error code
    30     movl $-1, PT_ORIG_EAX(%esp)    # no syscall to restart
    31     REG_TO_PTGS %ecx
    32     SET_KERNEL_GS %ecx
    33     movl $(__USER_DS), %ecx
    34     movl %ecx, %ds
    35     movl %ecx, %es
    36     TRACE_IRQS_OFF
    37     movl %esp,%eax            # pt_regs pointer
    38     call *%edi
    39     jmp ret_from_exception
    40     CFI_ENDPROC
    41 END(page_fault)

    该片段来自arch/x86/kernel/entry_32.S文件中。代码的开始部分3-22行,对寄存器进行压栈操作,因为这些寄存器将要在随后的异常处理程序中用到,所以事先要保存。最后可以看到在38行,执行了call %edi命令,调用了最终的异常处理程序,在28行可以看到将异常处理程序地址存入了edi寄存器中。第39行通过跳入ret_from_exception中,返回被中断的进程。

    至此,中断描述符的初始化工作就告一段落。文中有问题的地方希望大家指正。qq:1193533825

  • 相关阅读:
    dubbo学习(一)认识
    MySQL学习(九)小结
    MySQL学习(八)删除表数据
    MySQL学习(六)change-buffer
    RPC 学习(一)认识
    MySQL学习(五)事务隔离
    MySQL学习(四)死锁及死锁检测
    计算机操作系统 --- 进程和进程的上下文切换
    MySQL 学习(三)事务学习
    消息队列(七)--- RocketMQ延时发送和消息重试(半原创)
  • 原文地址:https://www.cnblogs.com/liangning/p/3871293.html
Copyright © 2020-2023  润新知