小任务
小任务(tasklet,有的书中翻译为“任务蕾”)是基于软中断实现的。为什么要提供小任务?因为小任务相对软中断有以下优势:
(1)软中断的种类是编译时静态定义的,在运行时不能添加或删除;小任务可以在运行时添加或删除
(2)同一种软中断的处理函数可以在多个处理器上同时执行,处理函数必须是可以重入的,需要使用锁保护临界区;一个小任务同一时刻只能在一个处理器上执行,不要求处理函数是可以重入的。
小任务根据优先级分为两种:低优先级小任务和高优先级小任务。
1. 数据结构
小任务的数据结构如下:
include/linux/interrupt.h
struct tasklet_struct
{
struct tasklet_struct *next; /* 用来把小任务添加到单向链表中 */
unsigned long state; /* 小任务状态,取值:0表示小任务没有被调度;(1<<TASKLET_STATE_SCHED)表示小任务被调度,即将被执行;(1<<TASKLET_STATE_RUN)表示只在多处理器系统中使用,表示小任务正在被执行 */
atomic_t count; /* 计数,0表示允许小任务被执行,非零值表示禁止小任务执行 */
void (*func)(unsigned long); /* 处理函数, */
unsigned long data; /* 传给处理函数的参数 */
};
每个处理器有两条单向链表:低优先级小任务链表和高优先级小任务链表。
kernel/softirq.c
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);
2. 编程接口
定义一个静态的小任务,并且允许小任务被执行,方法:
DECLARE_TASKLET(name, func, data)
定义一个静态的小任务,并且禁止小任务被执行,方法:
DECLARE_TASKLET_DISABLED(name, func, data)
在运行时动态初始化小任务,并且允许被执行,方法:
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
函数tasklet_disable()用来禁止小任务被执行,如果小任务正在被执行,该函数等待小任务执行完。
void tasklet_disable(struct tasklet_struct *t);
函数tasklet_disable_nosync()用来禁止小任务被执行,如果小任务正在被执行,该函数不会等待小任务执行完。
void tasklet_disable_nosync(struct tasklet_struct *t);
函数tasklet_enable()用来允许小任务被执行。
void tasklet_enable(struct tasklet_struct *t)
函数tasklet_schedule()用来调度低优先级小任务:把小任务添加到当前处理器的低优先级小任务链表中,并且触发低优先级小任务软中断。
void tasklet_schedule(struct tasklet_struct *t);
函数tasklet_hi_schedule()用来调度高优先级小任务:把小任务添加到当前处理器的高优先级小任务链表中,并且触发高优先级小任务软中断。
void tasklet_hi_schedule(struct tasklet_struct *t);
函数tasklet_kill用来杀死小任务,确保小任务不会被调度和执行。如果小任务正在被执行,该函数等待小任务执行完。通常在内核模块卸载的时候调用该函数。
void tasklet_kill(struct tasklet_struct *t);
3. 技术原理
小任务是基于软中断实现的,根据优先级分为两种:低优先级小任务和高优先级小任务。软中断HI_SOFTIRQ执行高优先级小任务,软中断TASKLET_SOFTIRQ执行低优先级小任务。
(1)调度小任务
函数tasklet_schedule()用来调度低优先级小任务,函数tasklet_hi_schedule()用来调度高优先级小任务。以函数tasklet_schedule()为例说明:
include/linux/interrupt.h
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
kernel/softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
{
__tasklet_schedule_common(t, &tasklet_vec,
TASKLET_SOFTIRQ);
}
static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;
local_irq_save(flags);
head = this_cpu_ptr(headp);
t->next = NULL;
*head->tail = t;
head->tail = &(t->next);
raise_softirq_irqoff(softirq_nr);
local_irq_restore(flags);
}
如果小任务没有被调度过,那么首先设置调度标志位,然后把小任务添加到当前处理器的低优先级小任务链表的尾部,最后触发软中断TASKLET_SOFTIRQ。
(2)执行小任务
初始化的时候,把软中断TASKLET_SOFTIRQ的处理函数注册为函数tasklet_action,把软中断HI_SOFTIRQ的处理函数注册为函数tasklet_hi_action。
start_kernel() -> softirq_init()
kernel/softirq.c
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_SOFTIRQ的处理函数为tasklet_action */
open_softirq(HI_SOFTIRQ, tasklet_hi_action); /* 设置软中断HI_SOFTIRQ的处理函数为tasklet_hi_action */
}
以函数tasklet_action()为例,其代码如下:
kernel/softirq.c
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
struct tasklet_struct *list;
local_irq_disable();
list = tl_head->head; /* 把当前处理器的小任务链表中的所有小任务迁移到临时链表list中 */
tl_head->head = NULL;
tl_head->tail = &tl_head->head;
local_irq_enable();
while (list) { /* 遍历临时链表list,依次处理每个小任务,具体如下 */
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { /* 尝试锁住小任务,确保一个小任务同一时刻只在一个处理器上执行 */
if (!atomic_read(&t->count)) { /* 如果小任务的计数为0,表示允许小任务被执行 */
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; /* 如果尝试锁住小任务失败(表示小任务正在其他处理器上执行),或者禁止小任务执行,那么把小任务重新添加 */
*tl_head->tail = t; /* 到当前处理器的小任务链表的尾部,然后出发软中断 */
tl_head->tail = &t->next;
__raise_softirq_irqoff(softirq_nr);
local_irq_enable();
}
}