中断机制简介
谨以此文纪念过往的岁月。
一.前言
在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()) ,如果在中断上下中是不会唤醒软中断守护程序,在进程上下文中则会唤醒。这个不清楚为什么。工作队列处在进程上下文中是可以理解的。