linux tasklet 的分析与使用

linux tasklet 的分析与使用

目录

  • linux tasklet 的分析与使用
    • tasklet 源码分析
    • tasklet_shedule 调度的分析
    • tasklet 执行
    • tasklet 使用简单示例
  • 结论

tasklet 是利用软中断实现的一种下半部机制,本质上是软中断的一种变种,运行在中断上下文中.
有关于软中断的分析,可以参考之前的文章,有详细的分析。

tasklet 源码分析

interrupt.h 文件下,tasklet_struct结构体见下面代码,代码中有注释说明;

enum
{
	TASKLET_STATE_SCHED,	/* Tasklet is scheduled for execution */
	TASKLET_STATE_RUN	/* Tasklet is running (SMP only) */
};
struct tasklet_struct
{
	struct tasklet_struct *next; //多个tasklet 串成一个链表
	unsigned long state; // 表示调试状态,见上面的枚举 TASKLET_STATE_SCHED
	atomic_t count; // 0 表示tasklet 处理激活状态; 不为0表示tasklet 被禁止,不允许执行
	void (*func)(unsigned long); // 处理函数
	unsigned long data; //处理函数的数据
};

每个cpu维护两个tasklet 链表,在softirq.c中 tasklet_vec 和tasklet_hi_vec;
tasklet_vec 在软中断的优先级是6,见软中断文章中的枚举,tasklet_hi_vec的优先级是0
在softirq.c 中函数softirq_init 使用,而softirq_init 又被系统启动时调用。

/*
 * Tasklets
 */
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);

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);
}

系统启动时调用

asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
	char *after_dashes;
	.......
	/* Trace events are available after this */
	trace_init();

	context_tracking_init();
	/* init some links before init_ISA_irqs() */
	early_irq_init();
	init_IRQ();
	tick_init();
	rcu_init_nohz();
	init_timers();
	hrtimers_init();
	softirq_init();   //软中断初始化
	timekeeping_init();
	time_init();
	......
}

同一个tasklet不会同时运行,所以不需要处理tasklet和它本身之间的锁定情况。但是两个tasklet之间共享数据都应该使用spin_lock()和spin_unlock()进行保护

tasklet_shedule 调度的分析

相关代码如下:

//interrupt.h
static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}
//softirq.c
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);
}
EXPORT_SYMBOL(__tasklet_schedule);

test_and_set_bit 函数(原子的)设置tasklet_struct->state 为TASKLET_STATE_SCHED 标志位,然后返回state旧的值,返回为true,说明该tasklet 已经被挂入到tasklet 链表中,返回为false,表示没有持入tasklet 链表,需要调度,调用 __tasklet_schedule。
__tasklet_schedule 把tasklet 挂入 task_vec链表中 ; __tasklet_schedule 调用后不会立刻执行tasklet (从源码中可以看出 tasklet_schedule 只是对tasklet_struct 结构体设置 TASKLET_STATE_SCHED 标志位,只要tasklet 还没有执行,驱动程序多次调用tasklet_schedule也不起作用),需要等到软中断执行时才有机会运行,tasklet 挂在哪个cpu的tasklet_vec链表,那么哪个cpu的软中断来执行。

关于test_and_set_bit 的实现

//在arch/arm/include/asm/bitops.h 
#define ATOMIC_BITOP(name,nr,p)		_##name(nr,p)
#define test_and_change_bit(nr,p)	ATOMIC_BITOP(test_and_change_bit,nr,p)

转换后为_test_and_change_bit 的函数,该函数是汇编写的,源码如下

//arch/arm/lib 
#include 
#include 
#include "bitops.h"
                .text

testop	_test_and_change_bit, eor, str

testop 为汇编的宏,代码在 arch/arm/lib/bitops.h

/**
 * testop - implement a test_and_xxx_bit operation.
 * @instr: operational instruction
 * @store: store instruction
 *
 * Note: we can trivially conditionalise the store instruction
 * to avoid dirtying the data cache.
 */
	.macro	testop, name, instr, store
ENTRY(	\name		)
UNWIND(	.fnstart	)
	ands	ip, r1, #3
	strneb	r1, [ip]		@ assert word-aligned
	and	r3, r0, #31
	mov	r0, r0, lsr #5
	save_and_disable_irqs ip
	ldr	r2, [r1, r0, lsl #2]!
	mov	r0, #1
	tst	r2, r0, lsl r3
	\instr	r2, r2, r0, lsl r3
	\store	r2, [r1]
	moveq	r0, #0
	restore_irqs ip
	ret	lr
UNWIND(	.fnend		)
ENDPROC(\name		)
	.endm

tasklet 执行

软中断执行时会按照软中断状态 __softirq_pending 来依次执行pending 状态的软中断,当执行到TASKLET_SOFTIRQ类型软中断时,会调用 tasklet_action

执行的链条:exit_irq -> __do_softirq -> tasklet_action

static __latent_entropy void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;
	/*
	在关中断的情况下,读取tasklet_vec链表到临时链表list中,
	并重新初始化tasklet_vec 链表
	*/
	local_irq_disable();
	list = __this_cpu_read(tasklet_vec.head);
	__this_cpu_write(tasklet_vec.head, NULL);
	__this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
	local_irq_enable();

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

		list = list->next;
		/*
		tasklet_trylock 函数设计成一个锁,如果tasklet 已经处于RUNNING状态,即被设置了TASKLET_STATE_RUN标志位,
		tasklet_trylock返回false,表示不能获取该锁
		*/
		if (tasklet_trylock(t)) {
			//检测count 计数是否为0 ,为0则表示tasklet 处理可执行状态
			//tasklet_disable 影响的就是count
			if (!atomic_read(&t->count)) {
				// 清除调试标志TASKLET_STATE_SCHED
				if (!test_and_clear_bit(TASKLET_STATE_SCHED,
							&t->state))
					BUG();
				//tasklet 的执行函数
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}
		
		//如果tasklet 已经在其它CPU上执行的情况下,tasklet 返回false,这时会把tasklet
		//重新挂入当前CPU的tasklet_vec链表中,等待下一次触发TASKLET_SOFTIRQ类型的软中断才会被执行
		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();
	}
}

static inline int tasklet_trylock(struct tasklet_struct *t)
{
	return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	//见if (!atomic_read(&t->count)) 前面注释所产生的影响
	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();
}

如上述注释中所写,tasklet_trylock 保证了同一个tasklet 只能在一个CPU上运行,不能在多cpu上并行,这与之前文章讲软中断并行不一样,这是tasklet 与其它softirq的差别

tasklet_trylock 函数设计成一个锁,如果tasklet
已经处于RUNNING状态,即被设置了TASKLET_STATE_RUN标志位,tasklet_trylock返回false,表示不能获取该锁

tasklet 使用简单示例

#include 
#include 
#include 

char tasklet_data[] = "We use a string; but it could be pointer to a structure";

void tasklet_function(unsigned long data)
{
    printk("%s\n", (char *)data);
    return;
}

DECLARE_TASKLET(my_tasklet, tasklet_function, (unsigned long)tasklet_data);

static int __init my_init(void)
{
    //tasklet_disable(&my_tasklet);
    //printk("tasklet_disable\n");

    tasklet_schedule(&my_tasklet);
    printk("tasklet example\n");
    return 0;
}

void my_exit(void)
{
    tasklet_kill(&my_tasklet);
    printk("tasklet example cleanup\n");
    return;
}

module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("null");
MODULE_LICENSE("GPL");

结论

tasklet 是利用软中断实现的一种下半部机制,本质上是软中断的一种变种,运行在中断上下文中.

你可能感兴趣的:(linux驱动,linux,驱动开发)