工作延迟机制
延迟时将所有要做的工作安排在将来执行的一种方法。
类型 | 使用场景 |
---|---|
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");