上一个博客写了中断下半部的软中断softirq部分的一些学习笔记,这批博客接着写tasklet微任务部分。
仅仅是平时看代码中的一些笔记,记下了怕忘了。
Tasklet是中断下半部的一种,它工作在中断上下文。同一个tasklet对象同一时刻只能在一个cpu上运行。
Tasklet的数据结构为
struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; |
各个字段的含义如下:
字段 |
含义 |
state |
Tasklet的状态,主要包括两种状态: TASKLET_STATE_SCHED 表示此tasklet已被调度,单还未执行 TASKLET_STATE_RUN 表示此tasklet已在cpu上运行 |
count |
引用计数值,不为0表示此tasklet被禁止,不允许执行 |
func |
要执行的函数 |
data |
函数的参数 |
这里不分析tasklet的用法,只是看下tasklet是如何做到串行化的,也就是同一个tasklet对象同一时刻只能在一个cpu上运行。
调用tasklet_schedule来提交一个tasklet对象,该函数首先设置此tasklet对象的state字段为TASKLET_STATE_SCHED状态,表明该tasklet对象已经被提交但还没有被运行。
static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); } |
tasklet_schedule函数首先调用test_and_set_bit设置state字段为TASKLET_STATE_SCHED,并返回它之前的值,如果之前值为0(即是TASKLET_STATE_SCHED状态),那么就调用__tasklet_schedule函数。这里说明一个tasklet对象在已调度还未执行之前,另一个相同的tasklet对象又被调度了,它也只会执行一次。如果test_and_set_bit返回值为非0,及state字段状态值为TASKLET_STATE_RUN,那么就不会调用__tasklet_schedule函数了,而是直接返回。
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对象加入所在cpu的percp数组tasklet_vec中。raise_softirq_irqoff函数会设置软中断中TASKLET_SOFTIRQ相应位为pending状态。
test_and_set_bit操作是原子操作,如果同时有两个处理器同时调用tasklet_schedule函数来调度同一个tasklet对象,那么只有一个会被成功提交。
TASKLET_STATE_SCHED是确保tasklet串行化执行的第一道防线,如果一个tasklet对象已被调度到处理器上运行,那么TASKLET_STATE_SCHED标志就会被清除。
static void tasklet_action(struct softirq_action *a) { struct tasklet_struct *list; 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); } } } |
tasklet_trylock函数会调用test_and_set_bit原子操作判断此tasklet对象是否已被其他的cpu调度开始执行了,具体是判断tasklet对象的state字段是否设置为TASKLET_STATE_RUN状态。tasklet_trylock成功表明此tasklet对象还没有被调度执行。
tasklet_trylock成功之后,原子读取count字段,如果此字段为0,表明此tasklet处于激活状态。如果此字段为非0,表明此tasklet被禁止,不允许执行。
接下来调用test_and_clear_bit清除掉TASKLET_STATE_SCHED标志。这样其他CPU就可以调用tasklet_schedule函数提交此tasklet对象。
正是通过TASKLET_STATE_RUN标志保证了SMP架构下面的同一tasklet对象同一时刻的串行化执行。