Linux内核实践之工作队列

原创 2012年03月30日 11:25:53

       工作队列(work queue)是另外一种将工作推后执行的形式,它和tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

 

       那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet

 

1.     工作、工作队列和工作者线程

        如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。

 

2.表示工作的数据结构

工作用<linux/workqueue.h>中定义的work_struct结构表示:

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

};

      这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

 

3.创建推后的工作

       要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:

DECLARE_WORK(name, void (*func) (void*), void *data);

       这样就会静态地创建一个名为name,待执行函数为func,参数为datawork_struct结构。

同样,也可以在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);

这会动态地初始化一个由work指向的工作。

 

4. 工作队列中待执行的函数

       工作队列待执行的函数原型是:

void work_handler(void*data)

        这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

 

5. 对工作进行调度

        现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用

schedule_work(&work)

        work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:

schedule_delayed_work(&work,delay);

这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。

上面内容部分摘自:http://blog.csdn.net/zyhorse2010/article/details/6455026

 

6. 工作队列的简单应用

       在Workqueue机制中,提供了一个系统默认的workqueue队列——keventd_wq,这个队列是Linux系统在初始化的时候就创建的。用户可以直接初始化一个work_struct对象,然后在该队列中进行调度,使用更加方便。

 

        当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueueworkqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。

 

       在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等待队列上睡眠,直到有人唤醒daemon去处理任务队列。

Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。

上面内容摘自:http://hi.baidu.com/%CD%EA%C3%C0%CB%C4%C4%EA/blog/item/412b8833ca91b2e61b4cff5b.html

 

7.补充与实验

      对于内核现成的队列,我们初始化完work后直接用queue_schedule加入系统默认的workqueue队列——keventd_wq并调度执行,对于我们从新创建的工作队列,需要用create_queue来创建work_queue,然后初始化work,最后,还需要使用queue_work加入我们创建的工作队列并调度执行。

 

下面是一个两种方案的使用例子:

#include <linux/init.h>

#include <linux/kernel.h>

#include <linux/module.h>

MODULE_AUTHOR("Mike Feng");

/*测试数据结构*/

struct my_data

{

         structwork_struct my_work;

         intvalue; 

};

struct workqueue_struct *wq=NULL;

struct work_struct work_queue;

/*初始化我们的测试数据*/

struct my_data* init_data(structmy_data *md)

{

         md=(structmy_data*)kmalloc(sizeof(struct my_data),GFP_KERNEL);

         md->value=1;

         md->my_work=work_queue;

         returnmd;

}

/*工作队列函数*/

static void work_func(struct work_struct *work)

{

         structmy_data *md=container_of(work,structmy_data,my_work);

         printk("<2>""Thevalue of my data is:%d\n",md->value);

}

static __init intwork_init(void)

{

         structmy_data *md=NULL;

         structmy_data *md2=NULL;

         md2=init_data(md2);

         md=init_data(md);     

         md2->value=20;

         md->value=10;

         /*第一种方式:使用统默认的workqueue队列——keventd_wq,直接调度*/

         INIT_WORK(&md->my_work,work_func);

         schedule_work(&md->my_work);

 

         /*第二种方式:创建自己的工作队列,加入工作到工作队列(加入内核就对其调度执行)*/

         wq=create_workqueue("test");

         INIT_WORK(&md2->my_work,work_func);

         queue_work(wq,&md2->my_work);    

         return0;

}

static void work_exit(void)

{

         /*工作队列销毁*/

         destroy_workqueue(wq);

}

module_init(work_init);

module_exit(work_exit);

 

实验结果:



Linux内核:工作队列

在我的上一篇文章Linux内核:中断、软中断、tasklet中,我们已经了解了中断底半部的两种实现方式,即软中断和tasklet微线程。但是这两种方式归根结底都是采用软中断机制的,其根本上还是在中断的...
  • JansonZhe
  • JansonZhe
  • 2015年10月02日 10:26
  • 1718

linux kernel 之 工作队列

1. 什么是workqueue Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得...
  • zhangzhi123456789
  • zhangzhi123456789
  • 2015年07月21日 17:02
  • 840

linux内核工作队列

Workqueue 是内核里面很重要的一个机制,特别是内核驱动,一般的小型任务 (work) 都不会自己起一个线程来处理,而是扔到 Workqueue 中处理。Workqueue 的主要工作就是用进程...
  • AXW2013
  • AXW2013
  • 2017年03月28日 10:37
  • 139

Linux内核中的工作队列

把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,往往用于中断函数里需要处理的任务,因为太繁琐的任务不易直接放在中断函数里直接执行,这个时候就需要使用工作队列执行任务: ...
  • wuyb2011
  • wuyb2011
  • 2017年11月03日 15:15
  • 57

linux 工作队列(workqueue)

在处理内核相关工作中, 我们经常看到工作队列(workqueue)的身影. 本文描述何为 linux workqueue.本文基于 2.6.32 的内核, 此时的工作队列还不是 cmwq. 为什...
  • liuxd3000
  • liuxd3000
  • 2012年06月28日 17:45
  • 2407

linux驱动之定时任务timer,队列queue,小任务tasklet机制及用法

1:内核定时器: 在内核中有系统自带的定时器,达到一定的时间就可以进行一个操作,这个和单片机中的定时器中断一样。 包含头文件是 Timer.h (\include\linux)结构体是timer_li...
  • u013256018
  • u013256018
  • 2015年08月27日 20:40
  • 1436

Linux内核工作队列之任务执行

在前面介绍了工作队列,工作队列上面任务,以及工作队列创建的内核线程,现在讨论线程上面处理的 每一个任务,即函数worker_thread()。 [] process_one_work [] wo...
  • sunlei0625
  • sunlei0625
  • 2017年03月08日 20:56
  • 289

Linux下多任务间通信和同步-消息队列

Linux下进程间通信和同步-消息队列 消息队列就是一些消息的列表.用户可以在消息队列中添加消息和读取消息等.从这点上看,消息队列具有一定的FIFO特性,但是它可以实现消息的随机查询,比FIFO具有更...
  • ce123
  • ce123
  • 2013年10月10日 07:12
  • 5954

linux内核分析--内核中的数据结构之队列(二)

内核中的队列是以字节形式保存数据的,所以获取数据的时候,需要知道数据的大小。 如果从队列中取得数据时指定的大小不对的话,取得数据会不完整或过大。 内核中关于队列定义的头文件位于: include/li...
  • yusiguyuan
  • yusiguyuan
  • 2014年02月25日 11:20
  • 2371

Linux内核队列——kfifo

内核kfifo
  • u011012049
  • u011012049
  • 2016年09月04日 22:50
  • 457
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核实践之工作队列
举报原因:
原因补充:

(最多只允许输入30个字)