目录
1. 简介
tasklet是基于softirq的,它在中断上下文执行,不屏蔽中断,在其中不能执行睡眠/等待操作。
2. 存储结构
softirq在linux kernel中由一个数组存储,每一种类型的softirq对应一个action。
softirq的类型定义在include/linux/interrupt.h文件中,共有(NR_SOFTIRQS - 1)个。
3. 代码流程
3.1 open_softirq
将传入的action,赋值到softirq_vec对应类型的位置上。
3.2 raise_softirq_irqoff
在有softirq需要处理时,调用raise_softirq_irqoff函数,该函数一共做了两件事:
- 将传入的参数类型softirq的pending bit置1。
- wakeup ksoftirqd smp_hotplug_thread。
存放pending bit的变量,是一个per-cpu变量,名为irq_stat,它的定义如下:
kernel/softirq.c
#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif
arch/arm64/include/asm/hardirq.h
typedef struct {
unsigned int __softirq_pending;
unsigned int ipi_irqs[NR_IPI];
} ____cacheline_aligned irq_cpustat_t;
唤醒的ksoftirqd,是一个smp_hotplug_thread线程,它的定义为:
kernel/softirq.c
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};
其中的成员做简单介绍:
- store: DEFINE_PER_CPU(struct task_struct *, ksoftirqd); ----定义的per-cpu task_struct。
- thread_should_run: 在wakeup这个线程时,会先调用这个函数,判断是否唤醒。
- thread_fn: wakeup ksoftirqd后,真正运行的核心函数。
- thread_comm: per-cpu的ksoftirqd名称定义,例如ksoftirqd/0。
3.3 run_ksoftirqd
在执行完raise_soft_irqoff后,若当前cpu不在中断中,则会唤醒ksoftirqd线程,执行run_ksoftirqd函数,再看看一下,这个run_ksoftirqd到底做了哪些事。
函数定义如下:
kernel/softirq.c
static void run_ksoftirqd(unsigned int cpu)
{
local_irq_disable();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
local_irq_enable();
cond_resched_rcu_qs();
return;
}
local_irq_enable();
}
这个函数非常简单,主要做的事情就是,关闭本地中断,调用__do_softirq函数。继续查看__do_softirq。
3.4 __do_softirq
这个函数篇幅较长,后续再完善,主要功能为:遍历所有的softirq类型,若有pending bit,则执行相应的action。
4. tasklet
1) tasklet位于softirq_vec数组中的第7个元素,为softirq_vec[6]。
2) tasklet注册action的流程为:
start_kernel
softirq_init {
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
3) 注册的action,分别为tasklet_action / tasklet_hi_action。
4.1 tasklet_action
先看一下代码,根据代码进行分析。
kernel/softirq.c
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __this_cpu_read(tasklet_vec.head); ------ 1)
__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_vec上所有的node
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { //尝试拿锁,若拿到则处理该tasklet
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);
}
//若没拿到锁,或者其它cpu正在处理该tasklet,则将其重新放入list中,raise后继续遍历下一个
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();
}
}
- 读取per-cpu的变量tasklet_vec,复制给临时变量list。
- 遍历list中的所有元素,若某个tasklet的count为0时,则调用tasklet->func。若不为0则将其添加到tasklet_list中,并设置tasklet pending bit 为 1。