Linux内核-tasklet微任务

tasklet用于处理一些不那么紧急的任务,可以延后在将来的某个时间点来执行,linux通过中断来响应处理一些任务,这类任务通常是很紧急的需要立即处理,此类任务通常运行于interrupt context(中断上下文),存在较多限制。

  • tasklet实现原理

tasklet实现基于软中断(softirq),而软中断基于硬件中断,它们的优先级逐次递增。

 简单描述下处理流程,当前硬件中断时,内核根据irq号进而执行该IRQ的处理例程(ISR),irq处理例程结束后处理软中断,tasklet软中断例程遍历所有注册的tasklet并依次执行它们。

执行中断例程(非ISR)
run_irq_on_irqstack_cond
中断例程执行完成后,在该函数中触发软中断
irq_exit_rcu();

irq_exit_rcu只是一个前端,所有的工作由__irq_exit_rcu实现

static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
	local_irq_disable();
#else
	lockdep_assert_irqs_disabled();
#endif
	account_hardirq_exit(current);
	preempt_count_sub(HARDIRQ_OFFSET);
	if (!in_interrupt() && local_softirq_pending())
		invoke_softirq(); //触发软中断执行

	tick_irq_exit();
}

该函数负责执行软中断例程(tasklet例程),该函数比较长,我这里摘抄核心部分说明,其余的是内核统计、中断禁用等与核心流程关系不大。

__do_softirq
    //softirq_vec中存放了所有已注册的软中断例程
    h = softirq_vec;
    //循环处理所有待处理软中断
	while ((softirq_bit = ffs(pending))) {
		unsigned int vec_nr; //中断号
		int prev_count;

		h += softirq_bit - 1;

		vec_nr = h - softirq_vec;
		prev_count = preempt_count();

		kstat_incr_softirqs_this_cpu(vec_nr);

		trace_softirq_entry(vec_nr);

        //这里执行软中断例程
		h->action(h);

		trace_softirq_exit(vec_nr);
		if (unlikely(prev_count != preempt_count())) {
			pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
			       vec_nr, softirq_to_name[vec_nr], h->action,
			       prev_count, preempt_count());
			preempt_count_set(prev_count);
		}
		h++;
		pending >>= softirq_bit;
	}

该函数循环处理所有软中断例程,其中实现tasklet的中断例程会执行,tasklet例程会执行所有已注册的tasklet任务。

  • tasklet数据结构
/**
tasklet任务结构,表示一个tasklet。
*/
struct tasklet_struct
{
	struct tasklet_struct *next; //多个tasklet通过next成员连接起来
	unsigned long state; //任务状态
	atomic_t count;
	bool use_callback; //为1时调用callback函数
	union {
        /*
        由于是union 结构所有func和callback只能设置一个
        */
		void (*func)(unsigned long data); //任务例程
		void (*callback)(struct tasklet_struct *t); //任务例程
	};
	unsigned long data; //需要传给任务函数的参数
};

如下图,通过宏DECLARE_TASKLET创建tasklet任务结构后,调用tasklet_schedule将该tasklet添加到当前CPU的tasklet_head结构,然后触发tasklet软中断,执行tasklet_action软中断处理例程,该中断例程除了启用中断、获取当前CPU的tasklet_head结构,核心处理逻辑是从tasklet_heap中取出任务并执行任务函数(tasklet_struct->func)。

 内核提供的tasklet处理接口


//创建tasklet_struck实例
#define DECLARE_TASKLET(name, _callback)		\
struct tasklet_struct name = {				\
	.count = ATOMIC_INIT(0),			\
	.callback = _callback,				\
	.use_callback = true,				\
}

//将该 t 添加到当前CPU的tasklet_head中并调度tasklet
static inline void tasklet_schedule(struct tasklet_struct *t)

//同tasklet_schedule一致,不过它执行高优先级的软中断例程
static inline void tasklet_hi_schedule(struct tasklet_struct *t)

//将 t 从tasklet_heap中移除
void tasklet_kill(struct tasklet_struct *t)

//这两个函数做的事情是一致的,都是将tasklet任务函数设置到tasklet_struct结构,出于兼容性考虑,内核都保留了下来。
void tasklet_setup(struct tasklet_struct *t,void (*callback)(struct tasklet_struct *))
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

实战编写tasklet案例,这里的tasklet任务函数只是打印一个消息到内核,实际驱动开发中,我们可以用tasklet完成很多简单、可快速完成的任务,这类任务用kthread太重了,tasklet则很合适。

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/interrupt.h>


//tasklet处理函数
static void my_tasklet_fn(unsigned long data)
{
    printk("execute my_tasklet_fn data %ld\n",data);
}


static struct tasklet_struct _my_tasklet;


static int __init tasklet_example_init(void)
{
    int retval=0;
    
    //初始化tasklet
    tasklet_init(&_my_tasklet,&my_tasklet_fn,123);

    //将tasklet添加到待执行列表,并调度tasklet软中断
    tasklet_schedule(&_my_tasklet);

    printk("tasklet_example_init...");
    return retval;
}

static void __exit tasklet_example_exit(void)
{
    printk("tasklet_example_exit...");
}


module_init(tasklet_example_init);
module_exit(tasklet_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangQiang");

make -j 6 编译内核模块 

通过insmod tasklet_example.ko 加载内核模块到内核,可以看到成功执行了tasklet函数。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值