当在驱动程序中初始化一个tasklet之后,在需要延迟的时候就需要将该tasklet加入到系统的tasklet_vec链表中,提交到系统中。linux系统使用tasklet_schedule函数用来提交一个tasklet
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags); ---------------A
t->next = NULL; ---------------B
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
raise_softirq_irqoff(TASKLET_SOFTIRQ); ----------------C
local_irq_restore(flags); ----------------D
}
A: 关闭本地中断,虽然tasklet_vec是per-cpu变量,但是如果不关闭中断仍然会存在并发的情况,需要关闭本地中断。
Tasklet机制的初始化
在开机启动的时候,内核使用softirq_init函数来初始化softirq, 用来安装了tasklet的执行函数。
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);
}
可以看到为每个cpu都初始化了tasklet_vec和tasklet_hi_vec, 同时为TASKLET_SOFTIRQ和HI_SOFTIRQ安装了各自对于的处理函数。
执行Tasklet
当一个tasklet提交到系统之后,系统会在合适的时机处理该tasklet,最终调用到tasklet_action函数中。
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable(); ------------------------A
list = __this_cpu_read(tasklet_vec.head); ------------------------B
__this_cpu_write(tasklet_vec.head, NULL); -----------------------C
__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head)); ------------------------D
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { ------------------------E
if (!atomic_read(&t->count)) { -----------------------F
if (!test_and_clear_bit(TASKLET_STATE_SCHED, ------------------------H
&t->state))
BUG();
t->func(t->data); -----------------------P
tasklet_unlock(t); -----------------------G
continue;
}
tasklet_unlock(t);
}
local_irq_disable(); -----------------------Q
t->next = NULL; -----------------------X
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
__raise_softirq_irqoff(TASKLET_SOFTIRQ); ---------------------Y
local_irq_enable();
}
}
A: 因为接下来需要操作到tasklet_vec,需要关闭本地中断,防止并发操作影响到tasklet_vec的数据。
B: 将整个tasklet_vec链表的数据保存到临时变量list中。
C: 将整个tasklet_vec的head指向NULL。
D: 同时初始化tail执行为NULL
E: 这时候就从list链表取出一个tasklet,首先通过tasklet_trylock函数判断
static inline int tasklet_trylock(struct tasklet_struct *t)
{
return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}
该函数就是判断state的TASKLET_START_RUN是否设置为1, 如果已经设置为1,则返回0,否则返回1。同时会设置TASKLET_START_RUN的位为1。
那这里为什么需要判断这个TASKLET_START_RUN标志呢? 根据前面的知识此标志只有在SMP系统有效。所以在SMP系统下有如下的情况:
假如有来两个处理的系统A处理器和B处理器。当前有个tasklet任务T已经提交到处理器A,并且已经调度执行了,此时TASKLET_STATE_SCHED状态已经清0。此时假设外部发生了一次中断,系统将此次中断处理权交给了B处理器,而在B的中断处理函数中调用了tasklet_schedule把tasklet T提交到B处理器,因为此taksklet T的状态TASKLET_STATE_SCHED的状态已经清0,从而可以提交成功。这样一下同一个Tasklet就有可能出现在两个处理器上执行,所以引入了TASKLET_START_RUN标志。还是前面的情况,因为在A处理器已经设置了TASKLET_STATE_RUN标志,所以在B处理器执行的时候就会失败,从而防止了统一个tasklet执行在多个处理器上。当然了这种情况只出现在SMP系统中。
F: 读取count的值,判断是否是enable的tasklet。disable的tasklet是不允许执行的。
H: 将state的TASKLET_STATE_SCHED的位清0。
P: 执行该tasklet的回调处理函数
G: 将state的TASKLET_START_RUN的位清0
Q: 因为接下来需要操作到tasklet_vec变量,为了防止中断引入并发问题,关闭中断
X: 将上述不符合条件的tasklet重新加入到tasklet_vec链表中进行管理。因为在上述的执行的过程中也有新的tasklet加入到tasklet_vec中
Y: 再次触发softirq。
其他Tasklet操作
enable一个tasklet
static inline void tasklet_enable(struct tasklet_struct *t)
{
smp_mb__before_atomic();
atomic_dec(&t->count);
}
将指定的tasklet对象t上的coun减去1。
disable一个tasklet
static inline void tasklet_unlock_wait(struct tasklet_struct *t)
{
while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { barrier(); }
}
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
atomic_inc(&t->count);
smp_mb__after_atomic();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
smp_mb();
}
内核提供了两个版本的disable函数,相比tasklet_disable_nosync函数tasklet_disable函数在disable的时候会调用tasklet_unlock_wait等待,主要是等待TASKLET_START_RUN的状态被清除,也就是等待tasklet被处理完毕后在disable。
kill掉一个tasklet
void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
pr_notice("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);
该函数最终会通过清除TAKLET_STATE_SCHED状态位,使器不再被调用。而如果当前tasklet已经提交但是没有执行,tasklet_kill将会睡眠直到该tasklet从tasklet_vec链表中删除。如果当前tasklet已经提交并且正在运行,这时候就需要等待tasklet运行完毕。该函数一般会在驱动的卸载函数中被调用到。