关闭

linux设备驱动下的工作队列(workqueue)

标签: 工作linuxstructdelaylistinsert
2280人阅读 评论(0) 收藏 举报
分类:

       从表面和使用来看,工作队列类似与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的内容很明了:遍历链表,执行已提交的工作队列的注册回调函数。

 

从形式上看,workqueuetasklet很类似,在中断顶半部和中断底半部中的使用中,他们的使用方法也是类似的。

 

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:469898次
    • 积分:7396
    • 等级:
    • 排名:第3067名
    • 原创:107篇
    • 转载:0篇
    • 译文:1篇
    • 评论:98条
    博客专栏
    最新评论