从表面和使用来看,工作队列类似与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很类似,在中断顶半部和中断底半部中的使用中,他们的使用方法也是类似的。