linux tasklet 的分析与使用

linux tasklet 的分析与使用

目录

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

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

tasklet 源码分析

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

c 复制代码
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 又被系统启动时调用。

c 复制代码
/*
 * 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);
}

系统启动时调用

c 复制代码
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 调度的分析

相关代码如下:

c 复制代码
//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 的实现

c 复制代码
//在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 的函数,该函数是汇编写的,源码如下

c 复制代码
//arch/arm/lib 
#include <linux/linkage.h>
#include <asm/assembler.h>
#include "bitops.h"
                .text

testop	_test_and_change_bit, eor, str

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

c 复制代码
/**
 * 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

c 复制代码
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 使用简单示例

c 复制代码
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>

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 是利用软中断实现的一种下半部机制,本质上是软中断的一种变种,运行在中断上下文中.

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言