Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1833608
  • 博文数量: 195
  • 博客积分: 4227
  • 博客等级: 上校
  • 技术积分: 2835
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-04 10:39
文章分类

全部博文(195)

文章存档

2013年(1)

2012年(26)

2011年(168)

分类: LINUX

2011-02-15 10:58:11

中断机制简介
 
谨以此文纪念过往的岁月。
 
一.前言
  在linux中中断可以采用顶部和底部来处理中断程序。在顶部时尽快完成其简单工作,这样来更快的处理来自于各种的中断。而低部则完成各个中断程序的处理时间较长的程序。不过这个也不是规定死说一定中断处理程序一定需要这两个部分,在某些特定的情况下,在顶部需要完成较大的程序。有些情况下是不需要底部程序。
二.中断处理
  在linux中申请中断很简单,直接调用request_irq即可申请一条中断线。其函数原型如下:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
{
 struct irqaction *action;
 struct irq_desc *desc;
 int retval;

 if ((irqflags & (IRQF_SHARED|IRQF_DISABLED))== (IRQF_SHARED|IRQF_DISABLED))  --如果同时设置中断共享和中断关闭,则不能保证中断共享
  pr_warning("IRQ %d/%s: IRQF_DISABLED is not ""guaranteed on shared IRQs\n",irq, devname);
 if ((irqflags & IRQF_SHARED) && !dev_id)    --如果设置中断共享,dev_id必须设置来标示该中断,最好设置为申请中断的设备描述
  return -EINVAL;
 desc = irq_to_desc(irq);   --根据中断号来获得关于中断资源的描述
 if (!desc)
  return -EINVAL;
 if (desc->status & IRQ_NOREQUEST)  --如果中断资源被设置为无法获得。
  return -EINVAL;
 if (!handler)       --中断必须要有中断处理函数
  return -EINVAL;
 action = 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;
 retval = __setup_irq(irq, desc, action);
 if (retval)
  kfree(action);
 return retval;
}
上述函数最终会调用__setup_irq函数,该函数才是真正的将中断注册。下面详细讲述该函数。
static int __setup_irq(unsigned int irq, struct irq_desc * desc, struct irqaction *new)
{
 struct irqaction *old, **p;
 const char *old_name = NULL;
 unsigned long flags;
 int shared = 0;
 int ret;
 if (!desc)
  return -EINVAL;
 if (desc->chip == &no_irq_chip)
  return -ENOSYS;
 if (new->flags & IRQF_SAMPLE_RANDOM) {
  rand_initialize_irq(irq);
 }
 spin_lock_irqsave(&desc->lock, flags);
 p = &desc->action;
 old = *p;
 if (old) {
  if (!((old->flags & new->flags) & IRQF_SHARED) ||
      ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
   old_name = old->name;
   goto mismatch;
  }
  do {
   p = &old->next;
   old = *p;
  } while (old);
  shared = 1;
 }
 if (!shared) {
  irq_chip_set_defaults(desc->chip);
  if (new->flags & IRQF_TRIGGER_MASK) {
   ret = __irq_set_trigger(desc, irq,
     new->flags & IRQF_TRIGGER_MASK);
   if (ret) {
    spin_unlock_irqrestore(&desc->lock, flags);
    return ret;
   }
  } else
    compat_irq_chip_set_default_handler(desc);
  desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
      IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);
  if (!(desc->status & IRQ_NOAUTOEN)) {
   desc->depth = 0;
   desc->status &= ~IRQ_DISABLED;
   desc->chip->startup(irq);
  } else
   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 */
  do_irq_select_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));
 }
 *p = new;
 desc->irq_count = 0;
 desc->irqs_unhandled = 0;
 if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
  desc->status &= ~IRQ_SPURIOUS_DISABLED;
  __enable_irq(desc, irq);
 }
 spin_unlock_irqrestore(&desc->lock, flags);
 new->irq = irq;
 register_irq_proc(irq, desc);
 new->dir = NULL;
 register_handler_proc(irq, new);
 return 0;
}
上面即是中断的申请。
而释放一个中断则是free_irq即可。
二.底部机制
一般底部机制有三种,tasklet,工作队列和软中断。
1.1 tasklet
tasklet的使用
定义tasklet处理函数xx_tasklet_func(unsigned long)
定义一个tasklet结构,采用宏定义DECLARE_TASKLET(xx_tasklet,xx_tasklet_fun,data);
在中断处理函数顶半部,可以采用tasklet_schedule来进行调度。
下面是tasklet结构体
struct tasklet_struct
{
 struct tasklet_struct *next;
 unsigned long state;
 atomic_t count;
 void (*func)(unsigned long);
 unsigned long data;
};
#define DECLARE_TASKLET(name, func, data)  struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
对于底部机制是采用软中断的方式实现。采用了ksoftirqd软中断处理守护程序来实现对中断底半部程序的处理。
1.2 tasklet程序详解
在中断中通过调用tasklet_schedule来实现软中断的切换,那来看看该函数的实现。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))  --检测并且设置tasklet的状态
  __tasklet_schedule(t);                               
}
void __tasklet_schedule(struct tasklet_struct *t)
{
 unsigned long flags;
 local_irq_save(flags);
 t->next = NULL;
 *__get_cpu_var(tasklet_vec).tail = t;  --获取当前CPU的tasklet_vec链表,将该tasklet添加入链表尾
 __get_cpu_var(tasklet_vec).tail = &(t->next);
 raise_softirq_irqoff(TASKLET_SOFTIRQ); --执行TASKLET_SOFTIRQ软中断
 local_irq_restore(flags);
}
inline void raise_softirq_irqoff(unsigned int nr)
{
 __raise_softirq_irqoff(nr);    --将软中断nr挂起
 if (!in_interrupt())           --判断是否在中断上下文中,不在中断上下文中,则唤醒软中断守护程序。
  wakeup_softirqd();
}
static inline void wakeup_softirqd(void)
{
 struct task_struct *tsk = __get_cpu_var(ksoftirqd);   --获取软中断守护程序的进程信息
 if (tsk && tsk->state != TASK_RUNNING)               --如果进程存在并且没有运行
  wake_up_process(tsk);                              --唤醒守护进程。
}
其实所用的软中断都会有软中断守护程序ksoftirqd来完成。该进程在系统初始化时即运行。
ksoftirqd -> do_softirq() -> __do_softirq在判断各种条件是否满足后最终会调用__do_softirq函数。
在__do_softirq该函数中会循环检测softirq_vec该文件全局变量,即tasklet_struct链表。处理tasklet链表中的函数,直到所用的tasklet都完成。就其tasklet的本质是采用软中断实现,而软中断的实现则是采用了一个守护程序来完成这些。
2.1工作队列
work queue的使用
定义tasklet处理函数xx_wq_func(unsigned long)
定义工作队列struct work_struct xx_wq;
定义一个tasklet结构,采用宏定义INIT_WORK(&xx_wq,(void (*)(void *))xx_wq_fun);
在中断处理函数顶半部,可以采用schedule_work来进行调度。
2.2工作队列详解如下
work_struct结构体
struct work_struct {
 atomic_long_t data;     --原子变量初始化为0
#define WORK_STRUCT_PENDING 0  /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
 struct list_head entry; --用于添加到keventd_wq全局工作队列
 work_func_t func;       --用于保存处理函数。
};
work queue的初始化,仍是采用宏定义的办法实现。
#define INIT_WORK(_work, _func)      \
 do {        \
  (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
  INIT_LIST_HEAD(&(_work)->entry);   \  
  PREPARE_WORK((_work), (_func));    \   -- (_work)->func = _func;
 }while (0)
在完成了初始化之后,schedule_work如何工作的。
schedule_work -> queue_work -> queue_work_on -> __queue_work -> insert_work
将work queue添加入系统。
static void insert_work(struct cpu_workqueue_struct *cwq,struct work_struct *work, struct list_head *head)
{
 set_wq_data(work, cwq);
 smp_wmb();
 list_add_tail(&work->entry, head);
 wake_up(&cwq->more_work);
}
3.1 软中断
使用open_softirq注册软中断处理对应的函数,而调用raise_softirq函数来触发一个中断,关于软中断的解释在上面已经看过了。
4.底半部总结
就其上面三种底半部机制,其实本质就两种,一种是采用工作队列,一种采用软中断。这两种实现的功能都是将中断处理函数中较耗费时间的部分程序,放到后期去执行。从而增加系统的并发性,提高系统性能。上述两种的区别在于是否可以在处理函数中发生睡眠。在软中断中不能够睡眠,而在工作队列中是可以睡眠的。关于为什么不清楚,网上说软中断是处于中断上下文中,而工作队列则是在进程上下文中。但是我查看源码,在判断是否将软中断守护程序唤醒的条件是if (!in_interrupt()) ,如果在中断上下中是不会唤醒软中断守护程序,在进程上下文中则会唤醒。这个不清楚为什么。工作队列处在进程上下文中是可以理解的。
三.linux中断机制
在看了上面的中断使用后,再来回顾看一下中断机制。
1.什么是中断
不解释。
2.关于linux中断的向量表建立和linux中中断的初始化在下面两个博客中详细说明了。
http://blog.csdn.net/hdm125/archive/2009/04/08/4058213.aspx
http://blog.csdn.net/hongtao_liu/archive/2009/06/12/4263176.aspx

 
 
 
阅读(4372) | 评论(1) | 转发(1) |
0

上一篇:等待队列

下一篇:定时器

给主人留下些什么吧!~~

steven_miao2011-02-15 10:59:45