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

相关推荐
Mr. Cao code22 分钟前
Docker:颠覆传统虚拟化的轻量级革命
linux·运维·ubuntu·docker·容器
抓饼先生42 分钟前
Linux control group笔记
linux·笔记·bash
搞一搞汽车电子1 小时前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车
挺6的还1 小时前
25.线程概念和控制(二)
linux
您的通讯录好友1 小时前
conda环境导出
linux·windows·conda
代码AC不AC2 小时前
【Linux】vim工具篇
linux·vim·工具详解
码农hbk3 小时前
Linux signal 图文详解(三)信号处理
linux·信号处理
bug攻城狮3 小时前
Skopeo 工具介绍与 CentOS 7 安装指南
linux·运维·centos
宇宙第一小趴菜3 小时前
08 修改自己的Centos的软件源
linux·运维·centos
bug攻城狮3 小时前
彻底禁用 CentOS 7.9 中 vi/vim 的滴滴声
linux·运维·服务器·centos·vim