Chinaunix首页 | 论坛 | 博客
  • 博客访问: 183067
  • 博文数量: 57
  • 博客积分: 2215
  • 博客等级: 大尉
  • 技术积分: 635
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-09 15:47
个人简介

非淡泊无以明志,非宁静无以致远

文章分类

全部博文(57)

文章存档

2013年(12)

2011年(15)

2010年(30)

我的朋友

分类: LINUX

2011-03-04 10:04:45

一:前言

  在前一个专题里曾分析过所有IRQ中断处理流程,经过SAVE_ALL保存硬件环境后,都会进入do_IRQ()进行处理,今天接着分析do_IRQ()处理的相关东西.分为两部中断处理程序与软中断两个大的部份进行介绍.

二:中断处理程序

  在驱动程序中,通常使用request_irq()来注册中断处理程序.我们先从注册中断处理程序的实现说起.

/*
   irq:可断号
   handler:中断处理程序
   irqflags:中断处理标志.SA_SHIRQ:共享中断线 SA_INTERRUPT:快速处理中断
         必须在关中断的情况下运行.SA_SAMPLE_RANDOM:该中断可能用于产生一个随机数
   devname dev_id:设备名称与ID   
*/
int request_irq(unsigned int irq,
      irqreturn_t (*handler)(int, void *, struct pt_regs *),
      unsigned long irqflags,
      const char * devname,
      void *dev_id)
{
   int retval;
   struct irqaction * action;
#if 1
   if (irqflags & SA_SHIRQ) {
      if (!dev_id)
         printk("Bad boy: %s (at 0x%x) called us without a dev_id!n", devname, (&irq)[-1]);
   }
#endif
   //参数有效性判断
   if (irq >= NR_IRQS)
      return -EINVAL;
   if (!handler)
      return -EINVAL;
   // 分配一个irqaction
   action = (struct irqaction *)
         kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
   if (!action)
      return -ENOMEM;
   action->handler = handler;
   action->flags = irqflags;
   cpus_clear(action->mask);
   action->name = devname;
   action->next = NULL;
   action->dev_id = dev_id;
   //将创建并初始化完在的action加入irq_desc[NR_IRQS]
   retval = setup_irq(irq, action);
   if (retval)
      kfree(action);
   return retval;
}

上面涉及到的irqaction结构与irq_desc[]的关系我们在上一节我们已经详细分析过了,这里不再赘述.

转进setup_irq():

int setup_irq(unsigned int irq, struct irqaction * new)
{
   int shared = 0;
   unsigned long flags;
   struct irqaction *old, **p;
   irq_desc_t *desc = irq_desc + irq;
   //如果hander == no_irq_type:说明中断控制器不支持该IRQ线
   if (desc->handler == &no_irq_type)
      return -ENOSYS;
   sif (new->flags & SA_SAMPLE_RANDOM) {
      rand_initialize_irq(irq);
   }
   /*
   * The following block of code has to be executed atomically
   */
   spin_lock_irqsave(&desc->lock,flags);
   p = &desc->action;
   if ((old = *p) != NULL) {
      //判断这条中断线上的中断处理程序是否允许SHARE
      /* Can't share interrupts unless both agree to */
      if (!(old->flags & new->flags & SA_SHIRQ)) {
         spin_unlock_irqrestore(&desc->lock,flags);
         return -EBUSY;
      }
      /* add new interrupt at end of irq queue */
      do {
         p = &old->next;
         old = *p;
      } while (old);
      shared = 1;
   }
   //将其添加到中断处理函数链的末尾
   *p = new;
   //如果这一条线还没有被占用,初始化这条中断线
   //包含清标志,在8259A上启用这条中断线
   if (!shared) {
      desc->depth = 0;
      desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS);
      desc->handler->startup(irq);
   }
   spin_unlock_irqrestore(&desc->lock,flags);
   //在proc下建立相关的文件
   register_irq_proc(irq);
   return 0;
}

现在知道怎么打一个中断处理程序挂到irq_desc[NR_IRQS]数组上了,继续分析中断处理中如何调用中断处理函数.从我们开篇时说到的do_IRQ()说起.

asmlinkage unsigned int do_IRQ(struct pt_regs regs)
{  
   //屏蔽高位,取得中断号
   int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code */
   //取得中断号对应的desc结构
   irq_desc_t *desc = irq_desc + irq;
   struct irqaction * action;
   unsigned int status;
   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: %ldn",
           esp - sizeof(struct thread_info));
         dump_stack();
      }
   }
#endif
   //更新统计计数
   kstat_this_cpu.irqs[irq]++;
   spin_lock(&desc->lock);
   //给8259 回一个ack.回ack之后,通常中断控制会屏蔽掉此条IRQ线
   desc->handler->ack(irq);
   //清除IRQ_REPLAY IRQ_WAITING标志
   status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
   //设置IRQ_PENDING:表示中断被应答,但没有真正被处理
   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;
   //中断被屏蔽或者正在处理
   //IRQ_DIASBLED:中断被禁用
   //IRQ_INPROGRESS:这个类型的中断已经在被另一个CPU处理了
   if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {
      action = desc->action;
      status &= ~IRQ_PENDING; /* we commit to handling */
      //置位,表示正在处理中...
      status |= c; /* we are handling it */
   }
   desc->status = status;
   //没有挂上相应的中断处理例程或者不满足条件
   if (unlikely(!action))
      goto out;
   for (;;) {
      irqreturn_t action_ret;
      u32 *isp;
      union irq_ctx * curctx;
      union irq_ctx * irqctx;
      curctx = (union irq_ctx *) current_thread_info();
      irqctx = hardirq_ctx[smp_processor_id()];
      spin_unlock(&desc->lock);
      //通常curctx == irqctx.除非中断程序使用独立的4K堆栈.
      if (curctx == irqctx)
         action_ret = handle_IRQ_event(irq, ®s, action);
      else {
         /* build the stack frame on the IRQ stack */
         isp = (u32*) ((char*)irqctx + sizeof(*irqctx));
         irqctx->tinfo.task = curctx->tinfo.task;
         irqctx->tinfo.real_stack = curctx->tinfo.real_stack;
         irqctx->tinfo.virtual_stack = curctx->tinfo.virtual_stack;
         irqctx->tinfo.previous_esp = current_stack_pointer();
         *--isp = (u32) action;
         *--isp = (u32) ®s;
         *--isp = (u32) irq;
         asm volatile(
           "    xchgl  %%ebx,%%esp   n"
           "    call  handle_IRQ_event n"
           "    xchgl  %%ebx,%%esp   n"
           : "=a"(action_ret)
           : "b"(isp)
           : "memory", "cc", "edx", "ecx"
         );
      }
      spin_lock(&desc->lock);
      //调试用,忽略
      if (!noirqdebug)
         note_interrupt(irq, desc, action_ret, ®s);
      if (curctx != irqctx)
         irqctx->tinfo.task = NULL;
      //如果没有要处理的中断了,退出
      if (likely(!(desc->status & IRQ_c)))
         break;
      //又有中断到来了,继续处理
      desc->status &= ~c;
   }
   //处理完了,清除IRQ_INPROGRESS标志
   desc->status &= ~IRQ_INPROGRESS;
out:
   /*
   * The ->end() handler has to deal with interrupts which got
   * disabled while the handler was running.
   */
   //处理完了,调用中断控制器的end.通常此函数会使中断控制器恢复IRQ线中断
   desc->handler->end(irq);
   spin_unlock(&desc->lock);
   //irq_exit():理论上中断处理完了,可以处理它的下半部了
   irq_exit();
   return 1;
}

这段代码比较简单,但里面几个标志让人觉的很迷糊,列举如下:

  IRQ_DISABLED:相应的IRQ被禁用.既然中断线被禁用了,也就不会产生中断,进入do_IRQ()了?因为电子器件的各种原因可能会产生 “伪中断”上报给CPU.

  IRQ_PENDING:CPU收到这个中断信号了,已经给出了应答,但并末对其进行处理.回顾上面的代码,进入do_IRQ后,发送ack,再设置此标志.

  IRQ_ INPROGRESS:表示这条IRQ线的中断正在被处理.为了不弄脏CPU的高速缓存.把相同IRQ线的中断放在一起处理可以提高效率,且使中断处理程序不必重入

  举例说明:如果CPU A接收到一个中断信号.回一个ACK,设置c,假设此时末有这个中断线的中断处理程序在处理,继而会将标志位设为IRQ_ INPROGRESS.转去中断处理函数执行.如果此时,CPU B检测到了这条IRQ线的中断信号.它会回一个ACK.设置

  IRQ_PENDING.但时此时这条IRQ线的标志为IRQ_ INPROGRESS.所以,它会进经过goto out退出.如果cpu A执行完了中断处理程序,判断它的标志线是否为IRQ_PENDING.因为CPU B已将其设为了IRQ_PENDING.所以继续循环一次.直到循环完后,清除IRQ_INPROGRESS标志

  注意上述读写标志都是加锁的.linux采用的这个方法,不能不赞一个 *^_^*

  继续看代码:

asmlinkage int handle_IRQ_event(unsigned int irq,
      struct pt_regs *regs, struct irqaction *action)
{
   int status = 1; /* Force the "do bottom halves" bit */
   int ret, retval = 0;
   //如果没有设置SA_INTERRUPT.将CPU 中断打开
   //应该尽量的避免CPU关中断的情况,因为CPU屏弊本地中断,会使
   //中断丢失
   if (!(action->flags & SA_INTERRUPT))
      local_irq_enable();
   //遍历运行中断处理程序
   do {
      ret = action->handler(irq, action->dev_id, regs);
      if (ret == IRQ_HANDLED)
         status |= action->flags;
      retval |= ret;
      action = action->next;
   } while (action);
   if (status & SA_SAMPLE_RANDOM)
      add_interrupt_randomness(irq);
   //关中断
   local_irq_disable();
   return retval;
}

可能会有这样的疑问.如果在一根中断线上挂上了很多个中断处理程序,会不会使这一段程序的效率变得很低下呢?事实上,我们在写驱动程序的过程中,都会首先在中断处理程序里判断设备名字与设备ID,只有条件符合的设备中断才会变处理.

  三:软中断

  为了提高中断的响应速度,很多操作系统都把中断分成了两个部份,上半部份与下半部份.上半部份通常是响应中断,并把中断所得到的数据保存进下半部.耗时的操作一般都会留到下半部去处理.

  接下来,我们看一下软中断的处理模型:

  Start_kernel() à softirq_init();

  在softirq_init()中会注册两个常用类型的软中断,看具体代码:

void __init softirq_init(void)
{
   open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
   open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
//参数含义:nr:软中断类型 action:软中断处理函数  data:软中断处理函数参数
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
   softirq_vec[nr].data = data;
   softirq_vec[nr].action = action;
}
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
struct softirq_action
{
   void (*action)(struct softirq_action *);
   void *data;
};

  在上面的代码中,我们可以看到:open_softirq()中.其实就是对softirq_vec数组的nr项赋值.softirq_vec是一个32元素的数组,实际上linux内核只使用了六项. 如下示:

enum
{
   HI_SOFTIRQ=0,
   TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
   NET_RX_SOFTIRQ,
   SCSI_SOFTIRQ,
   TASKLET_SOFTIRQ
}
另外.如果使软中断能被CPU调度,还得要让它激活才可以.激活所使用的函数为__raise_softirq_irqoff()

代码如下:

  #define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)

  这个宏使local_softirq_pending的nr位置1

  好了,经过open_softirq()à local_softirq_pending()后,我们来看下软中断怎么被CPU调度.

  继续上面中断处理的代码.在处理完硬件中断后,会调用irq_exit().这就是软中断的入口点了,我们来看下

#define irq_exit()                   
do {                       
      preempt_count() -= IRQ_EXIT_OFFSET;       
      //注意了,软中断不可以在硬件中断上下文或者是在软中断环境中使用哦 ^_^
      //softirq_pending()的判断,注意我们上面分析过的_raise_softirqoff().它判断当前cpu有没有激活软中断
      if (!in_interrupt() && softirq_pending(smp_processor_id()))
         do_softirq();             
      preempt_enable_no_resched();           
} while (0)

  跟踪进do_softirq()

asmlinkage void do_softirq(void)
{
   __u32 pending;
   unsigned long flags;
   //在硬件中断环境中,退出
   if (in_interrupt())
      return;
   //禁止本地中断,不要让其受中断的影响
   local_irq_save(flags);
   pending = local_softirq_pending();
   //是否有软中断要处理?
   if (pending)
      __do_softirq();
   //恢复CPU中断
   local_irq_restore(flags);
}
转入__do_softirq()

阅读(740) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2011-03-06 09:05:48

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com