一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调度tasklet使得下半部分的工作得以完成,但是下半部分何时执行属于内核的工作。
由于tasklet依靠软中断实现,所以tasklet不能休眠。这就意味着不能在tasklet中使用信号量或其他任何可能引起阻塞的函数。两个相同的tasklet绝不会同时执行。这点是和软中断的一个重要的区别。尽管两个不同的tasklet可以在两个处理器上同时执行,但只要不同的tasklet不共享数据,就不会有任何问题。当然,如果要共享数据,仍然需要使用本地锁保护临界区数据。
本文档描述基于3.14.77内核。
1. 定义
tasklet定义在linux/interrupt.h中,实现在kernel/softirq.c中。
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data; // 给tasklet处理函数传递的参数
};
tasklet_struct.state可以设置成0,也可以设置成两个枚举值(TASKLET_STATE_SCHED和TASKLET_STATE_RUN),除此之外,tasklet_struct.state不能设置成其他的值。
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
tasklet_struct.count是tasklet的引用计数器,如果count不为0,表示tasklet被禁用,不允许执行。只有当count为0时,tasklet才会被激活,并且只有tasklet被设置为挂起时,该tasklet才能被执行(下一次调用do_softirq函数时会执行所有挂起的tasklet)。
初始化
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, , ATOMIC_INIT(), func, data } #define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, , ATOMIC_INIT(), func, data } void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
由于tasklet_init函数本身不会创建tasklet_struct结构体,因此,在调用tasklet_init函数之前,需要使用kmalloc或其他类似的内核空间分配函数为tasklet_struct结构体分配内存空间(动态创建tasklet_struct结构体)。
调度
tasklet_schedule(&mytasklet); //调用底半部,注意,这不是立即调用,是调度,意味着tasklet底半部会在顶半部执行完成后才会执行。
在_tasklet_schedule函数中调用raise_softirq_irqoff函数挂起了TASKLET_SOFTIRQ中断。这就意味着一旦do_softirq函数在某个时刻被调用,系统就会扫描软中断向量表(softirq_vec),这时发现TASKLET_SOFTIRQ软中断被挂起,就会立刻执行与TASKLET_SOFTIRQ软中断对应的处理程序。在TASKLET_SOFTIRQ软中断处理程序中会扫描tasklet向量表(tasklet_vec)。
销毁
一般会在Linux驱动的exit函数中使用tasklet_kill函数(kernel/softirq.c)销毁当前的tasklet,实际上就是将tasklet_struct.state设为0。
extern void tasklet_kill(struct tasklet_struct *t);
extern void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu);
2. 应用
static struct tasklet_struct my_tasklet; static void tasklet_handler (unsigned long data)
{
printk(KERN_ALERT "tasklet_handler is running.\n");
} tasklet_init(&my_tasklet, tasklet_handler, ); // 初始化tasklet
tasklet_schedule(&my_tasklet); // 调度tasklet处理程序 tasklet_kill(&my_tasklet);
3. 小结
由于软中断是Linux系统全局的,整个Linux系统最多也只能有10个不同类型的软中断。而且如果不修改Linux内核源代码,这个最大中断数是不会变的,就算修改了内核源代码,最大也只能有32个软中断。为了可以处理更多的底半部,系统定义了两个特殊的软中断(HI_SOFTIRQ和TASKLET_SOFTIRQ),通过这两个软中断的处理函数可以执行两个tasklet向量(tasklet_vec和tasklet_hi_vec)中的tasklet处理程序。因此tasklet处理程序的执行效率要比软中断处理程序的执行效率低一些(也低不了太多)。这是因为tasklet处理程序需要先执行软中断处理程序,然后才能扫描tasklet向量,比软中断处理程序多了一步。
参考:
3. tasklet