本文摘抄于奔跑吧linux内核:基于linux内核源码问题分析
tasklet是利用软中断实现的下半部机制,本质上是软中断的一个变种,运行在软中断上下文中
基本原理:每个cpu都有两个链表,分别保存的是普通优先级(tasklet_vec)和高优先级(tasklet_hi_vec)的tasklet_struct的链表。当调用tasklet_schedule,其实就是将tasklet_struct挂到这两个链表中,然后触发一个软中断。当执行软中断的时候(tasklet_action)。在回调函数中把链表里面所以的节点(tasklet_struct)取出来执行.
struct tasklet_struct
{
struct tasklet_struct *next;//tasklet串成的链表
/*
TASKLET_STATE_SCHED表示tasklet已经被调度,正准备运行
TASKLET_STATE_RUN表示tasklet正在运行中
*/
unsigned long state;
atomic_t count;/* 0表示tasklet处于激活状态, otherwise tasklet被禁止,不允许执行 */
void (*func)(unsigned long);//tasklet处理程序
unsigned long data;//处理程序的参数
};
每个CPU维护两个tasklet链表,一个用于普通优先级的tasklet_vec,另一个是高优先级的tasklet_hi_vec.且两者都是Per-CPU变量。链表中的每个节点tasklet_struct代表不同的tasklet
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
在系统启动时会初始化这两个链表,并且为这两个tasklet注册两个软中断,且回调函数分别是tasklet_action和tasklet_hi_action
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);
}
定义tasklet
两者的区别就是成员cout的值不同,前者处于激活状态,后者为1,处于禁止执行的状态
/* stat = TASKLET_STATE_SCHED, count = 0,激活状态 */
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
/* stat = TASKLET_STATE_SCHED, count = 1,禁止执行状态 */
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
动态初始化
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;
}
调度tasklet
将tasklet的state设置为TASKLET_STATE_SCHED,即准备运行的状态。返回true,说明该tasklet已经被挂入到tasklet链表中。返回false,则需要调用__tasklet_schedule,将该tasklet挂入链表tasklet_vec中
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
软中断的回调函数是在开中断的情况下执行的。因此此时是开中断状态。在关中断的情况下,将tasklet挂到taskle_vec链表中,然后在触发一个类型为TASKLET_SOFTIRQ的软中断(raise_softirq_irqoff(TASKLET_SOFTIRQ);就是把本CPU的软中断状态寄存器-__softirq_pending对应的标志位置上)
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执行时机:
tasklet_schedule调用了就会马上执行该软中断吗?并不是。tasklet_schedule只是将需要处理的tasklet挂到了tasklet链表中,并且触发了一个软中断,表示该cpu有需要处理的软中断。因此,需要等到软中断执行的时候,才能执行tasklet。此外tasklet_vec是Per-CPU变量。每个CPU都会有一个tasklet_vec.当一个tasklet1被挂到了某个CPU的tasklet_vec中时,就只能在该CPU上执行。(因为挂到链表时会将state设置为TASKLET_STATE_SCHED。该状态也只能有该CPU取消,因为该标记是在马上被执行前才能取消,具体看tasklet_action。其他CPU尝试将tasklet1挂到tasklet_vec时(test_and_set_bit(TASKLET_STATE_SCHED, &t->state)),会返回true.这样就不能被加入到其他CPU的tasklet_vec中。 只有在本CPU执行之后,才能清除TASKLET_STATE_SCHED,才能被加入到其他CPU上)
当tasklet被执行时,会调用回调函数tasklet_action
h->action就是执向了tasklet_action。然后在tasklet_action里面获取当前CPU的tasklet链表,逐一遍历处理,调用相应的回调函数。
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
这里的h->action对于tasklet来说就是 tasklet_action/tasklet_hi_action
asmlinkage void __do_softirq(void)
{
.............................
/* 循环处理所有的软中断.总共10个 */
do {
if (pending & 1) {
...............
h->action(h);
..............
}
h++;
pending >>= 1;
} while (pending);
...................
}
tasklet_action和do_softirq还不一样。这个是会把tasklet链表里面的所有回调函数都执行完。不会像后者有什么时间限制。tasklet_action会将链表中所有的元素都取出来。因此tasklet只有再次被schedule的时候,才会被重新加入到链表中。
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
/*
将本地CPU的tasklet_vec保存到临时的链表list中
然后重新初始化tasklet_vec,即head==tail
注意操作tasklet_vec是在关闭本地CPU的中断下进行的
*/
list = __this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
local_irq_enable();
/*
遍历整个链表处理tasklet
处理tasklet链表是在开中断的情况下进行的
*/
while (list) {
struct tasklet_struct *t = list;
list = list->next;
/*
设置tasklet状态为RUN.如果本身已经被RUN(说明该tasklet被其他CPU执行了),则返回false
本轮不执行该tasklet,这样保证了同一个tasklet只能在同一个CPU上运行
*/
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {/* count = 0,才表示激活状态*/
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))//清除SCHED标志
BUG();
t->func(t->data);
tasklet_unlock(t);//清除RUN标志
continue;
}
tasklet_unlock(t);
}
/*
下面是处理tasklet已经在其他CPU上运行的情况
重新将tasklet加入到CPU的tasklet_vec中,
然后再次触发软中断,即将"软中断状态寄存器"的对应为置上
*/
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();
}
}
tasklet_atcion中出现tasklet_trylock(t)获取锁失败的原因:
执行tasklet回调函数的时候是开中断的。如果设备A发生硬件中断,然后CPU0负责处理设备A的中断。假设CPU0在执行设备A的taskletA的回调函数时(1、在tasklet回调函数是开中断的;2、CPU0要求执行taskletA,需要先执行tasklet_schedule,将taskletA的状态设为TASKLET_STATE_SCHED,并且将taskletA加入到CPU0的链表tasklet_vec中;3 由于CPU0已经开始执行taskletA的回调函数,因此taskletA的状态肯定已经被设置为Run,且SCHED也被清除了),设备B产生了中断。因此taskletA会被暂停下来,去处理设备B的中断。假设在处理设备B的中断时,设备A又产生中断。而此次中断控制器将该中断分配给了CPU1,由于SCHED标志被清除了,CPU1调用tasklet_schedule是能够成功的,即能够将taskletA加入到CPU1的tasklet_vec中。但是由于taskletA的回调函数在CPU0上被打断了,即没有执行完毕。因此taskletA的RUN标志并未被清除。如果CPU1在tasklet_action中去设置taskletA的状态为RUN是会失败。
只有等到CPU0将taskletA的回调函数执行完,清除RUN标志后,CPU1才能去执行taskletA
tasklet_hi_action和上面的几乎差不多。只是操作的tasklet链表不同。
static void tasklet_hi_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __this_cpu_read(tasklet_hi_vec.head);
__this_cpu_write(tasklet_hi_vec.head, NULL);
__this_cpu_write(tasklet_hi_vec.tail, &__get_cpu_var(tasklet_hi_vec).head);
local_irq_enable();
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;
*__this_cpu_read(tasklet_hi_vec.tail) = t;
__this_cpu_write(tasklet_hi_vec.tail, &(t->next));
__raise_softirq_irqoff(HI_SOFTIRQ);
local_irq_enable();
}
}