Linux内核深度解析之中断、异常和系统调用——中断下半部之小任务

小任务

小任务(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();
	}
}

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/106955135