分享

Linux中的工作队列

 wu_wade 2015-03-03
 

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

 

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

 

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

 

 

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

 

Linux中的工作队列 - Tanatseng - Tanatseng Blog

 

 

由于 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 <linux/kernel.h>

#include <linux/module.h>

#include <linux/workqueue.h>

 

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() 函数,这个函数会使等待队列中所有的任务都被执行。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多