当前位置: 首页 > 工具软件 > Tasklet > 使用案例 >

中断下半部-tasklet

壤驷彦
2023-12-01

本文摘抄于奔跑吧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();
	}
}

 类似资料: