LINUX中断描述符初始化

系统 1822 0
LINUX中断描述符初始化

@CopyLeft by ICANTH I Can do ANy THing that I CAN THink ~

Author WenHui WuHan University 2012-6-4

硬件产生中断之后,需要通过门描述符来寻找中断的处理程序入口。门描述符和段描述符一样,8个字节。门描述符大体分为:段偏移、段选择子以及DPL。段选择子用于在GDT中寻找到门段基址,DPL用于控制当前进程中断或异常访问权限。当发生中断时,将门描述符所指向的段基地放入%cs,将段偏移放入%eip,转入相应服务。

门描述符结构如下:

clip_image002

任务门描述符 :用于在发生中断时调度相应进程

中断门描述符 :描述中断处理程序所在段选择子和段内偏移值。据此修改EIP及关闭中断Eflags的IT标识位

陷阱门描述符 :与中断门描述符一样,但不关中断

中断描述符表IDT( Interrupt Descriptor Table )用于存放256个门描述符,对应256个中断向量。其中IA32规定前0~31号固定向量用于异常。寄存器 idtr 为门描述符表IDT的物理地址。根据向量寻找基对应门描述符,并将中断或异常程序加载过程下图所示:

clip_image004

LINUX初始化中断描述符表分两步:初步初始化,以及最终初始化。初步初始化在head.S文件: http://os1a.cs.columbia.edu/lxr/source/arch/x86/kernel/head_32.S

中断描述符表初步初始化

1)声明256个门描述符的IDT表空间。

    
    
    
      701
    
     idt_descr:
  
    
    
    
      702
    
             .word IDT_ENTRIES*8-1           # idt contains 256 entries
  
    
    
    
      703
    
             .long idt_table
  

2)设置指向IDT表地址的寄存器 ldtr

    
    
    
      425
    
             lgdt early_gdt_descr
  
    
    
    
      
        426
      
    
    
               lidt idt_descr
    
  
    
    
    
      427
    
             ljmp $(__KERNEL_CS),$1f
  
    
    
    
      428
    
     1:      movl $(__KERNEL_DS),%eax        # reload all the segment registers
  

2)初始化256个门描述符。对于每个门描述符,段选择子都指向内核段,段偏移都指向igore_int函数,该函数只打印一句话:“哥们,别急,俺还没被真正初始化勒!~”。

    
    
    
      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
    
      */
  
    
    
    
      502
    
     setup_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 */
  
    
    
    
      507
    
  
    
    
    
      508
    
             lea idt_table,%edi
  
    
    
    
      509
    
             mov $256,%ecx
  
    
    
    
      510
    
     rp_sidt:
  
    
    
    
      511
    
             movl %eax,(%edi)
  
    
    
    
      512
    
             movl %edx,4(%edi)
  
    
    
    
      513
    
             addl $8,%edi
  
    
    
    
      514
    
             dec %ecx
  
    
    
    
      515
    
             jne rp_sidt
  
中断描述符表最终初始化

中断描述符表最终初始化分为两部分:异常和中断,分别在函数trap_init(void)和init_IRQ(void)中实现,都由系统初始化入口函数start_kernel()所调用。对于特定异常,其处理异常函数预先已经设定好;但对于特定异步中断,由于需要捕获设备I/O中断的程序数目不定,所以得采用特定数据结构来对irq及其action进行描述。

trap_init: http://os1a.cs.columbia.edu/lxr/source/arch/x86/kernel/traps.c#830

do_IRQ:

1 )异常部分最终初始化

由于IA32规定异常所对应的固定向量,所以直接调用set_trap_gate()、set_intr_gate()、set_system_gate()、set_system_intr_gate()、set_task_gate()、set_intr_gate_ist()设置异常处理函数、初始相应异常。而这些函数,都只是_set_gate宏的封装而已。异常处理函数由宏定义DO_ERROR生成。其调用关系如下:

clip_image006

在trap_init(void)函数中,调用在set_intr_gate_ist()设置“stack exception”的12号中断门描述符。set_intr_gate_ist是设置中断描述符函数_set_gate的一层封装,给_set_gate传入异常处理函数stack_segment作为门描述符的偏移地址,传入 __KERNEL_CS 作为段选择子,传入GATE_INTERRUPT表示中断门描述符类型,传入DPL = 0表示只能由内核态访问、而不能由用户调用。

_set_gate函数中,其调用pack_gate()组装成一个门描述符格式,并调用write_idt_entry写入IDT表中相应描述符。stack_segment函数压入do_stack_segment函数地址并跳转到error_code汇编代码进行处理。具体代码(/arch/x86/include/asm/desc.h)如下:

    
    
    
      830
    
     void 
    
      __init
    
    
      trap_init
    
    (void)
  
    
    
    
      831
    
     {
  
    
    
    
      
        857
      
    
    
               
      
        set_intr_gate_ist
      
      (12, &
      
        stack_segment
      
      , 
      
        STACKFAULT_STACK
      
      );
    
  
    
    
    
      898
    
             
    
      x86_init
    
    .
    
      irqs
    
    .
    
      trap_init
    
    (); /* 初始化该平台x86的特定设备 */
  
    
    
    
      899
    
     }
  
     
  
    
    
    
      383
    
     static inline void 
    
      set_intr_gate_ist
    
    (int 
    
      n
    
    , void *
    
      addr
    
    , unsigned 
    
      ist
    
    )
  
    
    
    
      384
    
     {
  
    
    
    
      385
    
             
    
      BUG_ON
    
    ((unsigned)
    
      n
    
     > 0xFF);
  
    
    
    
      386
    
             
    
      _set_gate
    
    (
    
      n
    
    , 
    
      GATE_INTERRUPT
    
    , 
    
      addr
    
    , 0, 
    
      ist
    
    , 
    
      __KERNEL_CS
    
    );
  
    
    
    
      387
    
     }
  
     
  
    
    
    
      312
    
     static inline void 
    
      _set_gate
    
    (int 
    
      gate
    
    , unsigned 
    
      type
    
    , void *
    
      addr
    
    ,
  
    
    
    
      313
    
                                  unsigned 
    
      dpl
    
    , unsigned 
    
      ist
    
    , unsigned 
    
      seg
    
    )
  
    
    
    
      314
    
     {
  
    
    
    
      315
    
             
    
      gate_desc
    
    
      s
    
    ;
  
    
    
    
      316
    
             
    
      pack_gate
    
    (&
    
      s
    
    , 
    
      type
    
    , (unsigned long)
    
      addr
    
    , 
    
      dpl
    
    , 
    
      ist
    
    , 
    
      seg
    
    );
  
    
    
    
      317
    
             /*
  
    
    
    
      318
    
              * does not need to be atomic because it is only done once at
  
    
    
    
      319
    
              * setup time
  
    
    
    
      320
    
              */
  
    
    
    
      321
    
             
    
      write_idt_entry
    
    (
    
      idt_table
    
    , 
    
      gate
    
    , &
    
      s
    
    );
  
    
    
    
      322
    
     }
  
     
  
    
    
    
      064
    
     static inline void 
    
      pack_gate
    
    (
    
      gate_desc
    
     *
    
      gate
    
    , unsigned char 
    
      type
    
    ,
  
    
    
    
      065
    
                                  unsigned long 
    
      base
    
    , unsigned 
    
      dpl
    
    , unsigned 
    
      flags
    
    ,
  
    
    
    
      066
    
                                  unsigned short 
    
      seg
    
    )
  
    
    
    
      067
    
     {
  
    
    
    
      068
    
             
    
      gate
    
    ->
    
      a
    
     = (
    
      seg
    
     << 16) | (
    
      base
    
     & 0xffff);
  
    
    
    
      069
    
             
    
      gate
    
    ->
    
      b
    
     = (
    
      base
    
     & 0xffff0000) |
  
    
    
    
      070
    
                       (((0x80 | 
    
      type
    
     | (
    
      dpl
    
     << 5)) & 0xff) << 8);
  
    
    
    
      071
    
     }
  
     
  
    
    
    
      115
    
     static inline void 
    
      native_write_idt_entry
    
    (
    
      gate_desc
    
     *
    
      idt
    
    , int 
    
      entry
    
    ,
  
    
    
    
      116
    
                                               const 
    
      gate_desc
    
     *
    
      gate
    
    )
  
    
    
    
      117
    
     {
  
    
    
    
      118
    
             
    
      memcpy
    
    (&
    
      idt
    
    [
    
      entry
    
    ], 
    
      gate
    
    , sizeof(*
    
      gate
    
    ));
  
    
    
    
      119
    
     }
  
     
  
    然异常12号处理者stack_segment,何许人也?在/arch/x86/kernel/entry_32.S中定义如下:
  
    
    
    
      1014
    
     ENTRY(stack_segment)
  
    
    
    
      1015
    
             RING0_EC_FRAME
  
    
    
    
      
        1016
      
    
    
               pushl $do_stack_segment
    
  
    
    
    
      1017
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      
        1018
      
    
    
               jmp error_code
    
  
    
    
    
      1019
    
             CFI_ENDPROC
  
    
    
    
      1020
    
     END(stack_segment)
  

有的异常,例如stack_segment,系统由硬件产生错误码并压入栈中。另外一些异常,系统将不产生异常,例如overflow,为了统一中断、产生错误码的异常和不产生错误码的异常的栈内存布局,故“人为地”pushl $0。

    
    
    
      958
    
     ENTRY(overflow)
  
    
    
    
      959
    
             RING0_INT_FRAME
  
    
    
    
      
        960
      
    
    
               pushl $0
    
  
    
    
    
      961
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      
        962
      
    
    
               pushl $do_overflow
    
  
    
    
    
      963
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      
        964
      
    
    
               jmp error_code
    
  
    
    
    
      965
    
             CFI_ENDPROC
  
    
    
    
      966
    
     END(overflow)
  

在error_code中,首先保存中断寄存器上下文等,其次调用do_stack_segment进行处理,最后调用ret_from_exception进行善后工作,代码如下:

    
    
    
      1291
    
     error_code:
  
    
    
    
      1292
    
             /* the function address is in %gs's slot on the stack */
  
    
    
    
      1293
    
             pushl %fs
  
    
    
    
      1294
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1295
    
             /*CFI_REL_OFFSET fs, 0*/
  
    
    
    
      1296
    
             pushl %es
  
    
    
    
      1297
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1298
    
             /*CFI_REL_OFFSET es, 0*/
  
    
    
    
      1299
    
             pushl %ds
  
    
    
    
      1300
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1301
    
             /*CFI_REL_OFFSET ds, 0*/
  
    
    
    
      1302
    
             pushl %eax
  
    
    
    
      1303
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1304
    
             CFI_REL_OFFSET eax, 0
  
    
    
    
      1305
    
             pushl %ebp
  
    
    
    
      1306
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1307
    
             CFI_REL_OFFSET ebp, 0
  
    
    
    
      1308
    
             pushl %edi
  
    
    
    
      1309
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1310
    
             CFI_REL_OFFSET edi, 0
  
    
    
    
      1311
    
             pushl %esi
  
    
    
    
      1312
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1313
    
             CFI_REL_OFFSET esi, 0
  
    
    
    
      1314
    
             pushl %edx
  
    
    
    
      1315
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1316
    
             CFI_REL_OFFSET edx, 0
  
    
    
    
      1317
    
             pushl %ecx
  
    
    
    
      1318
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1319
    
             CFI_REL_OFFSET ecx, 0
  
    
    
    
      1320
    
             pushl %ebx
  
    
    
    
      1321
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      1322
    
             CFI_REL_OFFSET ebx, 0
  
    
    
    
      1323
    
             cld
  
    
    
    
      1324
    
             movl $(__KERNEL_PERCPU), %ecx
  
    
    
    
      1325
    
             movl %ecx, %fs
  
    
    
    
      1326
    
             UNWIND_ESPFIX_STACK   /* 用于处理系统栈不是32位的情况 */
  
    
    
    
      1327
    
             GS_TO_REG %ecx        /* 把%gs存入%ecx中 */
  
    
    
    
      1328
    
             movl PT_GS(%esp), %edi          # get the function address
  
    
    
    
      1329
    
             movl PT_ORIG_EAX(%esp), %edx    # get the error code
  
    
    
    
      
        1330
      
    
    
               movl $-1, PT_ORIG_EAX(%esp)     # no syscall to restart
    
  
    
    
    
      1331
    
             REG_TO_PTGS %ecx      /* 把%gs的值存入栈的%gs中,即处理代码指针位置 */
  
    
    
    
      1332
    
             SET_KERNEL_GS %ecx
  
    
    
    
      1333
    
             movl $(__USER_DS), %ecx
  
    
    
    
      1334
    
             movl %ecx, %ds
  
    
    
    
      1335
    
             movl %ecx, %es
  
    
    
    
      1336
    
             TRACE_IRQS_OFF
  
    
    
    
      
        1337
      
    
    
               movl %esp,%eax                  # pt_regs pointer
    
  
    
    
    
      1338
    
             call *%edi
  
    
    
    
      1339
    
             jmp ret_from_exception
  
    
    
    
      1340
    
             CFI_ENDPROC
  
    
    
    
      1341
    
     END(page_fault)
  

在error_code中执行在1338行call *edi之前,栈的内存布局如下图。

clip_image008

在error_code处理“栈异常”处理时,call *edi = call do_stack_segment,do_stack_segment函数由DO_ERROR宏生成。error_code给do_stack_segment函数传入regs参数时,由ABI规范规定,eax为第一个参数 struct pt_regs regs。pt_regs与栈内存布局相同,其结构体如下:

    
    
    
      021
    
     struct 
    
      pt_regs
    
     {
  
    
    
    
      022
    
             long 
    
      ebx
    
    ;
  
    
    
    
      023
    
             long 
    
      ecx
    
    ;
  
    
    
    
      024
    
             long 
    
      edx
    
    ;
  
    
    
    
      025
    
             long 
    
      esi
    
    ;
  
    
    
    
      026
    
             long 
    
      edi
    
    ;
  
    
    
    
      027
    
             long 
    
      ebp
    
    ;
  
    
    
    
      028
    
             long 
    
      eax
    
    ;
  
    
      029
    
             int  
    
      xds
    
    ;
  
    
      030
    
             int  
    
      xes
    
    ;
  
    
      031
    
             int  
    
      xfs
    
    ;
  
    
      032
    
             int  
    
      xgs
    
    ;     /* 对应%gs,对于异常而言是处理器代码 */
  
    
      033
    
             long 
    
      orig_eax
    
    ; /* 对应于内存布局中的error code或vector */
  
    
    
    
      034
    
             long 
    
      eip
    
    ;
  
    
    
    
      035
    
             int  
    
      xcs
    
    ;
  
    
    
    
      036
    
             long 
    
      eflags
    
    ;
  
    
    
    
      037
    
             long 
    
      esp
    
    ;
  
    
    
    
      038
    
             int  
    
      xss
    
    ;
  
    
    
    
      039
    
     };
  

stack_segment函数是“ 栈异常 ”的异常处理函数,由 DO_ERROR宏 产生:

    
    
    
      215
    
     #ifdef CONFIG_X86_32
  
    
    
    
      216
    
    
      DO_ERROR
    
    (12, 
    
      SIGBUS
    
    , "stack segment", 
    
      stack_segment
    
    )
  
    
    
    
      217
    
     #endif
  
    
    
    
      183
    
     #define 
    
      DO_ERROR
    
    (
    
      trapnr
    
    , 
    
      signr
    
    , 
    
      str
    
    , 
    
      name
    
    )                              \
  
    
    
    
      184
    
    
      dotraplinkage
    
     void 
    
      do_
    
    ##name(struct 
    
      pt_regs
    
     *
    
      regs
    
    , long 
    
      error_code
    
    )     \
  
    
    
    
      185
    
     {                                                                       \
  
    
    
    
      186
    
             if (
    
      notify_die
    
    (
    
      DIE_TRAP
    
    , 
    
      str
    
    , 
    
      regs
    
    , 
    
      error_code
    
    , 
    
      trapnr
    
    , 
    
      signr
    
    )  \
  
    
    
    
      187
    
                                                             == 
    
      NOTIFY_STOP
    
    ) \
  
    
    
    
      188
    
                     return;                                                 \
  
    
    
    
      189
    
             
    
      conditional_sti
    
    (
    
      regs
    
    );                                          \
  
    
    
    
      
        190
      
    
    
               
      
        do_trap
      
      (
      
        trapnr
      
      , 
      
        signr
      
      , 
      
        str
      
      , 
      
        regs
      
      , 
      
        error_code
      
      , 
      
        NULL
      
      );            \
    
  
    
    
    
      191
    
     }
  

可见,do_stack_segment最后调用do_trap进行异常处理。

2 )中断描述符表中断部分最终初始化

由start_kernel()调用init_IRQ(void)进行中断最终初始化。其操作流程如下:

clip_image010

1) 将CPU0 IRQ0…IRQ15的vectors设置为0…15。对于CPU0的vecotr_irq而言,若其IRQ是PIC中断,则其vector早已由PIC固定,所以设置其IRQ0…IRQ15的vector为0…15。若是I/O APIC,由于其vector分配是可由OS动态设置,所以vector 0…15可能会被重新分配;

2) 本质上调用native_init_IRQ()函数对irq_desc、以及中断部分门描述符进行初始化,并针对CONFIG_4KSTACKS配置、协处理器模拟浮点运算等进行配置;

a、 调用init_ISA_irqs()初始化8259A可断控制器,并对相应中断请求线IRQ进行初始化、使其对应中断控制器irq_desc的操作函数为8259A操作接口函数;

b、 调用apic_intr_init()函数针对采取I/O APIC中断处理器的情况,对APIC中断处理器进行初始化工作;

c、 将调用set_intr_gate为系统中每根中断请求线IRQ地应的中断向量号设置了相应的中断门描述门, 其中断处理函数定义在interrupt数组中

d、 在PC平台下set_irq(2, &rq2)对从8259A中断控制器的第三根IRQ请求做特殊处理;

e、 irq_ctx_init函数用于在配置CONFIG_4KSTACK的情况下配置当前CPU的中断栈相关项。LINUX内核在未配置CONFIG_4KSTACK时,共享所中断进程的内核栈,内核栈为两页,即8K。在2.6版本时,增加了CONFIG_4KSTACK选项,将栈大小从两页减小至一页,为了应对栈的减少,故中断处理程序拥有自己的中断处理程序线,为原先共享栈的一半,即4K,每个CPU拥有一个中断栈。

    
    
    
      125
    
     void 
    
      __init
    
    
      init_IRQ
    
    (void)
  
    
    
    
      126
    
     {
  
    
    
    
      127
    
             int 
    
      i
    
    ;
  
    
    
    
      128
    
  
    
    
    
      129
    
             /*
  
    
    
    
      130
    
              * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
  
    
    
    
      131
    
              * If these IRQ's are handled by legacy interrupt-controllers like PIC,
  
    
    
    
      132
    
              * then this configuration will likely be static after the boot. If
  
    
    
    
      133
    
              * these IRQ's are handled by more mordern controllers like IO-APIC,
  
    
    
    
      134
    
              * then this vector space can be freed and re-used dynamically as the
  
    
    
    
      135
    
              * irq's migrate etc.
  
    
    
    
      136
    
              */
  
    
    
    
      137
    
             for (
    
      i
    
     = 0; 
    
      i
    
     < 
    
      legacy_pic
    
    ->
    
      nr_legacy_irqs
    
    ; 
    
      i
    
    ++)
  
    
    
    
      138
    
                     
    
      per_cpu
    
    (vector_irq, 0)[
    
      IRQ0_VECTOR
    
     + 
    
      i
    
    ] = 
    
      i
    
    ;
  
    
       
    
  
    /* intr_init()本质上调用native_init_IRQ()初始化。用x86_init.irqs.intr_init()函数对irq_desc、以及中断部分门描述符进行初始化。x86_init是
    
      x86_init_ops
    
    类型,集成针对x86特定平台的各种初始化工作,包含初始化PCI总线、中断、异常等,其中irqs就是针对中断进行初始化操作。*/
  
    
      140
    
             
    
      x86_init
    
    .
    
      irqs
    
    .
    
      intr_init
    
    ();
  
    
    
    
      141
    
     }
  

/arch/x86/include/asm/x86_init.h

    
      115
    
     /**
  
    
      116
    
      * struct x86_init_ops - functions for platform specific setup
  
    
      117
    
      *
  
    
      118
    
      */
  
    
      119
    
     struct 
    
      x86_init_ops
    
     {
  
    
    
    
      122
    
             struct 
    
      x86_init_irqs
    
                
    
      irqs
    
    ;
  
    
    
    
      124
    
             struct 
    
      x86_init_paging
    
              
    
      paging
    
    ;
  
    
      125
    
             struct 
    
      x86_init_timers
    
              
    
      timers
    
    ;
  
    
      127
    
             struct 
    
      x86_init_pci
    
                 
    
      pci
    
    ;
  
    
      128
    
     };
  
     
  
    
    
    
      047
    
     /**
  
    
    
    
      048
    
      * struct x86_init_irqs - platform specific interrupt setup
  
    
    
    
      049
    
      * @pre_vector_init:            init code to run before interrupt vectors
  
    
    
    
      050
    
      *                              are set up.
  
    
      051
    
      * @intr_init:                  interrupt init code
  
    
      052
    
      * @trap_init:                  platform specific trap setup
  
    
      053
    
      */
  
    
      054
    
     struct 
    
      x86_init_irqs
    
     {
  
    
      055
    
             void (*
    
      pre_vector_init
    
    )(void);
  
    
    
    
      056
    
             void (*
    
      intr_init
    
    )(void);
  
    
    
    
      057
    
             void (*
    
      trap_init
    
    )(void);
  
    
    
    
      058
    
     };
  

/arch/x86/kernel/x86_init.c

    
    
    
      029
    
     /*
  
    
    
    
      030
    
      * The platform setup functions are preset with the default functions
  
    
    
    
      031
    
      * for standard PC hardware.
  
    
    
    
      032
    
      */
  
    
    
    
      033
    
     struct 
    
      x86_init_ops
    
    
      x86_init
    
    
      __initdata
    
     = {
  
    
    
    
      051
    
             .
    
      irqs
    
     = {
  
    
    
    
      052
    
                     .
    
      pre_vector_init
    
            = 
    
      init_ISA_irqs
    
    ,
  
    
    
    
      053
    
                     .
    
      intr_init
    
                  = 
    
      native_init_IRQ
    
    ,
  
    
    
    
      054
    
                     .
    
      trap_init
    
                  = 
    
      x86_init_noop
    
    ,
  
    
    
    
      055
    
             },
  
    
    
    
      082
    
     };
  
    
    
    
      235
    
     void 
    
      __init
    
    
      native_init_IRQ
    
    (void)
  
    
    
    
      236
    
     {
  
    
    
    
      237
    
             int 
    
      i
    
    ;
  
    
    
    
      238
    
  
    
    
    
      239
    
             /* Execute any quirks before the call gates are initialised: */
  
    /* 调用init_ISA_irqs()初始化irq_desc[0…NR_IRQS] = {.status = IRQ_DISABLED, 
  
    .action = NULLL, .depth = 1}。并且对于irq0 … 15,
  
    将其handler = &i8259A_irq_type*/
  
    
    
    
      240
    
             
    
      x86_init
    
    .
    
      irqs
    
    .
    
      pre_vector_init
    
    ();
  
    
    
    
      241
    
  
    
    
    
      242
    
             
    
      apic_intr_init
    
    ();
  
    
    
    
      243
    
  
    
    
    
      244
    
             /*
  
    
    
    
      245
    
              * Cover the whole vector space, no vector can escape
  
    
    
    
      246
    
              * us. (some of these will be overridden and become
  
    
    
    
      247
    
              * 'special' SMP interrupts)
  
    
    
    
      248
    
              */
  
    
      /* 
    
    调用set_intr_gate()为每根IRQ对应的中断向量号设置了相应的中断门描述符 */
  
    
      249
    
             for (
    
      i
    
     = 
    
      FIRST_EXTERNAL_VECTOR
    
    ; 
    
      i
    
     < 
    
      NR_VECTORS
    
    ; 
    
      i
    
    ++) {
  
    
    
    
      250
    
                     /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
  
    
    
    
      251
    
                     if (!
    
      test_bit
    
    (
    
      i
    
    , 
    
      used_vectors
    
    ))
  
    
    
    
      252
    
                             
    
      set_intr_gate
    
    (
    
      i
    
    , 
    
      interrupt
    
    [
    
      i
    
    -
    
      FIRST_EXTERNAL_VECTOR
    
    ]);
  
    
    
    
      253
    
             }
  
    /* 系统若非I/O APIC,则IRQ3用于8259A从片的级联,故进行特殊处理 */
  
    
    
    
      255
    
             if (!
    
      acpi_ioapic
    
    )
  
    
    
    
      256
    
                     
    
      setup_irq
    
    (2, &
    
      irq2
    
    );
  
    
    
    
      257
    
  
    
    
    
      258
    
     #ifdef CONFIG_X86_32
  
    
    
    
      259
    
             /*
  
    
    
    
      260
    
              * External FPU? Set up irq13 if so, for
  
    
    
    
      261
    
              * original braindamaged IBM FERR coupling.
  
    
    
    
      262
    
              */
  
    
      /* 
    
    在处理器集成协处理器,而该协处理器没有浮点处理单元的情况下设置针对浮点计算异常的处理函数fpu_irq,该函数用软件模拟的方法来进行浮点数运算 */
  
    
      263
    
             if (
    
      boot_cpu_data
    
    .
    
      hard_math
    
     && !
    
      cpu_has_fpu
    
    )
  
    
    
    
      264
    
                     
    
      setup_irq
    
    (
    
      FPU_IRQ
    
    , &
    
      fpu_irq
    
    );
  
    /* 在内核选中CONFIG_4KSTACKS时,为系统中每一CPU设置中断、异常处理函数所需的内核态栈;在没有选中该选项的情况下,irq_ctx_init是个空语句 */
  
    
    
    
      266
    
             
    
      irq_ctx_init
    
    (
    
      smp_processor_id
    
    ());
  
    
    
    
      267
    
     #endif
  
    
    
    
      268
    
     }
  
    
    
    
      101
    
     void 
    
      __init
    
    
      init_ISA_irqs
    
    (void)
  
    
    
    
      102
    
     {
  
    
    
    
      103
    
             int 
    
      i
    
    ;
  
    
      /* 
    
    若存在LOCAL_APIC,则对Local APIC模块进行设置 */
  
    
      105
    
     #if 
    
      defined
    
    (CONFIG_X86_64) || 
    
      defined
    
    (CONFIG_X86_LOCAL_APIC)
  
    
    
    
      106
    
             
    
      init_bsp_APIC
    
    ();
  
    
    
    
      107
    
     #endif
  
    
      /* 
    
    初始化中断控制器8259A */
  
    
      108
    
             
    
      legacy_pic
    
    ->
    
      init
    
    (0);
  
    
    
    
      109
    
  
    
    
    
      110
    
             /*
  
    
    
    
      111
    
              * 16 old-style INTA-cycle interrupts:
  
    
    
    
      112
    
              */
  
    
      /* 
    
    系统中断老式处理方式是由两片8259A级联而成,每片可有8个IRQ,故两片最多初始化16个IRQ,但由于IRQ 2用于级联,故实际仅有15个有效IRQ。 */
  
    
      113
    
             for (
    
      i
    
     = 0; 
    
      i
    
     < 
    
      legacy_pic
    
    ->
    
      nr_legacy_irqs
    
    ; 
    
      i
    
    ++) {
  
    
    
    
      114
    
                     struct 
    
      irq_desc
    
     *
    
      desc
    
     = 
    
      irq_to_desc
    
    (
    
      i
    
    );
  
    
      115
    
  
    
      116
    
                     
    
      desc
    
    ->
    
      status
    
     = 
    
      IRQ_DISABLED
    
    ;
  
    
      117
    
                     
    
      desc
    
    ->
    
      action
    
     = 
    
      NULL
    
    ;
  
    
      118
    
                     
    
      desc
    
    ->
    
      depth
    
     = 1;
  
    
      119
    
  
    
    
    
      120
    
                     
    
      set_irq_chip_and_handler_name
    
    (
    
      i
    
    , &
    
      i8259A_chip
    
    ,
  
    
    
    
      121
    
                                                   
    
      handle_level_irq
    
    , "XT");
  
    
      122
    
             }
  
    
    
    
      123
    
     }
  

interrupt 数组

interrupt数组符号由汇编代码定义,其源码如下:

/arch/x86/kernel/entry_32.S

    
    
    
      819
    
     /*
  
    
    
    
      820
    
      * Build the entry stubs and pointer table with some assembler magic.
  
    
    
    
      821
    
      * We pack 7 stubs into a single 32-byte chunk, which will fit in a
  
    
    
    
      822
    
      * single cache line on all modern x86 implementations.
  
    
    
    
      823
    
      */
  
    
    
    
      824
    
     .section .init.rodata,"a"
  
    
    
    
      825
    
     ENTRY(interrupt)
  
    
    
    
      826
    
     .text
  
    
    
    
      827
    
             .p2align 5
  
    
    
    
      828
    
             .p2align CONFIG_X86_L1_CACHE_SHIFT
  
    
    
    
      829
    
     ENTRY(irq_entries_start)
  
    
      830
    
             RING0_INT_FRAME
  
    
      831
    
     vector=FIRST_EXTERNAL_VECTOR /* =0x20,0~31号内部中断 */
  
    
    
    
      
        832
      
    
    
       .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
    
  
    
    
    
      833
    
             .balign 32 /* 32字节对齐 */
  
    
    
    
      
        834
      
    
    
         .rept 7
    
  
    
    
    
      835
    
         .if vector < NR_VECTORS
  
    
    
    
      836
    
           .if vector <> FIRST_EXTERNAL_VECTOR
  
    /* 按照CFA规则修改前一个offset,以达4字节对齐。CFA标准(Call Frame Information), help a debugger create a reliable backtrace through functions. */
  
    
    
    
      837
    
             CFI_ADJUST_CFA_OFFSET -4 
  
    
    
    
      838
    
           .endif
  
    
    
    
      839
    
     1:      pushl $(~vector+0x80)   /* Note: always in signed byte range ,[-256 ~ -1] */
    
    
  
    
      840
    
             CFI_ADJUST_CFA_OFFSET 4         /* 按CFA规则4字节对齐 */
  
    
    
    
      841
    
           .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
  
    
    
    
      842
    
             jmp 2f
  
    
    
    
      843
    
           .endif
  
    
    
    
      844
    
           .previous
  
    
    
    
      845
    
             .long 1b
  
    
    
    
      846
    
           .text
  
    
    
    
      847
    
     vector=vector+1
  
    
    
    
      848
    
         .endif
  
    
    
    
      
        849
      
    
    
         .endr /* end of rep 7 */
    
  
    
    
    
      850
    
     2:      jmp common_interrupt
  
    
    
    
      
        851
      
    
    
       .endr /* end of rep (NR_VECTORS – FIRST_...) */
    
  
    
    
    
      852
    
     END(irq_entries_start)
  
    
    
    
      853
    
  
    
    
    
      854
    
     .previous
  
    
    
    
      855
    
     END(interrupt)
  

ENTRY(interrupt)通过伪指令.rept、.endr,将在代码段产生(NR_VECTORS - FIRST_EXTERNAL_VECTOR)个跳转到common_interrupt的汇编代码片段,起始地址是irq_entries_start;在数据段产生一个中断数组的符号interrupt,用于记录产生代码段中每个中断向量处理的汇编代码片段地址,在C语言中将interrupt符号作为中断数组变量导入:

    
      132
    
     extern void (*
    
      __initconst
    
    
      interrupt
    
    [
    
      NR_VECTORS
    
    -
    
      FIRST_EXTERNAL_VECTOR
    
    ])(void);
  

ENTRY(interrupt)编译之后,所生成的代码段和数据段内存布局如下:

clip_image012

ENTRIY(interrupt)汇编代码段主要由两个rept构成,外层rept循环(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7次,而每次内层rept循环7次,内层循环所产生的代码以32字节对齐,内层rept循环产生的代码如上图irq_entries_start中以粗黑方框表示。

以两层rept循环生成jmp common_interrupt的目的在于:

在内循环内,前6次循环产生的代码指令为:push和short jmp,而第7次产生的代码指令为:push和long jmp。push占2字节,short jmp占2字节,long jmp占5字节,故采取此种方式内层rept循环7次产生的代码大小为:6 * (2 + 2) + 2 + 5 = 31 字节。而外层循环以32字节对齐,相比于老版本每次都为push和long jmp而言,所以利用short jmp节省了内存开销。参见: http://didat.sprg.uniroma2.it/la/docs/la12-10.pdf

每个中断门描述符在将vector压入后都跳转到common_interrupt进行处理。common_interrupt在保存中断现场之后,跳转到do_IRQ进行中断函数处理,最后调用iret_from_intr进行中断返回、恢复中断上下文。

    
    
    
      863
    
     common_interrupt:
  
    
    
    
      864
    
             addl $-0x80,(%esp)      /* Adjust vector into the [-256,-1] range */
  
    
    
    
      865
    
             SAVE_ALL /* 宏定义,负责完成宏定义中断现场的保护工作 */
  
    
    
    
      866
    
             TRACE_IRQS_OFF
  
    
    
    
      867
    
             movl %esp,%eax
  
    
    
    
      
        868
      
    
    
               call do_IRQ
    
  
    
    
    
      
        869
      
    
    
               jmp ret_from_intr
    
  
    
    
    
      870
    
     ENDPROC(common_interrupt)
  
    
    
    
      195
    
     .macro SAVE_ALL
  
    
    
    
      196
    
             cld  /* 清除系统标志寄存器EFLAGS中的方向标志位DF,使%si、%di寄存器的值在每次字符串指令操作后自动+1,使自字符串从低地址到高地址方向处理 */
  
    
    
    
      197
    
             PUSH_GS
  
    
    
    
      198
    
             pushl %fs
  
    
    
    
      199
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      200
    
             /*CFI_REL_OFFSET fs, 0;*/
  
    
    
    
      201
    
             pushl %es
  
    
    
    
      202
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      203
    
             /*CFI_REL_OFFSET es, 0;*/
  
    
    
    
      204
    
             pushl %ds
  
    
    
    
      205
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      206
    
             /*CFI_REL_OFFSET ds, 0;*/
  
    
    
    
      207
    
             pushl %eax
  
    
    
    
      208
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      209
    
             CFI_REL_OFFSET eax, 0
  
    
    
    
      210
    
             pushl %ebp
  
    
    
    
      211
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      212
    
             CFI_REL_OFFSET ebp, 0
  
    
    
    
      213
    
             pushl %edi
  
    
    
    
      214
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
      215
    
             CFI_REL_OFFSET edi, 0
  
    
      216
    
             pushl %esi
  
    
      217
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      218
    
             CFI_REL_OFFSET esi, 0
  
    
    
    
      219
    
             pushl %edx
  
    
    
    
      220
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      221
    
             CFI_REL_OFFSET edx, 0
  
    
    
    
      222
    
             pushl %ecx
  
    
    
    
      223
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      224
    
             CFI_REL_OFFSET ecx, 0
  
    
    
    
      225
    
             pushl %ebx
  
    
    
    
      226
    
             CFI_ADJUST_CFA_OFFSET 4
  
    
    
    
      227
    
             CFI_REL_OFFSET ebx, 0
  
    
    
    
      228
    
             movl $(__USER_DS), %edx
  
    
    
    
      229
    
             movl %edx, %ds
  
    
    
    
      230
    
             movl %edx, %es
  
    
    
    
      231
    
             movl $(__KERNEL_PERCPU), %edx
  
    
    
    
      232
    
             movl %edx, %fs
  
    
    
    
      233
    
             SET_KERNEL_GS %edx
  
    
    
    
      234
    
     .endm
  

common_interrupt在调用 do_IRQ 之前中断栈内存布局如下:

clip_image014

LINUX中断描述符初始化


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论