2009年7月3日 星期五

Linux Interrupts and Exceptions - 4

當 I/O Interrupt 發生時,CPU 從 IDT 表中找出該中斷的位置,並取得記錄於 IDT 的 Segment SelectorOffset
再利用這兩個值將目前指令跳至 Interrupt Handler,如下圖所示:


Interrupt Handler 的動作即是呼叫 do_IRQ() 函式來處理該中斷,其 IRQ Number 由引數 struct pt_regs 傳入。

定義於 include/asm-i386/ptrace.h

/* this struct defines the way the registers are stored on the
stack during a system call. */
struct pt_regs {
  long ebx;
  long ecx;
  long edx;
  long esi;
  long edi;
  long ebp;
  long eax;
  int xds;
  int xes;
  long orig_eax;
  long eip;
  int xcs;
  long eflags;
  long esp;
  int xss;
};

定義於 arch/i386/kernel/irq.c

/*
* do_IRQ handles all normal device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
fastcall unsigned int do_IRQ(struct pt_regs *regs){
  /* high bits used in ret_from_ code */
  int irq = regs->orig_eax & 0xff;
#ifdef CONFIG_4KSTACKS
  union irq_ctx *curctx, *irqctx;
  u32 *isp;#endif
  irq_enter();
#ifdef CONFIG_DEBUG_STACKOVERFLOW
  /* Debugging check for stack overflow: is there less than 1KB free? */
  {
    long esp;
    __asm__ __volatile__("andl %%esp,%0" :
          "=r" (esp) : "0" (THREAD_SIZE - 1));
    if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) {
      printk("do_IRQ: stack overflow: %ld\n",
        esp - sizeof(struct thread_info));
      dump_stack();
    }
  }
#endif

#ifdef CONFIG_4KSTACKS
  curctx = (union irq_ctx *) current_thread_info();
  irqctx = hardirq_ctx[smp_processor_id()];

  /*
   * this is where we switch to the IRQ stack. However, if we are
   * already using the IRQ stack (because we interrupted a hardirq
   * handler) we can't do that and just have to keep using the
   * current stack (which is the irq stack already after all)
   */
  if (curctx != irqctx) {
    int arg1, arg2, ebx;
    /* build the stack frame on the IRQ stack */
    isp = (u32*) ((char*)irqctx + sizeof(*irqctx));
    irqctx->tinfo.task = curctx->tinfo.task;
    irqctx->tinfo.previous_esp = current_stack_pointer;
    asm volatile(
      "  xchgl %%ebx,%%esp  \n"
      "  call __do_IRQ   \n"
      "  movl %%ebx,%%esp  \n"
      : "=a" (arg1), "=d" (arg2), "=b" (ebx)
      : "0" (irq), "1" (regs), "2" (isp)
      : "memory", "cc", "ecx"
    );
  } else
#endif

  __do_IRQ(irq, regs);

  irq_exit();

  return 1;
}

定義於 kernel/irq/handle.c

/*
* do_IRQ handles all normal device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)
{
  irq_desc_t *desc = irq_desc + irq;
  struct irqaction * action;
  unsigned int status;

  kstat_this_cpu.irqs[irq]++;
  if (desc->status & IRQ_PER_CPU) {
    irqreturn_t action_ret;

    /*
     * No locking required for CPU-local interrupts:
     */
    desc->handler->ack(irq);
    action_ret = handle_IRQ_event(irq, regs, desc->action);
    if (!noirqdebug)
      note_interrupt(irq, desc, action_ret);
    desc->handler->end(irq);
    return 1;
  }

  spin_lock(&desc->lock);
  desc->handler->ack(irq);
  /*
   * REPLAY is when Linux resends an IRQ that was dropped earlier
   * WAITING is used by probe to mark irqs that are being tested
   */
  status = desc->status & ~(IRQ_REPLAY IRQ_WAITING);
  status = IRQ_PENDING; /* we _want_ to handle it */

  /*
   * If the IRQ is disabled for whatever reason, we cannot
   * use the action we have.
   */
  action = NULL;
  if (likely(!(status & (IRQ_DISABLED IRQ_INPROGRESS)))) {
    action = desc->action;
    status &= ~IRQ_PENDING; /* we commit to handling */
    status = IRQ_INPROGRESS; /* we are handling it */
  }
  desc->status = status;

  /*
   * If there is no IRQ handler or it was disabled, exit early.
   * Since we set PENDING, if another processor is handling
   * a different instance of this same irq, the other processor
   * will take care of it.
   */
  if (unlikely(!action))
    goto out;

  /*
   * Edge triggered interrupts need to remember
   * pending events.
   * This applies to any hw interrupts that allow a second
   * instance of the same irq to arrive while we are in do_IRQ
   * or in the handler. But the code here only handles the _second_
   * instance of the irq, not the third or fourth. So it is mostly
   * useful for irq hardware that does not mask cleanly in an
   * SMP environment.
   */
  for (;;) {
    irqreturn_t action_ret;

    spin_unlock(&desc->lock);

    action_ret = handle_IRQ_event(irq, regs, action);

    spin_lock(&desc->lock);
    if (!noirqdebug)
      note_interrupt(irq, desc, action_ret);
    if (likely(!(desc->status & IRQ_PENDING)))
      break;
    desc->status &= ~IRQ_PENDING;
  }
  desc->status &= ~IRQ_INPROGRESS;

out:
  /*
   * The ->end() handler has to deal with interrupts which got
   * disabled while the handler was running.
   */
  desc->handler->end(irq);
  spin_unlock(&desc->lock);

  return 1;
}