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

工作队列是实现延迟的新机制,从 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工作队列 - workqueue总览【转】

    转自:https://blog.csdn.net/cc289123557/article/details/52551176 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载 ...

  2. 【转载】linux 工作队列上睡眠的认识--不要在默认共享队列上睡眠

    最近项目组做xen底层,我已经被完爆无数遍了,关键在于对内核.驱动这块不熟悉,导致分析xen代码非常吃力.于是准备细细的将 几本 linux 书籍慢慢啃啃. 正好看到LINUX内核设计与实现,对于内核 ...

  3. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  4. Linux中断底半部机制

    参考: Linux下半部处理之软中断 linux中断底半部机制 <深入理解Linux内核>软中断/tasklet/工作队列 软中断和tasklet介绍 详解操作系统中断 Linux内核:中 ...

  5. 【转】 Linux中的工作队列

    原文网址:http://blog.chinaunix.net/uid-20583479-id-1920134.html 工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以 ...

  6. Linux中断管理 (3)workqueue工作队列

    目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...

  7. linux内核工作队列使用总结

    我总结出的内核工作队列中的4种用法 1. 使用系统的工作队列(不延迟) 1)定义一个工作: struct work_struct my_work; 2)编写一个函数: void my_work_fun ...

  8. linux驱动---等待队列、工作队列、Tasklets【转】

    转自:https://blog.csdn.net/ezimu/article/details/54851148 概述: 等待队列.工作队列.Tasklet都是linux驱动很重要的API,下面主要从用 ...

  9. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

随机推荐

  1. 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析

    前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...

  2. Linux 内核配置和编译

    Linux 内核配置和编译 一.配置内核 (1). 为什么要配置内核 1. 硬件需求 2. 软件需求 选出需要的,去掉不要的 (2). 如何配置内核 1. make  config 基于文本模式的交互 ...

  3. Colossal Fibonacci Numbers(巨大的斐波那契数)UVA 11582

    评测地址:http://acm.hust.edu.cn/vjudge/problem/41990 The i'th Fibonacci number f (i) is recursively de n ...

  4. VS的启动方式

    启动VS的两种方式1.双击图标2.调出cmd,输入 devenv

  5. CSS—换行

    关于文本溢出换行问题,先看下涉及到换行的相关属性,查看:http://www.w3school.com.cn 一.word-break 定义:word-break 属性规定自动换行的处理方法. 值 描 ...

  6. trigger,triggerhandler模拟事件

    常用模拟 有时候,需要通过模拟用户操作,来达到单击的效果.例如在用户进入页面后,就触发click事件,而不需要用户去主动单击. 在JQuery中,可以使用trigger()方法完成模拟操作.例如可以使 ...

  7. Lombok(1.14.8) - @Synchronized

    @Synchronized @Synchronized,实现同步. package com.huey.lombok; import java.util.Date; import lombok.Sync ...

  8. 分享我的2014年3月unity3d面试题与参考答案

    今年3月份面试unity3d游戏程序,先做的面试题,然后人事问了我几个之前游戏开发的问题,问我英语怎么样等等,介绍了公司情况和对unity3d程序的要求.之后就回来了,总共面了不到1个半小时吧.笔试答 ...

  9. response小结(五)—通过response实现请求重定向

    请求重定向指的是一个web资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向.302状态码和location头即可实现重定向. 请求重定向最常见的应用场景就是用户登录. 下面 ...

  10. Javascript之获取屏幕宽高

    <head> <title> new document </title> <meta name="generator" content=& ...