Kernel tasklet
内核 tasklet 机制是在软中断的基础上实现的。我们知道软中断有如下两点,导致比较难用:
(1)软中断在内核中静态注册。如果要增加新的软中断,必须修改内核代码,重新编译内核镜像。
(2)SMP系统中,软中断action被所有CPU共享(即,同一个软中断同时可以在多个CPU上运行),需要用户自己保证软中断处理函数的可重入性。
tasklet 设计目标就是解决软中断如上两点不足之处,使得中断处理简单易用。针对第一点,tasklet设计了链表结构,提供 tasklet_schedule() 和 tasklet_hi_schedule() 等接口供用户动态注册 tasklet 处理函数;针对第二点,tasklet 设计了per-cpu变量(即,每个CPU都维护自己的一份 tasklet链表结构),从而避免了同一个 tasklet 被多个CPU共享,使得 tasklet 的处理函数不必是可重入的。
tasklet 占用两种软中断类型,一种是 HI_SOFTIRQ,代表高优先级的tasklet;另外一种是 TASKLET_SOFTIRQ,代表低优先级的 tasklet,两种类型的 tasklet 在实现原理上一样,以下仅以 TASKLET_SOFTIRQ 为例分析其工作原理。
tasklet 数据关系如下图所示:
1,tasklet及链表结构:
struct tasklet_struct
{
struct tasklet_struct *next; // 链表指针
unsigned long state; // 状态:是否运行,是否被调度
atomic_t count; // 引用计数
void (*func)(unsigned long); // tasklet处理函数
unsigned long data; // tasklet处理数据
};
struct tasklet_head {
struct tasklet_struct *head; // 指向tasklet链表头
struct tasklet_struct **tail; // 指向tasklet链表尾
};
2,tasklet软中断注册:
由于tasklet是基于软中断实现的,那么tasklet首先得在软中断上注册一个软中断处理函数 tasklet_action(),然后,被软中断调度运行的 tasklet_action 在per-cpu变量 tasklet_vec中找到当前CPU对应的 tasklet_head 链表,一次处理完该链表上所有的 tasklet_struct:
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); // 注册低优先级 tasklet 处理函数 tasklet_action
open_softirq(HI_SOFTIRQ, tasklet_hi_action); // 注册高优先级 tasklet 处理函数 tasklet_hi_action
}
3,调度运行每个 tasklet_struct:
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable(); // 关中断操作链表
list = __this_cpu_read(tasklet_vec.head); // 从 tasklet_vec 中一次性获取当前CPU上的所有 tasklet_struct
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
local_irq_enable(); // 开中断
while (list) { // 循环处理链表中所有 tasklet_struct
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 处理函数
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();
}
}
4,tasklet的用户初始化和动态注册:
(1)用户初始化 tasklet:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
(2)用户注册 tasklet:
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
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);
}
tasklet_schedule() 将用户定义的 tasklet_struct 加入当前CPU的 tasklet 链表中,等待 tasklet_action()在适当的时候调度执行。
注意:由于 tasklet_action() 处理方式是:一次性退出所有当前CPU上的 tasklet_struct,所以调用一次 tasklet_schedule(),该tasklet_struct只会被调度运行一次,如果需要重复执行,需要用户自己重复调用 tasklet_schedule。
转载于:https://my.oschina.net/yepanl/blog/3050052