前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

Tasklet

作者头像
DragonKingZhu
发布2020-03-24 16:23:04
9120
发布2020-03-24 16:23:04
举报

Tasklet

有的时候在驱动程序中需要延迟某些操作的进行,最典型的操作就是在驱动程序的中断处理函数延迟操作。比如在DMA驱动中,当数据传输完毕之后会触发中断的,通常这时候会启动一个tasklet来完成耗时的操作,也就是中断的下半部,让中断尽早的返回。

在Softirq中说过了,Tasklet的实现是基于Softirq的。也就是说Tasklet是Softirq中的一种。根据优先级不同,Linux将Tasklet分为两类如下:

代码语言:javascript
复制
enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

其中HI_SOFTIRQ和TASKLET_SOFTIRQ就是提供给Tasklet使用,HI_SOFTIRQ的优先级比较高,一般驱动程序很少见到使用。

Tasklet定义

linux内核使用tasklet_struct结构体来表示一个Tasklet

代码语言:javascript
复制
struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

next: 用于指向下一个Tasklet

state: 用于表示当前Tasklet的状态,linux内核定义了两种状态,如下:

代码语言:javascript
复制
enum
{
	TASKLET_STATE_SCHED,	/* Tasklet is scheduled for execution */
	TASKLET_STATE_RUN	/* Tasklet is running (SMP only) */
};

TASKLET_STATE_SCHED用于表示当前tasklet已经被提交,但是还没有执行,可能还在Tasklet列表中。

TASKLET_STATE_RUN用于表示当前tasklet正在执行,此状态只在SMP系统下有效。

count: 用于表示当前tasklet是否disable/enable。如果count=0表示tasklet是enabled,可以被调度执行,否则不可以被调度。

func: 该tasklet上的延迟回调函数,而date则是该回调函数的参数。

申明Tasklet

如果在驱动程序中需要使用tasklet,就需要先申明一个tasklet,驱动程序可以使用如下的宏同时初始化一个tasklet

代码语言:javascript
复制
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

同样内核也提供了一个相似的宏,用来申明一个disable状态的tasklet

代码语言:javascript
复制
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

除了静态申明以及初始化一个tasklet,内核也提供一个动态初始化tasklet的函数

代码语言:javascript
复制
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之后,就需要将其加入到系统的tasklet管理链表中来,内核定义了如下的结构用来管理tasklet

代码语言:javascript
复制
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);

head: 用来执行tasklet对象链接的第一个节点。

tail: tail是个双指针,用来执行tasklet_struct指针的指针,而每个tasklet_struct中有一个next指针,所以可以理解tail总是指向最后一个节点的next。

系统为HI_SOFTIRQ和TASKLET_SOFTIRQ类型定义了各自的tasklet_head管理链表,而且tasklet_vec和tasklet_hi_vec都是per-cpu变量。

也就是说系统使用tasklet_vec用来管理系统中所有softirq类型为TASKLET_SOFTIRQ的tasklet, tasklet_hi_vec同理。

提交tasklet

当在驱动程序中初始化一个tasklet之后,在需要延迟的时候就需要将该tasklet加入到系统的tasklet_vec链表中,提交到系统中。linux系统使用tasklet_schedule函数用来提交一个tasklet

代码语言:javascript
复制
static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

函数首先要提交的tasklet的state上的TASKLET_STATE_SCHED位是否已经置1,如果置1说明该tasklet已经被提交过,如果没有置1,则返回的是0,同时将state的TASKLET_STATE_SCHED位置1,置1之后表示这个tasklet已经被提交了。test_and_set_bit函数的作用就是返回旧的值,设置新的值。

代码语言:javascript
复制
void __tasklet_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);                                       ---------------A
	t->next = NULL;                                              ---------------B
	*__this_cpu_read(tasklet_vec.tail) = t;
	__this_cpu_write(tasklet_vec.tail, &(t->next));
	raise_softirq_irqoff(TASKLET_SOFTIRQ);                       ----------------C
	local_irq_restore(flags);                                    ----------------D
}

A: 关闭本地中断,虽然tasklet_vec是per-cpu变量,但是如果不关闭中断仍然会存在并发的情况,需要关闭本地中断。

B: 此处的操作就是将t加入到tasklet_vec链表的末尾,同时需要更改tail和next指针的指向。

C: 这时候就需要触发softirq,在softirq小节有讲到。

D: 恢复中断。

Tasklet机制的初始化

在开机启动的时候,内核使用softirq_init函数来初始化softirq, 用来安装了tasklet的执行函数。

代码语言:javascript
复制
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);
}

可以看到为每个cpu都初始化了tasklet_vec和tasklet_hi_vec, 同时为TASKLET_SOFTIRQ和HI_SOFTIRQ安装了各自对于的处理函数。

执行Tasklet

当一个tasklet提交到系统之后,系统会在合适的时机处理该tasklet,最终调用到tasklet_action函数中。

代码语言:javascript
复制
static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();                                                      ------------------------A
	list = __this_cpu_read(tasklet_vec.head);                                 ------------------------B
	__this_cpu_write(tasklet_vec.head, NULL);                                  -----------------------C
	__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));       ------------------------D
	local_irq_enable();

	while (list) {
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {                                           ------------------------E
			if (!atomic_read(&t->count)) {                              -----------------------F
				if (!test_and_clear_bit(TASKLET_STATE_SCHED,        ------------------------H
							&t->state))
					BUG();
				t->func(t->data);                                    -----------------------P
				tasklet_unlock(t);                                   -----------------------G
				continue;
			}
			tasklet_unlock(t);
		}

		local_irq_disable();                                              -----------------------Q
		t->next = NULL;                                                   -----------------------X
		*__this_cpu_read(tasklet_vec.tail) = t;    
		__this_cpu_write(tasklet_vec.tail, &(t->next));
		__raise_softirq_irqoff(TASKLET_SOFTIRQ);                          ---------------------Y
		local_irq_enable();
	}
}

A: 因为接下来需要操作到tasklet_vec,需要关闭本地中断,防止并发操作影响到tasklet_vec的数据。

B: 将整个tasklet_vec链表的数据保存到临时变量list中。

C: 将整个tasklet_vec的head指向NULL。

D: 同时初始化tail执行为NULL

E: 这时候就从list链表取出一个tasklet,首先通过tasklet_trylock函数判断

代码语言:javascript
复制
static inline int tasklet_trylock(struct tasklet_struct *t)
{
	return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

该函数就是判断state的TASKLET_START_RUN是否设置为1, 如果已经设置为1,则返回0,否则返回1。同时会设置TASKLET_START_RUN的位为1。

那这里为什么需要判断这个TASKLET_START_RUN标志呢? 根据前面的知识此标志只有在SMP系统有效。所以在SMP系统下有如下的情况:

假如有来两个处理的系统A处理器和B处理器。当前有个tasklet任务T已经提交到处理器A,并且已经调度执行了,此时TASKLET_STATE_SCHED状态已经清0。此时假设外部发生了一次中断,系统将此次中断处理权交给了B处理器,而在B的中断处理函数中调用了tasklet_schedule把tasklet T提交到B处理器,因为此taksklet T的状态TASKLET_STATE_SCHED的状态已经清0,从而可以提交成功。这样一下同一个Tasklet就有可能出现在两个处理器上执行,所以引入了TASKLET_START_RUN标志。还是前面的情况,因为在A处理器已经设置了TASKLET_STATE_RUN标志,所以在B处理器执行的时候就会失败,从而防止了统一个tasklet执行在多个处理器上。当然了这种情况只出现在SMP系统中。

F: 读取count的值,判断是否是enable的tasklet。disable的tasklet是不允许执行的。

H: 将state的TASKLET_STATE_SCHED的位清0。

P: 执行该tasklet的回调处理函数

G: 将state的TASKLET_START_RUN的位清0

Q: 因为接下来需要操作到tasklet_vec变量,为了防止中断引入并发问题,关闭中断

X: 将上述不符合条件的tasklet重新加入到tasklet_vec链表中进行管理。因为在上述的执行的过程中也有新的tasklet加入到tasklet_vec中

Y: 再次触发softirq。

其他Tasklet操作

enable一个tasklet

代码语言:javascript
复制
static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic();
	atomic_dec(&t->count);
}

将指定的tasklet对象t上的coun减去1。

disable一个tasklet

代码语言:javascript
复制
static inline void tasklet_unlock_wait(struct tasklet_struct *t)
{
	while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { barrier(); }
}

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	atomic_inc(&t->count);
	smp_mb__after_atomic();
}

static inline void tasklet_disable(struct tasklet_struct *t)
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}

内核提供了两个版本的disable函数,相比tasklet_disable_nosync函数tasklet_disable函数在disable的时候会调用tasklet_unlock_wait等待,主要是等待TASKLET_START_RUN的状态被清除,也就是等待tasklet被处理完毕后在disable。

kill掉一个tasklet

代码语言:javascript
复制
void tasklet_kill(struct tasklet_struct *t)
{
	if (in_interrupt())
		pr_notice("Attempt to kill tasklet from interrupt\n");

	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		do {
			yield();
		} while (test_bit(TASKLET_STATE_SCHED, &t->state));
	}
	tasklet_unlock_wait(t);
	clear_bit(TASKLET_STATE_SCHED, &t->state);

该函数最终会通过清除TAKLET_STATE_SCHED状态位,使器不再被调用。而如果当前tasklet已经提交但是没有执行,tasklet_kill将会睡眠直到该tasklet从tasklet_vec链表中删除。如果当前tasklet已经提交并且正在运行,这时候就需要等待tasklet运行完毕。该函数一般会在驱动的卸载函数中被调用到。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Tasklet
  • Tasklet定义
  • 申明Tasklet
  • 管理tasklet
  • 提交tasklet
  • Tasklet机制的初始化
  • 执行Tasklet
  • 其他Tasklet操作
相关产品与服务
智能推荐平台
智能推荐平台(Intelligent Recommendation Platform,IRP)是集生态、技术、场景于一体,采用业界领先的AI学习技术和智能推荐算法,基于腾讯多年在超大型场景中积累的最佳实践方法论,助力客户业务实现增长的企业级应用产品。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档