从表面和使用来看,工作队列类似与tasklet,它们都允许内核代码请求某个函数在将来的时间被调用。
但实际上它们有一些非常重要的区别:
1、 tasklet在软件中断上下文中运行,因此所有的tasklet代码都必须是原子的。而工作队列函数在一个特殊内核进程的上下文总运行,所以具有更好的灵活性。更主要的是工作队列函数可以休眠。
2、 tasklet始终运行在被初始提交的同一处理器上,而工作队列默认是这样。
3、 内核代码可以请求工作队列函数的执行延迟给定的时间间隔。有一种提交工作队列的函数接口是:
int queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
queue_delayed_work - queue work on a workqueue after delay
二者的关键区别在于:tasklet会在很短的时间段内很快执行,并且以原子模式执行,而工作队列函数可具有更长的延迟且不必原子化。
工作队列的初始化:
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#endif
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
工作队列也是用内核中的list_head双向链表;
工作队列的提交:
值得注意的是:在一般情况下,我们都会用schedule_work函数来提交工作队列,这里
int schedule_work(struct work_struct *work)
{
return queue_work(keventd_wq, work);
}
该函数是将work_struct加入一个全局的keventd_wq队列中,也就是内核提供的共享的默认工作队列。
如果我们有特殊的延时需求,那我们只有建立自己的专用工作队列。
使用create_workqueue系列宏定义。
然后用下面的函数提交工作队列:
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)
其中queue_delayed_work就用到了linux的动态定时器,所以才有delay的效果。
我们主要看queue_work函数:
int schedule_work(struct work_struct *work)
{
return queue_work(keventd_wq, work);
}
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
int ret;
ret = queue_work_on(get_cpu(), wq, work);
put_cpu();
return ret;
}
int
queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
{
int ret = 0;
if (!test_and_set_bit(WORK_STRUCT_PENDING, work_data_bits(work))) {
BUG_ON(!list_empty(&work->entry));
__queue_work(wq_per_cpu(wq, cpu), work);
ret = 1;
}
return ret;
}
static void __queue_work(struct cpu_workqueue_struct *cwq,
struct work_struct *work)
{
unsigned long flags;
debug_work_activate(work);
spin_lock_irqsave(&cwq->lock, flags);
insert_work(cwq, work, &cwq->worklist);
spin_unlock_irqrestore(&cwq->lock, flags);
}
static void insert_work(struct cpu_workqueue_struct *cwq,
struct work_struct *work, struct list_head *head)
{
trace_workqueue_insertion(cwq->thread, work);
set_wq_data(work, cwq);
/*
* Ensure that we get the right work->data if we see the
* result of list_add() below, see try_to_grab_pending().
*/
smp_wmb();
list_add_tail(&work->entry, head);
wake_up(&cwq->more_work);
}
最终还是list_add_tail(&work->entry, head); 双向链表随处可见
工作队列的执行:
与tasklet是利用软中断不同,工作队列的执行是在一个特殊的内核进程中运行的。
worker_threadà run_workqueue
worker_thread是怎么来的,暂时不过问了,就当他是一个特殊的内核进程。
而run_workqueue的内容很明了:遍历链表,执行已提交的工作队列的注册回调函数。
从形式上看,workqueue和tasklet很类似,在中断顶半部和中断底半部中的使用中,他们的使用方法也是类似的。