目录
tasklet和workqueue都属于内核的延期工作(deferred work),也属于中断的范畴,处在中断的bottom half(前面文章《Linux中断——request_irq》介绍过top half)。
tasklet拥有的一些特征:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
DECLARE_TASKLET(name, func, data)
DECLARE_TASKLET_DISABLED(name, func, data)
name 一般为声明的数据结构;func为任务函数,data为传给func的数据。
以上两者差别:
DECLARE_TASKLET(name,func, data) 初始化的引用计数器count=0,小任务处于激活状态。
DECLARE_TASKLET_DISABLED(name,func, data) 初始化的引用计数器count=1,小任务处于禁止状态
static inline void tasklet_schedule(struct tasklet_struct *t);
linux/include/linux/interrupt.h
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
linux/kernel/softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();
}
static inline void tasklet_hi_schedule(struct tasklet_struct *t);
void __tasklet_hi_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__get_cpu_var(tasklet_hi_vec).tail = t;
__get_cpu_var(tasklet_hi_vec).tail = &(t->next);
raise_softirq_irqoff(HI_SOFTIRQ);
local_irq_restore(flags);
}
从代码的实现中可以看到tasklet_hi_schedule和tasklet_schedule唯一差别是在调用raise_softirq_irqoff中指定的参数不一样,tasklet_schedule指定的是TASK_SOFTIRQ,而tasklet_hi_schedule指定的是HI_SOFTIRQ。表现出来的效果是它优先级更高,只有那种对latency要求小的情况才用tasklet_hi_schedule,正常情况都使用普通tasklet_schedule。可以通过/proc/jitasklethi看状态。
static inline void tasklet_disable_nosync(struct tasklet_struct *t);
static inline void tasklet_disable(struct tasklet_struct *t);
static inline void tasklet_enable(struct tasklet_struct *t);
static inline void tasklet_hi_enable(struct tasklet_struct *t);
当没有使用disable就无需执行enable,调用tasklet_schedule之后直接执行。
tasklet_disable_nosync和tasklet_disable的差别在于:tasklet_disable_nosync不等待当前正在执行的tasklet而直接disable掉,但是它无法disable运行在其他CPU上的tasklet;被tasklet_disable作用的tasklet如果是正在运行,它会忙等待它退出,但是退出之后被tasklet_schedule进来的tasklet依然会在enable之后作用,而不是丢弃。
tasklet_enable和tasklet_hi_enable仅仅在优先级上有差异,如果是一个任务被tasklet_schedule了,但是当前处于disable状态,如果被enable之后会第一时间自动执行而无需再次执行tasklet_schedule。
void tasklet_kill(struct tasklet_struct *t)
它一般在设备关闭或者驱动卸载的时候被执行,如果当前task正在执行,它会等待执行完后销毁。如果tasklet有调用它本身,那就要注意在执行tasklet_kill时必须先阻止它调用自己。
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include<linux/interrupt.h>
static struct t asklet_struct my_tasklet;
static void tasklet_handler (unsigned longd ata)
{
printk(KERN_ALERT,"tasklet_handler is running./n");
}
staticint __init test_init(void)
{
tasklet_init(&my_tasklet,tasklet_handler,0);
tasklet_schedule(&my_tasklet);
return 0;
}
static void __exit test_exit(void)
{
tasklet_kill(&tasklet);
printk(KERN_ALERT,"test_exit is running./n");
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
workqueue拥有的一些特征:
struct work_struct {
atomic_long_t data;
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name;
int singlethread;
int freezeable; /* Freeze threads during suspend */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
workqueue——work——handlefunc
注意这三者的关系,workqueue是一个类,work是一个成员,handlefunc是具体的事件
将work与对应的handlefunc建立联系
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data)
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data)
DECLARE_WORK(name, void, (*function)(void *), void *data)
static struct workqueue_struct *create_workqueue(const char *name);
static struct workqueue_struct *create_freezeable_workqueue(const char *name);
static struct workqueue_struct *create_singlethread_workqueue(const char *name);
将workqueue与work建立联系
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);
以上函数实现将对应的work加入到指定的workqueue中。如果返回0,表示成功加入队列中;返回非零,表示相同的work已经在队列中。
但是有两个注意点:
针对多核CPU的调度
int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
int cancel_delayed_work_sync(struct delayed_work *dwork);
int cancel_work_sync(struct work_struct *work);
返回0,表示它正在执行;返回非0,表示已经被取消;
注意点:
void flush_workqueue(struct workqueue_struct *wq);
如果在执行cancel之后返回0,如过要确认执行的工作队列完毕后再进行下一步操作该怎么办?通过调用flush操作会等待当前工作队列完成后返回,但是如果有新的工作队列被执行,将不受它监控。
void destroy_workqueue(struct workqueue_struct *wq);
安全结束一个workqueue,但是所有被阻塞的work会被先执行完。注意是workqueue不是work
#include<linux/module.h>
#include<linux/init.h>
#include<linux/workqueue.h>
staticstruct workqueue_struct *queue =NULL;
staticstruct work_struct work;
staticvoid work_handler(struct work_struct*data)
{
printk(KERN_ALERT"work handler function./n");
}
staticint __init test_init(void)
{
queue= create_singlethread_workqueue("helloworld"); /*创建一个单线程的工作队列*/
if(!queue)
goto err;
INIT_WORK(&work, work_handler);
schedule_work(&work);/*schedule_work是添加到系统的events workqueue, 要添加到自己的 workqueue, 应该使用queue_work, 故此处有误*/
return 0;
err:
return-1;
}
staticvoid __exit test_exit(void)
{
destroy_workqueue(queue);
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
另外还有一种shared workqueue,它使用比较少,会对具体workfunc有一些限制,比如不建议使用sleep,因为当它sleep,其它共享的将无法使用。另外接口函数缺少struct workqueue_struct *wq成员。由于使用比较少,这里就不介绍。
tasklet与workqueue非常相似,都属于内核的一种延期工作操作,但是两者还是有一些差异的
tasklet | Workqueue |
处于atomic context,不能sleep | 不处于atomic context,可以sleep |
处于中断上下文,OS不可以进行进程调度 | 处于进程上下文,OS可以进行进程调度 |
运行调度它们的同一个CPU上 | 默认同一个CPU上 |
不能指定确定时间进行调度 | 不能指定确定时间进行调度或者指定至少延时一个确定时间后调度 |
只能交给ksoftirqd/0 | 可以提交给events/0,也可以提交给自定义的workqueue |
Tasklet函数带参数 | Work函数不带参数 |
(1) 必须立即进行紧急处理的极少量任务放入在中断的top half(request_irq)中,此时屏蔽了与自己同类型的中断,由于任务量少,所以可以迅速不受打扰地处理完紧急任务。
(2) 需要较少时间的中等数量的急迫任务放在tasklet中。此时不会屏蔽任何中断(包括与自己的顶半部同类型的中断),所以不影响顶半部对紧急事务的处理;同时又不会进行用户进程调度,从而保证了自己急迫任务得以迅速完成。
(3) 需要较多时间且并不急迫(允许被操作系统剥夺运行权)的大量任务放在workqueue中。此时操作系统会尽量快速处理完这个任务,但如果任务量太大,期间操作系统也会有机会调度别的用户进程运行,从而保证不会因为这个任务需要运行时间将其它用户进程无法进行。
(4) 可能引起睡眠的任务放在workqueue中。因为在workqueue中睡眠是安全的。
atomic是原子的意思,意味"不可分割"的整体。在Linux kernel中有一类atomic操作API。这些操作对用户而言是原子执行的,在一个CPU上执行过程中,不会被其他CPU打断。最常见的操作是原子读改写,简称RMW。简单的例子是,一个普通的变量递增操作,在多核同时参数的情况下,会导致与预期结果不一致。atomic实现原理 介绍了三种实现方式Bus Lock、Cacheline Lock、LL/SC