linux 内核工作延迟机制-softirq-tasklet

工作延迟机制

延迟时将所有要做的工作安排在将来执行的一种方法。

类型使用场景
SoftIRQ执行在原子上下文
Tasklet执行在原子上下文
工作队列执行在进程上下文

Softirq 和 Ksoftirqd

软中断用于快速处理,在使用软中断时禁用内核的调度器,处于中断上下文中。softirq在实际场景中很少使用,只有网络和块设备子系统使用softirq,一般在硬件中断中被调度,中断发生很快,快过对他们的处理速度,内核会对他们进行排队以便稍后处理,ksoftirqd负责后期执行(进程上下文),ksoftirqd是单CPU内核线程。

使用top命令可以看到系统中有多个ksoftirqd内核线程,ksoftirqd/n,其中的n代表的是cpu的编号,每一个cpu有一个ksoftirqd线程。
在这里插入图片描述
特别注意:如果CPU资源被ksoftirqd大量消耗,说明此时系统产生了大量的中断。

Tasklet

Tasklet是软中断的一个实例,在需要软中断的情况下,一般使用tasklet就可以满足需求。tasklet本质上是不可以再入的,如果代码执行期间随处可中断,并且之后能够被再次安全的调用,就称其为可再入。tasklet只能运行在一个cpu上,也就是调度它的cpu,不同的tasklet只能在不同的cpu上运行。

Tasklet

/* Tasklets --- multithreaded analogue of BHs.

   This API is deprecated. Please consider using threaded IRQs instead:
   https://lore.kernel.org/lkml/20200716081538.2sivhkj4hcyrusem@linutronix.de

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
 */

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	bool use_callback;
	union {
		void (*func)(unsigned long data);
		void (*callback)(struct tasklet_struct *t);
	};
	unsigned long data;
};

tasklet声明
  • 动态声明
void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data);
  • 静态声明
#define DECLARE_TASKLET(name, _callback)		\
struct tasklet_struct name = {				\
	.count = ATOMIC_INIT(0),			\
	.callback = _callback,				\
	.use_callback = true,				\
}

#define DECLARE_TASKLET_DISABLED(name, _callback)	\
struct tasklet_struct name = {				\
	.count = ATOMIC_INIT(1),			\
	.callback = _callback,				\
	.use_callback = true,				\
}

DECLARE_TASKLET创建tasklet已经启用,并准备好在没有任何其他函数调用的情况下被调度,通过count字段设置为0来实现。
DECLARE_TASKLET_DISABLED创建的tasklet被禁用,通过将count的值设置为1来实现,需要在调用tasklet_enable之后,才可以调度这个tasklet。

  • 启用Tasklet
static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic();
	atomic_dec(&t->count);
}
  • 禁用tasklet
// 同步
static inline void tasklet_disable(struct tasklet_struct *t)
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}

//异步,执行函数后立即返回
static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	atomic_inc(&t->count);
	smp_mb__after_atomic();
}

tasklet调度

以下两个为tasklet调度函数,具体取决于tasklet是具有正常优先级还是更高优先级。

void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);

内核将普通优先级和高优先级的Tasklet维护在两个不同的链表中

  • tasklet_schedule
    将tasklet添加到普通优先级链表中,用TASKLET_SOFTIRQ标志调度相关的softirq。

  • tasklet_hi_schedule
    将tasklet添加到高优先级链表中,并用HI_SOFTIRQ标志调度相关softirq,高优先级旨在用于具有低延时要求的软件中断处理程序。

  • 在已经被调度但尚未开始执行的tasklet上调用task_schedule将不会执行任何操作,该tasklet最终也仅执行一次。

  • 可以在tasklet中调用tasklet_schedule,意味着tasklet可以重新调度自己。

  • 高优先级tasklet总是在正常优先级的tasklet之前执行,滥用高优先级任务会增加系统延迟,一定要在真正需要快速执行时再使用。

  • tasklet_kill

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))
		wait_var_event(&t->state, !test_bit(TASKLET_STATE_SCHED, &t->state));

	tasklet_unlock_wait(t);
	tasklet_clear_sched(t);
}
EXPORT_SYMBOL(tasklet_kill);

调用task_kill可以停止一个tasklet,这个函数的主要作用时防止tasklet再次运行或者该tasklet当前计划运行时,会等待其执行完成后再杀掉它。

  • 使用实例
// tasklet.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.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);
}

DEFINE_TASKLET(test_tasklet, tasklet_function, (unsgined long)tasklet_data);

static int __init test_init(void)
{
	tasklet_schedule(&test_tasklet);
	return 0;
}

void test_exit(void)
{
        printk("Waitqueue example cleanup!\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值