现在的位置: 首页 > 综合 > 正文

工作队列

2013年09月15日 ⁄ 综合 ⁄ 共 4916字 ⁄ 字号 评论关闭

工作队列是实现延迟的新机制,从 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 );
void destroy_workqueue( struct workqueue_struct * );



通过工作队列与之通信的任务可以由结构体 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 );



任务结构体的初始化完成后,接下来要将任务安排进工作队列。 可采用多种方法来完成这一操作(参见 清单 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 );



可以使用全局的内核全局工作队列,利用 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 );

 

还没有在处理程序当中执行的任务可以被取消。 调用 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;
}

 


回页首

微线程与工作队列的区别

从对微线程和工作队列的简短介绍中, 可以发现两个将任务从 top halves 延迟到 bottom halves 的不同方法。 微线程提供低延迟机制,该方式简单而直接,而工作队列提供复杂的 API 来允许对多个任务项目进行排队。 每种方法都在中断上下文延迟任务,但只有微线程采用 run-to-complete 的风格自动运行, 而在此处,如果需要,工作队列允许处理程序休眠。 为有效实现任务延迟,可根据具体需求来选择相应的方法。


回页首

更进一步

这里所探讨的任务延迟方法涉及了历史的和当前的应用在 Linux 内核中的延迟方法(除了计时器之外,这将在以后的文章中讨论)。它们当然不是新的 — 事实上,它们在过去已经以其他形式存在 — 但是它们代表了一种有趣的架构模式,这在 Linux 中和其他地方都很有用。从软中断到微线程再到任务队列再到延迟的工作队列,Linux 在提供一致的和兼容的用户空间体验的同时,保持其内核各方面的持续发展。

 

抱歉!评论已关闭.