原文网址:http://blog.chinaunix.net/uid-20583479-id-1920134.html

工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以把耗时的工作放到工作队列。说白了就是系统延时调度的一个自定义函数。

工作队列是实现延迟的新机制,从 2.5 版本 Linux 内核开始提供该功能。不同于微线程一步到位的延迟方法,工作队列采用通用的延迟机制, 工作队列的处理程序函数能够休眠(这在微线程模式下无法实现)。 工作队列可以有比微线程更高的时延,并为任务延迟提供功能更丰富的 API。 从前,延迟功能通过 keventd 对任务排队来实现, 但是现在由内核工作线程 events/X 来管理。

工作队列提供一个通用的办法将任务延迟到 bottom halves。 处于核心的是工作队列(结构体 workqueue_struct), 任务被安排到该结构体当中。 任务由结构体 work_struct 来说明, 用来鉴别哪些任务被延迟以及使用哪个延迟函数(参见 图 3)。 events/X 内核线程(每 CPU 一个)从工作队列中抽取任务并激活一个 bottom-half 处理程序(由处理程序函数在结构体 work_struct 中指定)。

图 3. 工作队列背后的处理过程

由于 work_struct 中指出了要采用的处理程序函数, 因此可以利用工作队列来为不同的处理程序进行任务排队。 现在,让我们看一下能够用于工作队列的 API 函数。

工作队列 API

工作队列 API 比微线程稍复杂,主要是因为它支持很多选项。 我们首先探讨一下工作队列,然后再看一下任务和变体。

通过 图 3 可以回想工作队列的核心结构体是队列本身。 该结构体用于将任务安排出 top half ,进入 bottom half ,从而延迟它的执行。 工作队列通过宏调用生成 create_workqueue,返回一个 workqueue_struct 参考值。当用完一个工作队列,可以通过调用函数 destroy_workqueue 来去掉它(如果需要):

struct workqueue_struct *create_workqueue( name );

struct workqueue_struct *create_singlethread_workqueue(const char *name);

void destroy_workqueue( struct workqueue_struct * );

一个工作队列必须明确的在使用前创建,若使用 create_workqueue, 就得到一个工作队列它在系统的每个处理器上有一个专用的线程。在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用create_singlethread_workqueue 来创建工作队列。

通过工作队列与之通信的任务可以由结构体 work_struct 来定义。 通常,该结构体是用来进行任务定义的结构体的第一个元素(后面有相关例子)。 工作队列 API 提供三个函数来初始化任务(通过一个事先分配的缓存); 参见 清单 6。 宏 INIT_WORK 提供必需的初始化数据以及处理程序函数的配置(由用户传递进来)。 如果开发人员需要在任务被排入工作队列之前发生延迟,可以使用宏 INIT_DELAYED_WORK 和INIT_DELAYED_WORK_DEFERRABLE。

清单 6. 任务初始化宏

INIT_WORK( work, func );

INIT_DELAYED_WORK( work, func );

INIT_DELAYED_WORK_DEFERRABLE( work, func );

INIT_* 做更加全面的初始化结构的工作,在第一次建立结构时使用。

任务结构体的初始化完成后,接下来要将任务安排进工作队列。 可采用多种方法来完成这一操作(参见 清单 7)。 首先,利用 queue_work 简单地将任务安排进工作队列(这将任务绑定到当前的 CPU)。 或者,可以通过queue_work_on 来指定处理程序在哪个 CPU 上运行。 两个附加的函数为延迟任务提供相同的功能(其结构体装入结构体 work_struct 之中,并有一个 计时器用于任务延迟 )。

清单 7. 工作队列函数

int queue_work( struct workqueue_struct *wq, struct work_struct *work );

int queue_work_on( int cpu, struct workqueue_struct *wq, struct work_struct *work );

int queue_delayed_work( struct workqueue_struct *wq,

struct delayed_work *dwork, unsigned long delay );

int queue_delayed_work_on( int cpu, struct workqueue_struct *wq,

struct delayed_work *dwork, unsigned long delay );

每个都添加work到给定的workqueue。如果使用 queue_delay_work, 则实际的工作至少要经过指定的 jiffies 才会被执行。 这些函数若返回 1 则工作被成功加入到队列; 若为0,则意味着这个 work 已经在队列中等待,不能再次加入。

可以使用全局的内核全局工作队列,利用 4 个函数来为工作队列定位。 这些函数(见 清单 8)模拟 清单 7,只是不需要定义工作队列结构体。

清单 8. 内核全局工作队列函数

int schedule_work( struct work_struct *work );

int schedule_work_on( int cpu, struct work_struct *work );

int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay );

int scheduled_delayed_work_on(

int cpu, struct delayed_work *dwork, unsigned long delay );

还有一些帮助函数用于清理或取消工作队列中的任务。想清理特定的任务项目并阻塞任务, 直到任务完成为止, 可以调用 flush_work 来实现。 指定工作队列中的所有任务能够通过调用 flush_workqueue 来完成。 这两种情形下,调用者阻塞直到操作完成为止。 为了清理内核全局工作队列,可调用 flush_scheduled_work。

int flush_work( struct work_struct *work );

int flush_workqueue( struct workqueue_struct *wq );

void flush_scheduled_work( void );

在 flush_workqueue 返回后, 没有在这个调用前提交的函数在系统中任何地方运行。

还没有在处理程序当中执行的任务可以被取消。 调用 cancel_work_sync 将会终止队列中的任务或者阻塞任务直到回调结束(如果处理程序已经在处理该任务)。 如果任务被延迟,可以调用 cancel_delayed_work_sync。

int cancel_work_sync( struct work_struct *work );

int cancel_delayed_work_sync( struct delayed_work *dwork );

最后,可以通过调用 work_pending 或者 delayed_work_pending 来确定任务项目是否在进行中。

work_pending( work );

delayed_work_pending( work );

这就是工作队列 API 的核心。在 ./kernel/workqueue.c 中能够找到工作队列 API 的实现方法, API 在 ./include/linux/workqueue.h 中定义。 下面我们看一个工作队列 API 的简单例子。

工作队列简单例子

下面的例子说明了几个核心的工作队列 API 函数。 如同微线程的例子一样,为方便起见,可将这个例子部署在内核模块上下文。

首先,看一下将用于实现 bottom half 的任务结构体和处理程序函数(参见 清单 9)。 首先您将注意到工作队列结构体参考的定义 (my_wq)以及 my_work_t 的定义。 my_work_t 类型定义的头部包括结构体 work_struct 和一个代表任务项目的整数。 处理程序(回调函数)将 work_struct 指针引用改为 my_work_t 类型。 发送出任务项目(来自结构体的整数)之后,任务指针将被释放。

清单 9. 任务结构体和 bottom-half 处理程序

#include

#include

#include

MODULE_LICENSE("GPL");

static struct workqueue_struct *my_wq;

typedef struct {

struct work_struct my_work;

int    x;

} my_work_t;

my_work_t *work, *work2;

static void my_wq_function( struct work_struct *work)

{

my_work_t *my_work = (my_work_t *)work;

printk( "my_work.x %d\n", my_work->x );

kfree( (void *)work );

return;

}

清单 10 是 init_module 函数, 该函数从使用 create_workqueue API 函数生成工作队列开始。 成功生成工作队列之后,创建两个任务项目(通过 kmalloc 来分配)。 利用 INIT_WORK 来初始化每个任务项目,任务定义完成,接着通过调用 queue_work 将任务安排到工作队列中。 top-half 进程(在此处模拟)完成。如同清单 10 中所示,任务有时会晚些被处理程序处理。

清单 10. 工作队列和任务创建

int init_module( void )

{

int ret;

my_wq = create_workqueue("my_queue");

if (my_wq) {

/* Queue some work (item 1) */

work = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);

if (work) {

INIT_WORK( (struct work_struct *)work, my_wq_function );

work->x = 1;

ret = queue_work( my_wq, (struct work_struct *)work );

}

/* Queue some additional work (item 2) */

work2 = (my_work_t *)kmalloc(sizeof(my_work_t), GFP_KERNEL);

if (work2) {

INIT_WORK( (struct work_struct *)work2, my_wq_function );

work2->x = 2;

ret = queue_work( my_wq, (struct work_struct *)work2 );

}

}

return 0;

}

最终的元素在 清单 11 中展示。 在模块清理过程中,会清理一些特别的工作队列(它们将保持阻塞状态直到处理程序完成对任务的处理), 然后销毁工作队列。

清单 11. 工作队列清理和销毁

void cleanup_module( void )

{

flush_workqueue( my_wq );

destroy_workqueue( my_wq );

return;

}

参考:

内核的工作队列使用方法

工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以把耗时的工作放到工作队列。说白了就是系统延时调度的一个自定义函数。

1、定义struct work_struct irq_queue;

2、初始化INIT_WORK(&irq_queue,do_irq_queuework);

3、调用方法:schedule_work(&rq_queue);

注,调用完毕后系统会释放此函数,所以如果想再次执行的话,就再次调用schedule_work()即可。

另外,内核必须挂载文件系统才可以使用工作队列。我的理解是:工作队列也属于调度,如果内核挂了,他就不调度了,当然就不能用工作队列了。

工作队列接口

  工作队列接口是在2.5的开发过程中引入的,用于取代任务队列接口(用于调度内核任务)。每个工作队列有一个专门的线程,

所有来自运行队列的任务在进程的上下文中运行(这样它们可以休眠)。驱动程序可以创建并使用它们自己的工作队列,或者使用内核的一个工作队列。

工作队列用以下方式创建:

  struct workqueue_struct *create_workqueue(const char *name); 在这里 name 是工作队列的名字。

  工作队列任务可以在编译时或者运行时创建。任务需要封装为一个叫做 work_struct 的结构体。在编译期初始化一个工作队列任务时要用到:

  DECLARE_WORK(name, void (*function)(void *), void *data); 在这里 name 是 work_struct 的名字,function 是当任务被调度时调用的函数,data 是指向那个函数的指针。

  在运行期初始化一个工作队列时要用到:

  INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

用下面的函数调用来把一个作业(一个类型为work_struct 结构的工作队列作业/任务)加入到工作队列中:

  int queue_work(struct workqueue_struct *queue, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);

  在queue_delay_work()中指定delay,是为了保证至少在经过一段给定的最小延迟时间以后,工作队列中的任务才可以真正执行。

  工作队列中的任务由相关的工作线程执行,可能是在一个无法预期的时间(取决于负载,中断等等),或者是在一段延迟以后。任何一个在工作队列中等待了无限长的时间也没有运行的任务可以用下面的方法取消:

  int cancel_delayed_work(struct work_struct *work);

如果当一个取消操作的调用返回时,任务正在执行中,那么这个任务将继续执行下去,但不会再加入到队列中。清空工作队列中的所有任务使用:

  void flush_workqueue(struct workqueue_struct *queue);

销毁工作队列使用:

  void destroy_workqueue(struct workqueue_struct *queue);

不是所有的驱动程序都必须有自己的工作队列。驱动程序可以使用内核提供的缺省工作队列。由于这个工作队列由很多驱动程序共享,

任务可能会需要比较长一段时间才能开始执行。为了解决这一问题,工作函数中的延迟应该保持最小或者干脆不要。

  需要特别注意的是缺省队列对所有驱动程序来说都是可用的,但是只有经过GP许可的驱动程序可以用自定义的工作队列:

  int schedule_work(struct work_struct *work); -- 向工作队列中添加一个任务

int schedule_delayed_work(struct work_struct *work, unsigned long delay); -- 向工作队列中添加一个任务并延迟执行

  当模块被缷载时应该去调用一个 flash_scheduled_work() 函数,这个函数会使等待队列中所有的任务都被执行。

【转】 Linux中的工作队列的更多相关文章

  1. 聊一聊Linux中的工作队列2

    上一篇文章对工作队列原理以及核心数据结构做了简单介绍,本文重点介绍下workqueue的创建以及worker的管理. 一.工作队列的创建(__alloc_workqueue_key) struct w ...

  2. 聊一聊Linux中的工作队列

    2018-01-18 工作队列是Linux内核中把工作延迟执行的一种手段,其目的不同于软中断,软中断是提高CPU的响应,尽可能的缩短关中断的时间:而工作队列主要目的是节省资源,其比较适合很微小的任务, ...

  3. Linux中的工作队列

    工作队列(work queue)是Linux kernel中将工作推后执行的一种机制.这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于 ...

  4. 浅析Linux中的进程调度

    2016-11-22 前面在看软中断的时候,牵扯到不少进程调度的知识,这方面自己确实一直不怎么了解,就趁这个机会好好学习下. 现代的操作系统都是多任务的操作系统,尽管随着科技的发展,硬件的处理器核心越 ...

  5. linux中的tasklet机制【转】

    转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...

  6. Linux中断分层--工作队列

    1. 工作队列是一种将任务推后执行的方式,它把推后的任务交由一个内核线程去执行.这样中断的下半部会在进程上下文执行,他允许重新调度甚至睡眠.每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队 ...

  7. 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制

    参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...

  8. Linux中find常见用法示例

    ·find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find命令的参数: pathname: find命 ...

  9. Linux中检索文件

    1 , Use locate command It is a fast way to find the files location, but if a file just created ,it w ...

随机推荐

  1. bzoj 3225: [Sdoi2008] 立方体覆盖 题解

    [原题] 3225: [Sdoi2008]立方体覆盖 Time Limit: 2 Sec  Memory Limit: 128 MB Submit: 51  Solved: 36 [Submit][S ...

  2. [Flexbox] Using order to rearrange flexbox children

    Using the order property we alter the order in which flexbox children appear on the page, without ma ...

  3. Linux下一个Redis启动/关闭/重新启动服务脚本

    脚本功能: 实现redis单机多实例情况下的正常启动.关闭.重新启动单个redis实例.完毕系统标准服务的下面经常使用功能:  start|stop|status|restart 注:redis程序代 ...

  4. [转] Nginx模块开发入门

    前言 Nginx是当前最流行的HTTP Server之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%.与Apache相比,Nginx在高并 ...

  5. [转] 有趣的JavaScript原生数组函数

    在JavaScript中,可以通过两种方式创建数组,Array构造函数和 [] 便捷方式, 其中后者为首选方法.数组对象继承自Object.prototype,对数组执行typeof操作符返回‘obj ...

  6. [转] nodeJS的post提交简单实现

    index.js: ? 1 2 3 4 5 6 7 8 var server = require('./server'); var router = require('./route'); var r ...

  7. python环境准备

    一.环境准备. 1.安装python3.5.2(勾选环境变量),python2.7.12 2.设置环境变量 (要求命令行输入python,进入python2命令行,打python3时,进入python ...

  8. Core Python Notes

    开发需要在读 Python 核心编程,一些 Point 记录如下. ******************************************** 版本相关 标准版的 Python 是用 C ...

  9. HTML (1)href与Action,get post

    1.   href与Action的区别 href只能get参数,action能get参数又能post参数 href一般用于单个连接,可以带参数(URL重写),是采用get方式请求的,在地址栏中可以看到 ...

  10. Linq101-Ordering

    using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { class Orderin ...