下图表述了Linux内核的中断处理机制,为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一
个平衡点, Linux将中断处理程序分解为两个半部: 顶半部(Top Half) 和底半部(Bottom Half) 。
顶半部: 用于完成尽量少的比较紧急的功能, 它往往只是简单地读取寄存器中的中断状态, 并在清除中断标志后就
进行“登记中断”的工作。 “登记中断 意味着将底半部处理程序挂到该设备的底半部执行队列中去。 这样, 顶半部
执行的速度就会很快, 从而可以服务更多的中断请求。
底半部: 几乎做了中断处理程序所有的事情, 而且可以被新的中断打断, 这也是底半部和顶半部的最大不同, 因为顶半部往往被设计成不可中断。 底半部相对来说并不是非常紧急的, 而且相对比较耗时, 不在硬件中断服务程序中执行。
先来个比较:
中断下半部机制 | 上下文 | 是否可被打断 | 是否可睡眠 | 实时性 |
---|---|---|---|---|
tasklet | 运行于软中断上下文 | 可被硬中断打断,不会被定时器中断打断 | 不可睡眠 | 会阻塞应用程序 |
工作队列 | 运行于内核线程上下文 | 可被硬中断、定时器中断打断 | 可睡眠 | 同一个链表中可能挂多个work 所以实时性会降低 |
threaded_irq | 运行于内核线程上下文 | 可被硬中断、定时器中断打断 | 可睡眠 | 对于每个中断都有独立的内核线程 实时性高 |
tasklet的使用较简单, 它的执行上下文是软中断, 执行时机通常是顶半部返回的时候。 我们只需要定义tasklet及
其处理函数, 并将两者关联则可, 例如:
/* 定义一个处理函数 */
void my_tasklet_func(unsigned long);
/* 定义一个 tasklet 结构 my_tasklet , 与 my_tasklet_func(data) 函数相关联 */
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
或
/* 定义一个 tasklet_struct 结构体 */
struct tasklet_struct tasklet;
/* 定义一个处理函数 */
void my_tasklet_func(unsigned long);
/* 关联 tasklet 与 处理函数 */
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data)
在需要调度tasklet的时候引用一个tasklet_schedule() 函数就能使系统在适当的时候进行调度运行:
tasklet_schedule(struct tasklet_struct *tasklet);
销毁 tasklet
tasklet_kill(struct tasklet_struct *tasklet);
taskelet 运行于软中断上下文,所以不能睡眠,会阻塞应用程序,可被外部中断打断,不会被内核定时器中断打断
/* 中断下半部处理函数 不能睡眠
可以被外部中断打断,但不会被定时中断打断 */
static void my_tasklet_func(unsigned long arg)
{
int i = 0;
printk("my_tasklet_func gpio = %d\n", (int)arg);
/* 循环期间,APP程序得不到执行 */
while(i < 500){
printk("%d ",i++);
if(i % 20 == 0)
printk("\n");
}
printk("\n");
}
测试结果:
tasklet 可以被按键中断,但不能被定时器中断,也不能睡眠(测试了添加msleep后,加载驱动直接一直输出报错)
tasklet 使用模板
/* 定义 tasklet 和底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中断处理底半部 */
void xxx_do_tasklet(unsigned long)
{
...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
tasklet_schedule(&xxx_tasklet);
...
}
/* 设备驱动模块加载函数 */
int __init xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt,0, "xxx", NULL);
...
return IRQ_HANDLED;
}
/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
...
}
工作队列的使用方法和tasklet非常相似, 但是工作队列的执行上下文是内核线程, 因此可以调度和睡眠。 下面的
代码用于定义一个工作队列和一个底半部执行函数:
struct work_struct my_wq; /* 定义一个工作队列 */
void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */
通过INIT_WORK() 可以初始化这个工作队列并将工作队列与处理函数绑定:
/* 初始化工作队列并将其与处理函数绑定 */
INIT_WORK(&my_wq, my_wq_func);
与tasklet_schedule() 对应的用于调度工作队列执行的函数为schedule_work() , 如:
/* 调度工作队列执行 */
schedule_work(&my_wq);
假设有两个cpu,当workqueue有新任务时,会将任务放进cpu0和cpu1左侧的work链表中(所有cpu的work链表都放),调度任务时从两个cpu的work_pool中选择一个普通优先级线程放入执行(总共只选一个)
工作队列使用模板
/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
/* 中断处理底半部 */
void xxx_do_work(struct work_struct *work)
{
...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
schedule_work(&xxx_wq);
...
return IRQ_HANDLED;
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt,0, "xxx", NULL);
...
/* 初始化工作队列 */
INIT_WORK(&xxx_wq, xxx_do_work);
...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
...
}
测试结果:
工作队列可以被按键和定时器中断,也可以睡眠
在内核中, 除了可以通过request_irq() 、 devm_request_irq() 申请中断以外, 还可以通过
request_threaded_irq() 和devm_request_threaded_irq() 申请。 这两个函数的原型为:
int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);
/*
* Default primary interrupt handler for threaded interrupts. Is
* assigned as primary handler when request_threaded_irq is called
* with handler == NULL. Useful for oneshot interrupts.
*/
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
threaded_irq 使用案例 (可通过
/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
/* 中断线程化:存在于内核线程 */
static irqreturn_t key_threaded_func_handler(int irq, void *dev)
{
int i = 0;
printk("the process is\"%s\"(pid %d)\n",current->comm, current->pid); //打印出线程名和pid
while(i < 5){
printk("%d ",i++);
msleep(500);
}
printk("\n");
return IRQ_HANDLED;
}
/* 中断处理顶半部 */
static irqreturn_t key_irq_handler(int irq, void *dev)
{
struct gpioirq *girq = dev;
...
return IRQ_WAKE_THREAD;
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
...
/* 申请中断 */
/* 中断下半部:threaded_irq IRQF_ONESHOT:在上下文中屏蔽对应中断号 */
request_threaded_irq(xxx_irq, key_irq_handler, key_threaded_func_handler,
IRQF_TRIGGER_FALLING, "hc_key_irq", xxx_arg);
...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
...
/* 释放中断 */
free_irq(xxx_irq, xxx_arg);
...
}