中断下半部处理之tasklet
1.tasklet概述
下半部和退后执行的工作,软中断的使用只在那些执行频率很高和连续性要求很高的情况下才需要。在大多数情况下,为了控制一个寻常的硬件设备,tasklet机制都是实现自己下半部的最佳选择。其实tasklet是利用软中断实现的一种下半部机制。tasklet和软中断在本质上很相似,行为表现也相近。tasklet有两类中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者之间唯一的区别在于HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。
2.tasklet的定义
tasklet由tasklet_struct结构表示,每个结构体单独代表一个tasklet,在<linux/interrupt.h>中定义为:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
1.next:链表中的下一个tasklet;
2.state:tasklet的状态。有三个取值:0,TASKLET_STATE_SCHED和TASKLET_STATE_RUN之间的取值。TASKLET_STATE_SCHED表明tasklet已经被调度,正准备投入运行,TASKLET_STATE_RUN表示该tasklet正在运行。
3.count:tasklet的引用计数,如果它不为0,则tasklet被禁止,不允许执行;只有当它为0时,tasklet才被激活,并且在被设置为挂起(TASKLET_STATE_SCHED)状态时,该tasklet才能够被执行。
4.结构体中的func成员是tasklet的处理程序,data是它唯一的参数。
3.tasklet的调度
内核使用tasklet_schedule()函数来执行tasklet的调度,已调度的tasklet存放在两个单处理器数据结构:tasklet_vec(普通的tasklet)和tasklet_hi_vec(高优先级的tasklet)。这两个数据结构都是由tasklet_struct结构体构成的链表。链表中的每个tasklet_struct 代表一个不同的tasklet。
tasklet是由tasklet_schedule()和tasklet_hi_shedule()函数进行调度的,它们接受一个指向tasklet_struct结构的指针作为参数。两个函数非常相似(区别在于一个使用TASKLET_SOFTIRQ,一个使用HI_SOFTIRQ()),接下来我们来看下tasklet_schedule()的内核源码:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
这个函数检查tasklet的状态,如果为TASKLET_STATE_SCHED,则表示已经被调度过了,直接返回。如果没有调度的话就会去执行__tasklet_schedule(t);
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags); //保存IF标志的状态,并禁用本地中断
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t; //下面的这两行代码就是为该tasklet分配per_cpu变量
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ); //触发软中断,让其在下一次do_softirq()的时候,有机会被执行
local_irq_restore(flags); //恢复前面保存的标志
}
由上面的代码可以看出,每次中断只会向其中的一个cpu注册,而不是所有的cpu。完成注册后的tasklet由tasklet_action()函数来执行。
4.执行tasklet
前面说过tasklet被放在一个全局的tasklet_vec的链表中,链表中的元素是tasklet_struct结构体。内核中有个ksoftirqd()的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了(pending),就执行相应的处理函数。tasklet对应的处理函数就是tasklet_action,这个函数在系统启动初始化软中断时,就在软中断向量表中注册。tasklet_action()遍历全局的tasklet_vec链表。链表中的元素为tasklet_struct结构体
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).head; //得到当前处理器上的tasklet链表tasklet_vec或者tasklet_hi_vec.
__get_cpu_var(tasklet_vec).head = NULL; //将当前处理器上的该链表设置为NULL, 达到清空的效果。
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
local_irq_enable(); //允许响应中断
//循环遍历获得链表上的每一个待处理的tasklet
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;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
在遍历执行时,在tasklet_trylock()和tasklet_unlock()这一段函数中,完成的功能是:首先会去检查count值时否为0,前面已经分析过,当值不为0的时候,说明该tasklet被禁止,如果没有被禁止,则执行其注册的函数,首先会检查tasklet_state的标志位是否是TASKLET_STATE_RUN状态,如果是,则表示该任务已经在别的处理器上运行,如果没有运行,则将其状态标志设置为TASKLET_STATE_RUN,这样别的处理器就不会再去执行它了,这就保证了在同一时间里,相同类型的tasklet只能有一个在运行。
5.Tsklet提供的API
5.1.声明一个Tasklet
静态创建
声明一个tasklet,可以使用下面<linux/interrupt.h>中定义的两个宏中的一个:
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
这两个宏都能根据给定的名字静态的创建一个tasklet_struct结构。当该tasklet被调度后,给定的函数func会被执行,data为其参数。这两个宏的区别在于前者前面一个宏把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态,另一个把引用计数器设置为1,所以该tasklet处于禁止状态。
动态创建
也可以使用一个间接的引用(一个指针)赋给一个动态创建的tasklet_struct结构的方式来初始化一个tasklet_init()函数:
tasklet_init(t, tasklet_handler, dev) 动态创建
5.2.编写tasklet处理函数
因为是软中断实现的,这就意味着不能在tasklet处理函数中使用信号量或者一些阻塞的函数。两个相同的tasklet绝不会同时执行,所以,如果你的tasklet和其他的tasklet或者是软中断共享了数据,你必须进行相应的锁保护。
5.3.调度
只有通过调度才能使我们的tasklet有机会被执行,这就使用上面提到的tasklet_shedule()函数。
tasklet_schedule(&my_tasklet);
5.4.禁止或者激活一个tasklet
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic_inc();
}
可以用来禁止指定的tasklet,不过它无须再返回前等待tasklet执行完毕,这么做往往不太安全,因为你无法估计该tasklet是否仍在执行。
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
tasklet_disable()函数来禁止某个指定的tasklet,如果该tasklet当前正在执行,这个函数会等到它执行完毕再返回。
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic_dec();
atomic_dec(&t->count);
}
tasklet_enable()函数可以激活一个tasklet。
5.5.删除一个tasklet
通过调用tasklet_kill()函数从挂起的队列中去掉一个tasklet。该函数的参数是一个指向某个tasklet的tasklet_struct的指针。这个函数会等待tasklet执行完毕,然后再将它移除。该函数可能会引起休眠,所以要禁止在中断上下文中使用。
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");
while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
do {
yield();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}
6.编写自己的tasklet任务
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
char my_tasklet_data[]="my_tasklet_function was called";
/* Bottom Half Function */
void my_tasklet_function( unsigned long data )
{
printk( "%s\n", (char *)data );
}
//声明tasklet
DECLARE_TASKLET( my_tasklet, my_tasklet_function,(unsigned long) &my_tasklet_data );
int init_module( void )
{
/* Schedule the Bottom Half */
printk("diable my_tasklet\n");
tasklet_disable(&my_tasklet);
// printk("enable my_tasklet\n");
// tasklet_enable(&my_tasklet);
tasklet_schedule( &my_tasklet );
return 0;
}
void cleanup_module( void )
{
/* Stop the tasklet before we exit */
tasklet_kill( &my_tasklet );
return;
}
MODULE_AUTHOR("support@ingben.com");
MODULE_DESCRIPTION ("tasklet of buttom half test2");
MODULE_LICENSE ("GPL v2");