您的位置:首页 > 运维架构 > Linux

《Linux设备驱动开发详解》-- Linux中断处理底半部机制(tasklet、工作队列和软中断)

2014-06-12 16:41 471 查看
1.tasklet 

tasklet 的使用较简单, 我们只需要定义 tasklet 及其处理函数并将两者关联, 例如:
void my_tasklet_func(unsigned long); /*定义一个处理函数*/ 

DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/ 

代码 DECLARE_T ASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet 的 tasklet 并将其与 my_tasklet_func()这个函数绑定,而传入这个函数的参数为 data。

在需要调度 tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行,如下所示:
tasklet_schedule(&my_tasklet); 

使用 tasklet 作为底半部处理中断的设备驱动程序模板如代码清单 10.2 所示 (仅包含与中断相关的部分)。

代码清单 10.2  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,  struct  pt_regs *regs)
{
...
tasklet_schedule(&xxx_tasklet);
...
}

/*设备驱动模块加载函数*/
int __init xxx_init(void)
{
...
/*申请中断*/
result = request_irq(xxx_irq, xxx_interrupt,
SA_INTERRUPT, "xxx", NULL);
...
}

/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq, xxx_interrupt);
...
}


        上述程序在模块加载函数中申请中断 (第 24~25 行) , 并在模块卸载函数中释放它(第 34 行) 。对应于 xxx_irq 的中断处理程序被设置为 xxx_interrupt()函数,在这个函数中,第 15 行的 tasklet_schedule(&xxx_tasklet)调度的tasklet 函数xxx_do_tasklet()在适当的时候得到执行。

上述代码第 12 行显示中断处理程序顶半部的返回类型为 irqreturn_t, 它定义为 int,中断处理程序顶半部一般返回 IRQ_HANDLED。
2.工作队列 (详见http://blog.csdn.net/houxn22/article/details/30247427)

工作队列的使用方法和 tasklet 非常相似,下面的代码用于定义一个工作队列和一个底半部执行函数。
struct work_struct my_wq; /*定义一个工作队列*/ 

void my_wq_func(unsigned long); /*定义一个处理函数*/ 

通过 INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定,如下所示:
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作队列并将其与处理函数绑定*/ 

与 tasklet_schedule()对应的用于调度工作队列执行的函数为 schedule_work(),如:
schedule_work(&my_wq);/*调度工作队列执行*/

与代码清单 10.2 对应的使用工作队列处理中断底半部的设备驱动程序模板如代码清单 10.3 所示(仅包含与中断相关的部分)。

代码清单 10.3  工作队列使用模板
/*定义工作队列和关联函数*/
struct work_struct xxx_wq;
void xxx_do_work(unsigned long);

/*中断处理底半部*/
void xxx_do_work(unsigned long)
{
...
}

/*中断处理顶半部*/
irqreturn_t  xxx_interrupt(int  irq,  void  *dev_id,  struct  pt_regs *regs)
{
...
schedule_work(&xxx_wq);
...
}

/*设备驱动模块加载函数*/
int xxx_init(void)
{
...
/*申请中断*/
result = request_irq(xxx_irq, xxx_interrupt,
SA_INTERRUPT, "xxx", NULL);
...
/*初始化工作队列*/
INIT_WORK(&xxx_wq, (void (*)(void *)) xxx_do_work, NULL);
...
}

/*设备驱动模块卸载函数*/
void xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq, xxx_interrupt);
...
}


与代码清单 10.2 不同的是,上述程序在设计驱动模块加载函数中增加了初始化工作队列的代码(第 28 行) 。
        尽管 Linux 专家们多建议在设备第一次打开时才申请设备的中断并在最后一次关闭时释放中断以尽量减少中断被这个设备占用的时间,但是,大多数情况下,为求省事, 大多数驱动工程师还是将中断申请和释放的工作放在了设备驱动的模块加载和卸载函数中。

另转:

tasklet特性:
1.一个tasklet可在稍后被禁止或者重新启用;只有启用的次数和禁止的次数相同时,tasklet才会被执行。
2.和定时器类似,tasklet可以自己注册自己。
3.tasklet可被调度以在通常的优先级或者高优先级执行。高优先级的tasklet总会优先执行。
4.如果系统负荷不重,则tasklet会立即执行,但始终不会晚于下一个定时器滴答
5.一个tasklet可以和其它tasklet并发,但对自身来讲是严格串行处理的,也就是说,同一tasklet永远不会在多个处理器上同时运行:tasklet始终会调度自己在同一CPU上运行;
工作队列:
表面来看,工作队列类似于tasklet:允许内核代码请求某个函数在将来的时间被调用。
但其实还是有很多不同:
1.tasklet在软中断上下文中运行,因此,所有的tasklet代码都是原子的。相反,工作队列函数在一个特殊的内核进程上下文中运行,因此他们有更好的灵活性
尤其是,工作队列可以休眠!
2.tasklet始终运行在被初始提交的统一处理器上,但这只是工作队列的默认方式
3.内核代码可以请求工作队列函数的执行延迟给定的时间间隔
4.tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。
 
两者的关键区别:tasklet会在很短的时间内很快执行,并且以原子模式执行,而工作队列函数可以具有更长的延迟并且不必原子化。两种机制有各自适合的情形。
更多详见 http://blog.csdn.net/houxn22/article/details/45720247


3.软中断

        软中断是用软件方式模拟硬件中断的念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。

        第 9 章异步通知所基于的信号也类似于中断,现在,总结一下硬中断、软中断和信号的区别:硬中断是外部设备对 CPU 的中断,软中断通常是硬中断服务程序对内核的中断,而信号则是由内核(或其他进程)对某个进程的中断。

        在 Linux 内核中,用 softirq_action 结构体表征一个软中断,这个结构体中包含软中断处理函数指针和传递给该函数的参数。 使用 open_softirq()函数可以注册软中断对应的处理函数,而 raise_softirq()函数可以触发一个软中断。

        软中断和 tasklet 仍然运行于中断上下文,而工作队列则运行于进程上下文。 因此,软中断和 tasklet 处理函数中不能睡眠,而工作队列处理函数中允许睡眠。

        local_bh_disable()和 local_bh_enable()是内核中用于禁止和使能软中断和 tasklet 底半部机制的函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: