tasklet用于处理一些不那么紧急的任务,可以延后在将来的某个时间点来执行,linux通过中断来响应处理一些任务,这类任务通常是很紧急的需要立即处理,此类任务通常运行于interrupt context(中断上下文),存在较多限制。
- tasklet实现原理
tasklet实现基于软中断(softirq),而软中断基于硬件中断,它们的优先级逐次递增。
简单描述下处理流程,当前硬件中断时,内核根据irq号进而执行该IRQ的处理例程(ISR),irq处理例程结束后处理软中断,tasklet软中断例程遍历所有注册的tasklet并依次执行它们。
执行中断例程(非ISR) run_irq_on_irqstack_cond
中断例程执行完成后,在该函数中触发软中断 irq_exit_rcu();
irq_exit_rcu只是一个前端,所有的工作由__irq_exit_rcu实现
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq(); //触发软中断执行
tick_irq_exit();
}
该函数负责执行软中断例程(tasklet例程),该函数比较长,我这里摘抄核心部分说明,其余的是内核统计、中断禁用等与核心流程关系不大。 __do_softirq
//softirq_vec中存放了所有已注册的软中断例程
h = softirq_vec;
//循环处理所有待处理软中断
while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr; //中断号
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec;
prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
//这里执行软中断例程
h->action(h);
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count_set(prev_count);
}
h++;
pending >>= softirq_bit;
}
该函数循环处理所有软中断例程,其中实现tasklet的中断例程会执行,tasklet例程会执行所有已注册的tasklet任务。
- tasklet数据结构
/**
tasklet任务结构,表示一个tasklet。
*/
struct tasklet_struct
{
struct tasklet_struct *next; //多个tasklet通过next成员连接起来
unsigned long state; //任务状态
atomic_t count;
bool use_callback; //为1时调用callback函数
union {
/*
由于是union 结构所有func和callback只能设置一个
*/
void (*func)(unsigned long data); //任务例程
void (*callback)(struct tasklet_struct *t); //任务例程
};
unsigned long data; //需要传给任务函数的参数
};
如下图,通过宏DECLARE_TASKLET创建tasklet任务结构后,调用tasklet_schedule将该tasklet添加到当前CPU的tasklet_head结构,然后触发tasklet软中断,执行tasklet_action软中断处理例程,该中断例程除了启用中断、获取当前CPU的tasklet_head结构,核心处理逻辑是从tasklet_heap中取出任务并执行任务函数(tasklet_struct->func)。
内核提供的tasklet处理接口
//创建tasklet_struck实例
#define DECLARE_TASKLET(name, _callback) \
struct tasklet_struct name = { \
.count = ATOMIC_INIT(0), \
.callback = _callback, \
.use_callback = true, \
}
//将该 t 添加到当前CPU的tasklet_head中并调度tasklet
static inline void tasklet_schedule(struct tasklet_struct *t)
//同tasklet_schedule一致,不过它执行高优先级的软中断例程
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
//将 t 从tasklet_heap中移除
void tasklet_kill(struct tasklet_struct *t)
//这两个函数做的事情是一致的,都是将tasklet任务函数设置到tasklet_struct结构,出于兼容性考虑,内核都保留了下来。
void tasklet_setup(struct tasklet_struct *t,void (*callback)(struct tasklet_struct *))
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
实战编写tasklet案例,这里的tasklet任务函数只是打印一个消息到内核,实际驱动开发中,我们可以用tasklet完成很多简单、可快速完成的任务,这类任务用kthread太重了,tasklet则很合适。
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/interrupt.h>
//tasklet处理函数
static void my_tasklet_fn(unsigned long data)
{
printk("execute my_tasklet_fn data %ld\n",data);
}
static struct tasklet_struct _my_tasklet;
static int __init tasklet_example_init(void)
{
int retval=0;
//初始化tasklet
tasklet_init(&_my_tasklet,&my_tasklet_fn,123);
//将tasklet添加到待执行列表,并调度tasklet软中断
tasklet_schedule(&_my_tasklet);
printk("tasklet_example_init...");
return retval;
}
static void __exit tasklet_example_exit(void)
{
printk("tasklet_example_exit...");
}
module_init(tasklet_example_init);
module_exit(tasklet_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangQiang");
make -j 6 编译内核模块
通过insmod tasklet_example.ko 加载内核模块到内核,可以看到成功执行了tasklet函数。