1. 首页
  2. 业界

Linux操作系统内核中断 异常

  中断:


  可屏蔽中断:所有有I/O设备请求的中断都是,被屏蔽的中断会一直被CPU 忽略,直到屏蔽位被重置。


  不可屏蔽中断:非常危险的事件引起(如硬件失败)。


  异常:


  处理器产生的(Fault,Trap,Abort)异常


  programmed exceptions(软中断):由程序员通过INT或INT3指令触发,通常当做trap处理,用处:实现系统调用。


  中断描述符表(IDT):256项,其中的每一项关联一个中断/异常处理过程,有三种类型:


  Task Gate Descriptor. Linux未使用该类型的描述符。


  Interrupt Gate Descriptor.用于处理中断。


  Trap Gate Descriptor. 用于处理异常。


  中断门: 用于硬件中断,DPL为0,不允许用户态直接使用int指令访问,硬件中断免去这一判断,因此可以在用户态响应中断,见set_intr_gate


  DPL3 陷阱门: 用于系统调用,DPL为3,允许用户态直接使用int指令访问,这样才能通过int80访问系统调用,只有80号向量属于此门,见 set_system_gate


  DPL0陷阱门: 用于CPU异常,不允许用户态直接使用int指令访问,硬件中断免去这一判断,因此可以在用户产生CPU异常,见set_trap_gate


  在指令执行过程中控制单元检测是否有中断/异常发生,如果有,等待该条指令执行完成以后,硬件按如下过程执行:


  确定 中断向量的编号i。


  从IDT表中得到第i个门描述符。(idtr指向IDT)


  由第i项中的选择符和gdtr 查到位于GDT中的段描述符,从而得到中断处理程序的基地址,而偏移量位于门描述符中。


  做权限检查:比较cs中的CPL和GDT中 段描述符的DPL,确保中断处理程序的特权级不低于调用者。对于programed exception 还需检查CPL与门描述符的DPL,还应确保CPL大于等于门的DPL。Why?因为INT指令允许用户态的进程产生中断信号,其向量值 可以为0到255的任一值,为了避免用户通过INT指令产生非法中断,在初始化的时候,将向量值为80H的门描述符(系统调用使用该门)的DPL设为3, 将其他需要避免访问的门描述符的DPL值设为0,这样在做权限检查的时候就可以检查出来非法的情况。


  检查是否发 生了特权级的变化,一般指是否由用户态陷入了内核态。如果是由用户态陷入了内核态,控制单元必须开始使用与新的特权级相关的堆栈a. 读tr寄存器,访问运行进程的tss段。why?因为任何进程从用户态陷入内核态都必须从TSS获得内核堆栈指针。


  b. 用与新特权级相关的栈段和栈指针装载ss和esp寄存器。这些值可以在进程的tss段中找到。


  c. 在新的栈(内核栈)中保存用户态的ss和esp,这些值指明了用户态相关栈的逻辑地址。


  若发生的是故障,用引起异常的指令 地址修改cs和eip寄存器的值,以使得这条指令在异常处理结束后能被再次执行


  在栈中保存eflags、cs和eip的内容


  如 果异常带有一个硬件出错码,则将它保存在栈中


  装载cs和eip寄存器,其值分别是在GDT中找到的段描述符段基址和IDT表中第i 个门的偏移量。这样就得到了中断/异常处理程序第一条指令的逻辑地址。


  从中断/异 常返回:


  中断/异常处理完后,相应的处理程序会执行一条iret指令,做了如下事情:


  1)用保存在 栈中的值装载cs、eip和eflags寄存器。如果一个硬件出错码曾被压入栈中,那么弹出这个硬件出错码


  2)检查处理程序的特权级是 否等于cs中最低两位的值(这意味着进程在被中断的时候是运行在内核态还是用户态)。若是内核态,iret终止执行;否则,转入3


  3) 从栈中装载ss和esp寄存器。这步意味着返回到与旧特权级相关的栈。


  4)检查ds、es、fs和gs段寄存器的内容,如果其中一个寄 存器包含的选择符是一个段描述符,并且特权级比当前特权级高,则清除相应的寄存器。这么做是防止怀有恶意的用户程序利用这些寄存器访问内核空间。


  关于硬件中断和异常的原理简单描述为:当中断到到来时,由硬件触发中断引脚,通过引脚号找到中断号,然后通过中断号从中断描述符表(IDT)中找到对应的项。从gdtr寄存器中获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址。权限检查。保存现场。装载cs和eip寄存器,其值分别是IDT表中第i想们描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。中断或异常返回后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程。


  中断流:


  中断描述符表的初始化


  在内核初始化过程中,setup_idt汇编语言函数用同一个中断门(即指向ignore_int中断处理程序)来填充所有这256个表项


  [plain] view plaincopyprint?/*


  * setup_idt


  *


  * sets up a idt with 256 entries pointing to


  * ignore_int, interrupt gates. It doesn’t actually load


  * idt – that can be done only after paging has been enabled


  * and the kernel moved to PAGE_OFFSET. Interrupts


  * are enabled elsewhere, when we can be relatively


  * sure everything is ok.


  *


  * Warning: %esi is live across this function.


  */


  setup_idt:


  lea ignore_int,%edx


  movl $(__KERNEL_CS 《 16),%eax


  movw %dx,%ax /* selector = 0x0010 = cs */


  movw $0x8E00,%dx /* interrupt gate – dpl=0, present */


  lea idt_table,%edi


  mov $256,%ecx


  rp_sidt:


  movl %eax,(%edi)


  movl %edx,4(%edi)


  addl $8,%edi


  dec %ecx


  jne rp_sidt


  .macro set_early_handler handler,trapno


  lea \handler,%edx


  movl $(__KERNEL_CS 《 16),%eax


  movw %dx,%ax


  movw $0x8E00,%dx /* interrupt gate – dpl=0, present */


  lea idt_table,%edi


  movl %eax,8*\trapno(%edi)


  movl %edx,8*\trapno+4(%edi)


  .endm


  set_early_handler handler=early_divide_err,trapno=0


  set_early_handler handler=early_illegal_opcode,trapno=6


  set_early_handler handler=early_protection_fault,trapno=13


  set_early_handler handler=early_page_fault,trapno=14


  ret


  在start_kernel中调用trap_init函数想idt表中添加项(主要是异常处理)


  [cpp] view plaincopyprint?void __init trap_init(void)


  {


  int i;


  #ifdef CONFIG_EISA


  void __iomem *p = early_ioremap(0x0FFFD9, 4);


  if (readl(p) == ‘E’ + (‘I’《8) + (‘S’《16) + (‘A’《24))


  EISA_bus = 1;


  early_iounmap(p, 4);


  #endif


  set_intr_gate(0, ÷_error);


  set_intr_gate_ist(1, &debug, DEBUG_STACK);


  set_intr_gate_ist(2, &nmi, NMI_STACK);


  /* int3 can be called from all */


  set_system_intr_gate_ist(3, &int3, DEBUG_STACK);


  /* int4 can be called from all */


  set_system_intr_gate(4, &overflow);


  set_intr_gate(5, &bounds);


  set_intr_gate(6, &invalid_op);


  set_intr_gate(7, &device_not_available);


  #ifdef CONFIG_X86_32


  set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);


  #else


  set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);


  #endif


  set_intr_gate(9, &coprocessor_segment_overrun);


  set_intr_gate(10, &invalid_TSS);


  set_intr_gate(11, &segment_not_present);


  set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);


  set_intr_gate(13, &general_protection);


  set_intr_gate(14, &page_fault);


  set_intr_gate(15, &spurious_interrupt_bug);


  set_intr_gate(16, &coprocessor_error);


  set_intr_gate(17, &alignment_check);


  #ifdef CONFIG_X86_MCE


  set_intr_gate_ist(18, &machine_check, MCE_STACK);


  #endif


  set_intr_gate(19, &simd_coprocessor_error);


  /* Reserve all the builtin and the syscall vector: */


  for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)


  set_bit(i, used_vectors);


  #ifdef CONFIG_IA32_EMULATION


  set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);


  set_bit(IA32_SYSCALL_VECTOR, used_vectors);


  #endif


  #ifdef CONFIG_X86_32


  if (cpu_has_fxsr) {


  printk(KERN_INFO “Enabling fast FPU save and restore… “);


  set_in_cr4(X86_CR4_OSFXSR);


  printk(“done.\n”);


  }


  if (cpu_has_xmm) {


  printk(KERN_INFO


  ”Enabling unmasked SIMD FPU exception support… “);


  set_in_cr4(X86_CR4_OSXMMEXCPT);


  printk(“done.\n”);


  }


  set_system_trap_gate(SYSCALL_VECTOR, &system_call);


  set_bit(SYSCALL_VECTOR, used_vectors);


  #endif


  /*


  * Should be a barrier for any external CPU state:


  */


  cpu_init();


  x86_init.irqs.trap_init();


  }


  异常处理


  异常处理程序有一个标准的结构,由以下三部分组成:


  1,在内核堆栈中保存大多数寄存器的内容(这部分用汇编语言实现)


  例如,对于除0异常的汇编


  [plain] view plaincopyprint?ENTRY(divide_error)


  RING0_INT_FRAME


  pushl $0 # no error code


  CFI_ADJUST_CFA_OFFSET 4


  pushl $do_divide_error


  CFI_ADJUST_CFA_OFFSET 4


  jmp error_code


  CFI_ENDPROC


  END(divide_error)


  其中入口divide_error为idt表中对应项的处理函数地址,也就是说,产生异常后首先跳到这里执行。当异常产生时,如果控制单元没有自动地把一个硬件出错代码插入到栈中,相应的汇编片段会含一条pushl $0指令,在栈中垫上一个空值。然后,把高级c函数的地址压入栈中,他的名字由异常处理程序名与do_前缀组成。然后跳转到error_code中执行


  [plain] view plaincopyprint?error_code:


  /* the function address is in %gs’s slot on the stack */


  pushl %fs


  CFI_ADJUST_CFA_OFFSET 4


  /*CFI_REL_OFFSET fs, 0*/


  pushl %es


  CFI_ADJUST_CFA_OFFSET 4


  /*CFI_REL_OFFSET es, 0*/


  pushl %ds


  CFI_ADJUST_CFA_OFFSET 4


  /*CFI_REL_OFFSET ds, 0*/


  pushl %eax


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET eax, 0


  pushl %ebp


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET ebp, 0


  pushl %edi


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET edi, 0


  pushl %esi


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET esi, 0


  pushl %edx


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET edx, 0


  pushl %ecx


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET ecx, 0


  pushl %ebx


  CFI_ADJUST_CFA_OFFSET 4


  CFI_REL_OFFSET ebx, 0


  cld


  movl $(__KERNEL_PERCPU), %ecx


  movl %ecx, %fs


  UNWIND_ESPFIX_STACK


  GS_TO_REG %ecx


  movl PT_GS(%esp), %edi # get the function address


  movl PT_ORIG_EAX(%esp), %edx # get the error code


  movl $-1, PT_ORIG_EAX(%esp) # no syscall to restart


  REG_TO_PTGS %ecx


  SET_KERNEL_GS %ecx


  movl $(__USER_DS), %ecx


  movl %ecx, %ds


  movl %ecx, %es


  TRACE_IRQS_OFF


  movl %esp,%eax # pt_regs pointer


  call *%edi


  jmp ret_from_exception


  error_code汇编代码主要完成大部分寄存器的保存,然后调用call *%edi代码调用上面保存在栈中的c函数执行。


  在linux2.6内核中,采用宏的方式定义这类do_函数:


  [cpp] view plaincopyprint?DO_ERROR_INFO(0, SIGFPE, “divide error”, divide_error, FPE_INTDIV, regs->ip)


  DO_ERROR(4, SIGSEGV, “overflow”, overflow)


  DO_ERROR(5, SIGSEGV, “bounds”, bounds)


  DO_ERROR_INFO(6, SIGILL, “invalid opcode”, invalid_op, ILL_ILLOPN, regs->ip)


  DO_ERROR(9, SIGFPE, “coprocessor segment overrun”, coprocessor_segment_overrun)


  DO_ERROR(10, SIGSEGV, “invalid TSS”, invalid_TSS)


  DO_ERROR(11, SIGBUS, “segment not present”, segment_not_present)


  #ifdef CONFIG_X86_32


  DO_ERROR(12, SIGBUS, “stack segment”, stack_segment)


  #endif


  我们对上面的宏,看一个


  [cpp] view plaincopyprint?#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \


  dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \


  { \


  siginfo_t info; \


  info.si_signo = signr; \


  info.si_errno = 0; \


  info.si_code = sicode; \


  info.si_addr = (void __user *)siaddr; \


  if (notify_die(DIE_TRAP, str, regs, error_code, trapnr, signr) \


  == NOTIFY_STOP) \


  return; \


  conditional_sti(regs); \


  do_trap(trapnr, signr, str, regs, error_code, &info); \


  }


  可见最后都调用了do_trap函数来执行。


  异常返回


  当执行异常处理的C函数终止时,程序执行一条jmp指令以跳转到ret_from_exception函数(上面的error_code汇编函数)


  [cpp] view plaincopyprint?ret_from_exception:


  preempt_stop(CLBR_ANY)


  ret_from_intr:


  GET_THREAD_INFO(%ebp)


  check_userspace:


  movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS


  movb PT_CS(%esp), %al


  andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax


  cmpl $USER_RPL, %eax


  /*当被中断的程序在中断发生运行时在内核态*/


  jb resume_kernel # not returning to v8086 or userspace


  /*在用户空间时*/


  ENTRY(resume_userspace)


  LOCKDEP_SYS_EXIT


  DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don’t miss an interrupt


  # setting need_resched or sigpending


  # between sampling and the iret


  TRACE_IRQS_OFF


  movl TI_flags(%ebp), %ecx


  andl $_TIF_WORK_MASK, %ecx # is there any work to be done on


  # int/exception return?


  jne work_pending


  jmp restore_all


  END(ret_from_exception)


  #ifdef CONFIG_PREEMPT


  ENTRY(resume_kernel)


  DISABLE_INTERRUPTS(CLBR_ANY)


  /*允许内核抢占时,执行need_resched*/


  cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?


  /*不等于0,被中断的程序重新开始执行*/


  jnz restore_all


  need_resched:


  movl TI_flags(%ebp), %ecx # need_resched set ?


  testb $_TIF_NEED_RESCHED, %cl


  jz restore_all


  testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?


  jz restore_all


  call preempt_schedule_irq


  jmp need_resched


  END(resume_kernel)


  #endif


  CFI_ENDPROC


  中断请求初始化


  对于每一个外设,要么以静态(声明为 static 类型的全局变量)或动态(调用request_irq 函数)的方式向 Linux 内核注册中断处理程序。不管以何种方式注册,都会声明或分配一块irqaction 结构(其中handler 指向中断服务程序),然后调用setup_irq() 函数,将irq_desc_t 和irqaction 联系起来。irq_desc[]数组中每一项对应一个中断向量,每一个中断向量为一个irq_desc类型的变量,该变量中有个action指针,指向irqaction链表的首地址。也就是说多个中断服务(irqaction)可以共享一个中断向量,这些中断服务以链表的方式依次链入,当中断到来时,同一中断向量中的所有中断服务函数都会依次执行一遍。request_irq函数主要是实例化一个irqaction结构,这里直接看setup_irq函数。


  [cpp] view plaincopyprint?/*


  * Internal function to register an irqaction – typically used to


  * allocate special interrupts that are part of the architecture.


  */


  /*将中断服务链入irq_desc[irq]->action中*/


  static int


  __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)


  {


  struct irqaction *old, **old_ptr;


  const char *old_name = NULL;


  unsigned long flags;


  int nested, shared = 0;


  int ret;


  if (!desc)


  return -EINVAL;


  if (desc->chip == &no_irq_chip)


  return -ENOSYS;


  /*


  * Some drivers like serial.c use request_irq() heavily,


  * so we have to be careful not to interfere with a


  * running system.


  */


  if (new->flags & IRQF_SAMPLE_RANDOM) {


  /*


  * This function might sleep, we want to call it first,


  * outside of the atomic block.


  * Yes, this might clear the entropy pool if the wrong


  * driver is attempted to be loaded, without actually


  * installing a new handler, but is this really a problem,


  * only the sysadmin is able to do this.


  */


  rand_initialize_irq(irq);


  }


  /* Oneshot interrupts are not allowed with shared */


  if ((new->flags & IRQF_ONESHOT) && (new->flags & IRQF_SHARED))


  return -EINVAL;


  /*


  * Check whether the interrupt nests into another interrupt


  * thread.


  */


  /*如果嵌套在另一个中断线程中*/


  nested = desc->status & IRQ_NESTED_THREAD;


  if (nested) {


  if (!new->thread_fn)


  return -EINVAL;


  /*


  * Replace the primary handler which was provided from


  * the driver for non nested interrupt handling by the


  * dummy function which warns when called.


  */


  new->handler = irq_nested_primary_handler;


  }


  /*


  * Create a handler thread when a thread function is supplied


  * and the interrupt does not nest into another interrupt


  * thread.


  *//*如果提供了中断线程*/


  if (new->thread_fn && !nested) {


  struct task_struct *t;


  /*创建内核中断线程*/


  t = kthread_create(irq_thread, new, “irq/%d-%s”, irq,


  new->name);


  if (IS_ERR(t))


  return PTR_ERR(t);


  /*


  * We keep the reference to the task struct even if


  * the thread dies to avoid that the interrupt code


  * references an already freed task_struct.


  *//*增加使用计数*/


  get_task_struct(t);


  new->thread = t;


  }


  /*


  * The following block of code has to be executed atomically


  */


  spin_lock_irqsave(&desc->lock, flags);


  old_ptr = &desc->action;/*保存action链表头*/


  old = *old_ptr;


  if (old) {/*如果链表不为空,也就是说该中断号对应的有中断服务函数*/


  /*


  * Can’t share interrupts unless both agree to and are


  * the same type (level, edge, polarity)。 So both flag


  * fields must have IRQF_SHARED set and the bits which


  * set the trigger type must match.


  */


  if (!((old->flags & new->flags) & IRQF_SHARED) ||


  ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {


  old_name = old->name;


  goto mismatch;


  }


  #if defined(CONFIG_IRQ_PER_CPU)


  /* All handlers must agree on per-cpuness */


  if ((old->flags & IRQF_PERCPU) !=


  (new->flags & IRQF_PERCPU))


  goto mismatch;


  #endif


  /* add new interrupt at end of irq queue */


  do {/*这一步是遍历到链表的最后一个


  old->next=NULL为止*/


  old_ptr = &old->next;


  old = *old_ptr;


  } while (old);


  shared = 1;/*共享*/


  }


  if (!shared) {


  /*设置irq_desc[irq]结构中chip成员的还没设置的指针


  ,让它们指向一些默认函数*/


  irq_chip_set_defaults(desc->chip);


  init_waitqueue_head(&desc->wait_for_threads);


  /* Setup the type (level, edge polarity) if configured: */


  /*设置触发方式*/


  if (new->flags & IRQF_TRIGGER_MASK) {


  ret = __irq_set_trigger(desc, irq,


  new->flags & IRQF_TRIGGER_MASK);


  if (ret)


  goto out_thread;


  } else


  compat_irq_chip_set_default_handler(desc);


  #if defined(CONFIG_IRQ_PER_CPU)


  if (new->flags & IRQF_PERCPU)


  desc->status |= IRQ_PER_CPU;


  #endif


  desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_ONESHOT |


  IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);


  if (new->flags & IRQF_ONESHOT)


  desc->status |= IRQ_ONESHOT;


  if (!(desc->status & IRQ_NOAUTOEN)) {


  desc->depth = 0;


  desc->status &= ~IRQ_DISABLED;


  desc->chip->startup(irq);


  } else


  /* Undo nested disables: */


  desc->depth = 1;


  /* Exclude IRQ from balancing if requested */


  if (new->flags & IRQF_NOBALANCING)


  desc->status |= IRQ_NO_BALANCING;


  /* Set default affinity mask once everything is setup */


  setup_affinity(irq, desc);


  } else if ((new->flags & IRQF_TRIGGER_MASK)


  && (new->flags & IRQF_TRIGGER_MASK)


  != (desc->status & IRQ_TYPE_SENSE_MASK)) {


  /* hope the handler works with the actual trigger mode… */


  pr_warning(“IRQ %d uses trigger mode %d; requested %d\n”,


  irq, (int)(desc->status & IRQ_TYPE_SENSE_MASK),


  (int)(new->flags & IRQF_TRIGGER_MASK));


  }


  new->irq = irq;/*将new的irq设置为irq*/


  *old_ptr = new;/*将new链入链表中,这个从上面可以看到*/


  /* Reset broken irq detection when installing new handler */


  desc->irq_count = 0;


  desc->irqs_unhandled = 0;


  /*


  * Check whether we disabled the irq via the spurious handler


  * before. Reenable it and give it another chance.


  */


  if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {


  desc->status &= ~IRQ_SPURIOUS_DISABLED;


  __enable_irq(desc, irq, false);/*启用中断*/


  }


  spin_unlock_irqrestore(&desc->lock, flags);


  /*


  * Strictly no need to wake it up, but hung_task complains


  * when no hard interrupt wakes the thread up.


  */


  if (new->thread)


  wake_up_process(new->thread);


  /*下面为注册proc文件系统对应的项*/


  register_irq_proc(irq, desc);


  new->dir = NULL;


  register_handler_proc(irq, new);


  return 0;


  mismatch:


  #ifdef CONFIG_DEBUG_SHIRQ


  if (!(new->flags & IRQF_PROBE_SHARED)) {


  printk(KERN_ERR “IRQ handler type mismatch for IRQ %d\n”, irq);


  if (old_name)


  printk(KERN_ERR “current handler: %s\n”, old_name);


  dump_stack();


  }


  #endif


  ret = -EBUSY;


  out_thread:


  spin_unlock_irqrestore(&desc->lock, flags);


  if (new->thread) {


  struct task_struct *t = new->thread;


  new->thread = NULL;


  if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))


  kthread_stop(t);


  put_task_struct(t);


  }


  return ret;


  }


  中断相应和服务


  清楚了中断机制和内核中有关数据结构的初始化以后,我们就从中断请求的发生到CPU的相应,再到中断服务程序的调用与返回,沿着CPU所经过的路线走一遍。


  当某个外设已经产生了依次中断请求后,该请求通过中断控制器i8259A到达CPU的“中断请求”引线INTR。由于中断时开着的,所以CPU在执行完当前指令后就来相应该次中断请求。


  中断向量的设置和初始化主要在setup.s文件中设置了一部分(上面已经介绍),在start_kernel函数中init_IRQ函数最终调用函数


  [cpp] view plaincopyprint?void __init native_init_IRQ(void)


  {


  int i;


  /* Execute any quirks before the call gates are initialised: */


  x86_init.irqs.pre_vector_init();


  apic_intr_init();


  /*


  * Cover the whole vector space, no vector can escape


  * us. (some of these will be overridden and become


  * ‘special’ SMP interrupts)


  *//*更新外部中断(IRQ)的IDT表项*/


  for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {


  /* IA32_SYSCALL_VECTOR could be used in trap_init already. */


  if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/


  set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);


  }


  if (!acpi_ioapic)


  setup_irq(2, &irq2);


  #ifdef CONFIG_X86_32


  /*


  * External FPU? Set up irq13 if so, for


  * original braindamaged IBM FERR coupling.


  */


  if (boot_cpu_data.hard_math && !cpu_has_fpu)


  setup_irq(FPU_IRQ, &fpu_irq);


  irq_ctx_init(smp_processor_id());


  #endif


  }


  可以看到这里将interrupt[]数组中的值设置为中断服务函数而i-FIRST_EXTERNAL_VECTOR中的i为中断号,interrupt[]数组在汇编中实现


  [cpp] view plaincopyprint?ENTRY(interrupt)


  .text


  .p2align 5


  .p2align CONFIG_X86_L1_CACHE_SHIFT


  ENTRY(irq_entries_start)


  RING0_INT_FRAME


  vector=FIRST_EXTERNAL_VECTOR


  .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7


  .balign 32


  .rept 7


  .if vector < NR_VECTORS


  .if vector <> FIRST_EXTERNAL_VECTOR


  CFI_ADJUST_CFA_OFFSET -4


  .endif


  1: pushl $(~vector+0x80) /* Note: always in signed byte range */


  CFI_ADJUST_CFA_OFFSET 4


  .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6


  jmp 2f


  .endif


  .previous


  .long 1b


  .text


  vector=vector+1


  .endif


  .endr


  2: jmp common_interrupt


  .endr


  END(irq_entries_start)


  .previous


  END(interrupt)


  上面的代码相当于下面代码


  [cpp] view plaincopyprint?346 ENTRY(irq_entries_start)


  347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */


  348 ALIGN


  349 1: pushl $vector-256 /*vector在354行递增 */


  350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/


  351 .data


  352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */


  353 .text


  354 vector=vector+1


  355 .endr /*与347行呼应*/


  356


  357 ALIGN


  /*首先342行和352行都处于。data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput[0],从而执行:


  pushl 0-256


  jmp common_interrupt


  的代码片断,进入到Linux内核安排好的中断入口路径 */


  common_interrupt:


  [cpp] view plaincopyprint?common_interrupt:


  /*将中断向量号减256。内核用负数表示所有的中断*/


  addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */


  /*调用SAVE_ALL宏保存寄存器的值*/


  SAVE_ALL


  TRACE_IRQS_OFF


  /*保存栈顶地址*/


  movl %esp,%eax


  /*调用do_IRQ函数*/


  call do_IRQ


  /*从中断返回*/


  jmp ret_from_intr


  ENDPROC(common_interrupt)


  CFI_ENDPROC


  保存现场后调用do_IRQ函数进入c语言中执行中断函数


  [cpp] view plaincopyprint?unsigned int __irq_entry do_IRQ(struct pt_regs *regs)


  {


  /*取得原来的寄存器*/


  struct pt_regs *old_regs = set_irq_regs(regs);


  /* high bit used in ret_from_ code */


  /*取得中断向量号,通过汇编代码中栈中保存


  的寄存器的顺序和前面c函数中的idt的初始化


  可以得知这里返回的值再取补


  正好是我们要的中断号*/


  unsigned vector = ~regs->orig_ax;


  unsigned irq;


  /*退出idle进程*/


  exit_idle();


  /*进入中断*/


  irq_enter();


  /*中断线号与设备的中断号之间对应关系


  ,由系统分派,分派表是一个per-cpu变量vector_irq*/


  irq = __get_cpu_var(vector_irq)[vector];


  if (!handle_irq(irq, regs)) {/*处理*/


  ack_APIC_irq();/*应答apic*/


  if (printk_ratelimit())


  pr_emerg(“%s: %d.%d No irq handler for vector (irq %d)\n”,


  __func__, smp_processor_id(), vector, irq);


  }


  irq_exit();


  set_irq_regs(old_regs);


  return 1;


  }


  [cpp] view plaincopyprint?/*下面是处理函数。函数根据中断号,查找相应的desc结构*/


  bool handle_irq(unsigned irq, struct pt_regs *regs)


  {


  struct irq_desc *desc;


  int overflow;


  overflow = check_stack_overflow();


  desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/


  if (unlikely(!desc))


  return false;


  /*如果是在中断栈上调用,则稍微复杂一点


  ,需要先构造一个中断栈,再调用handle_irq*/


  if (!execute_on_irq_stack(overflow, desc, irq)) {


  if (unlikely(overflow))


  print_stack_overflow();


  /*handle_irq函数指针,指向了handle_level_irq,


  或者是handle_edge_irq。不论是哪一种,


  中断电流处理函数在会调用handle_IRQ_event


  进一步处理,handle_IRQ_event函数的本质是


  遍历中断号上所有的action,调用其handler。


  这是在设备驱动初始化时向中断子系统注册的


  */


  desc->handle_irq(irq, desc);


  }


  return true;


  }


  由上面的注释知,我们直接看handle_IRQ_event函数


  [cpp] view plaincopyprint?irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)


  {


  irqreturn_t ret, retval = IRQ_NONE;


  unsigned int status = 0;


  /*为CPU会禁止中断,这里将其打开,如果没有指定


  IRQF_DISABLED标志的话,它表示处理程序在中断禁止


  情况下运行*/


  if (!(action->flags & IRQF_DISABLED))


  local_irq_enable_in_hardirq();


  do {/*遍历当前irq的action链表中的所有action,调用之*/


  trace_irq_handler_entry(irq, action);/*打开中断跟踪*/


  ret = action->handler(irq, action->dev_id);/*调用中断函数*/


  trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/


  switch (ret) {


  case IRQ_WAKE_THREAD:


  /*


  * Set result to handled so the spurious check


  * does not trigger.


  */


  ret = IRQ_HANDLED;


  /*


  * Catch drivers which return WAKE_THREAD but


  * did not set up a thread function


  */


  if (unlikely(!action->thread_fn)) {


  warn_no_thread(irq, action);


  break;


  }


  /*


  * Wake up the handler thread for this


  * action. In case the thread crashed and was


  * killed we just pretend that we handled the


  * interrupt. The hardirq handler above has


  * disabled the device interrupt, so no irq


  * storm is lurking.


  */


  if (likely(!test_bit(IRQTF_DIED,


  &action->thread_flags))) {


  set_bit(IRQTF_RUNTHREAD, &action->thread_flags);


  wake_up_process(action->thread);


  }


  /* Fall through to add to randomness */


  case IRQ_HANDLED:


  status |= action->flags;


  break;


  default:


  break;


  }


  retval |= ret;


  action = action->next;/*取得下一个action,如果有的话*/


  } while (action);


  /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/


  if (status & IRQF_SAMPLE_RANDOM)


  add_interrupt_randomness(irq);


  /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/


  local_irq_disable();


  return retval;


  }


  可知,在handle_event_IRQ函数中完成了具体的中断函数响应执行工作。也在这里我们看出,多个中断服务函数共享同一个中断号时,在每次中断到来时,在中断向量上的服务函数队列中所有的服务函数将执行一遍。而对于中断线程也会在这里得到执行。


  到这里中断的初始化、中断服务的相应的分析完了,对于中断返回的退出,基本和异常的返回是一样的,我们从代码中可以看出


  [cpp] view plaincopyprint?ret_from_exception:


  preempt_stop(CLBR_ANY)


  ret_from_intr:


  GET_THREAD_INFO(%ebp)


  …


  至此,中断和异常的分析基本上就这样了,中间有很多细节是值得去理解的。包括汇编和c的实现技巧。对于软中断和任务队列将在后面给予介绍。