tasklet
是利用软中断实现的一种下半部机制
。所以说,本质上也是软中断的一种,其运行在软中断上下文
。
tasklet
使用struct tasklet_struct
结构体来描述,如下定义(/include/linux/interrupt.h):
struct tasklet_struct
{
//多个tasklet串接成一个链表
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
//tasklet处理程序。
void (*func)(unsigned long);
//传递给tasklet处理函数的参数
unsigned long data;
};
state
表示tasklet的状态:TASK_STATE_SCHED
表示tasklet已经被调度。TASKLET_STATE_RUN
表示tasklet正在运行(只支持SMP)count
为0表示tasklet处于激活状态;不为0表示该tasklet被禁止,不允许运行。 本小结描述在开发中如何使用tasklet
。具体步骤如下:
(2-1)定义一个tasklet
。
(2-2)使用tasklet_init()
函数初始化tasklet。函数原型如下(/kernel/softirq.c):
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
也可以使用DECLARE_TASKLET(name, func, data)
一次性完成tasklet的定义和初始化
name:tasklet的名字
func:tasklet的处理函数
data:需要传递给func函数的参数
(2-3)在中断上半部(即中断处理函数中),调用tasklet_schedule()
函数使能tasklet在合适的时间运行。函数原型如下:
void tasklet_schedule(struct tasklet_struct *t)
(2-4)一个代码示例
//定义一个tasklet
struct taskle_struct my_tasklet;
//定义一个处理函数
void tasklet_handler(unsigned long data)
{
}
//中断处理函数
irereturn_t my_irqHandler(int irq,void *dev_id)
{
//....
//调度tasklet
tasklet_schedule(&my_tasklet);
//....
}
//驱动入口
static int __init xxx_init(void)
{
//....
//初始化tasklet
tasklet_init(&my_tasklet,tasklet_handler,data);
//注册中断处理函数
request_irq(xxx_irq, my_irqHandler, 0, "xxx", &xxx_dev);
//...
}
//驱动出口
static void __exit xxx_exit(void)
{
//...
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("iriczhao");
本小结简单分析一下tasklet的源码:
对于tasklet,在linux内核中每个cpu维护着两个链表,如下定义(/kernel/softirq.c):
struct tasklet_head {
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
TASKLET_SOFTIRQ
类型,其优先级是6;HI_SOFTIRQ
,优先级是0。在linux启动过程中,在start_kernel()
函数中将调用softirq_init()
函数初始化tasklet_vec、tasklet_hi_vec链表,另外还将注册TASKLET_SOFTIRQ(软中断回调函数:tasklet_action)和HI_SOFTIRQ(tasklet_hi_action)这两个软中断。如下代码(/kernel/ softirq.c):
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
tasklet_schdule()
函数定义如下:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
函数中调用了__tasklet_schedule()
,函数定义如下(/kernel/softirq.c):
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
EXPORT_SYMBOL(__tasklet_schedule);
__tasklet_schdule()
函数在关闭中断的情况下,把tasklet挂入到tasklet_vec
链表中,然后触发一个TASKLET_SOFTIRQ
类型的软中断。
好啦,这儿有个疑问?——tasklet被挂入到tasklet_vec
链表中去了,那什么时候执行呢。从上面代码片段可见,在中断函数中调用tasklet_schdule
,tasklet并不会马上运行。
当一个tasklet被挂入到一个cpu的tasklet_vec链表中去后会设置TASK_STATE_SCHED
标志位,只要tasklet还没有被执行,那么驱动程序中调用多次tasklet_schdule
都无用。它必须在该CPU的软中断上下文中执行,直到执行完毕后清除了TASK_STATE_SCHED
标志位后,才会有可能到其他CPU上运行。
在linux内核中,当中断发生,到中断得到处理整个过程中,最后会使用irq_exie()
检查当前是否有pending
等待的软中断。
tasklet的执行是基于软中断执行过程的。在软中断执行时,将按照中断状态__softirq_pending
来依次执行pending状态的软中断。当轮到执行TASKLET_SOFTIRQ
类型软中断时,其对应的回调函数tasklet_action
才会被调用。
tasklet_action定义如下(/kernel/softirq.c):
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
注:tasklet_action()
在softirq_init()
函数中设置触发。
tasklet
在驱动程序设计中,还是会经常使用到。本文简单描述了tasklet的使用方法,以及对源码进行了些分析。
由于小生知识与精力有限,如果文章存在问题,还望看官们多多批评,O(∩_∩)O哈哈~