Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18028
  • 博文数量: 4
  • 博客积分: 190
  • 博客等级: 入伍新兵
  • 技术积分: 52
  • 用 户 组: 普通用户
  • 注册时间: 2007-06-08 20:28
文章分类
文章存档

2010年(3)

2008年(1)

我的朋友
最近访客

分类: LINUX

2010-06-17 16:13:49

 

 

0 引言

操作系统为了尽可能快速对外部事件作出响应,在linux2.6内核中实现了软中断(sotfirq)和小任务(tasklet)机制,这两种机制虽然解决了一些场合快速对外部事件作出响应的问题,但是它们依然是运行在中断上下文中,因此不能使用信号量和进入休眠。正是为了弥补二者在处理中断上的缺陷,工作队列机制(work queque)产生了,它可以借助于进程调度的优势,把许多需要长时间处理甚至可能休眠的动作放在专门的进程(线程)中实现。

1. 数据结构

1.1工作队列全局信息结构

  

struct workqueue_struct {

       struct cpu_workqueue_struct cpu_wq[NR_CPUS]; /* 工作队列对应的处理器线程集合 */

       const char *name;                            /* 工作队列名称 */

       struct list_head list;                             /* 工作队列链表 */

};

     workqueue_struct由每个CPU的工作队列组成的数组,是外部可见的所有工作队列的抽象。struct cpu_workqueue_struct cpu_wq[NR_CPUS];,是对应的工作线程,通常针对同一个工作队列来说,其工作线程的数目与CPU数目一致。在下面的叙述中把这个struct workqueue_struct数据结构就称为工作队列。

1.2  工作者线程信息

 struct cpu_workqueue_struct {

 

       spinlock_t lock;

 

       long remove_sequence;

       long insert_sequence;    

 

       struct list_head worklist;     

       wait_queue_head_t more_work; /* 尚未执行的工作任务等待队列 */

       wait_queue_head_t work_done; /* 已经完成的工作任务等待队列 */

 

       struct workqueue_struct *wq;  /* 指向所在工作队列全局信息struct workqueue_struct */

       task_t *thread;              /* 线程入口函数 */

 

       int run_depth;       

}

 

这是工作队列在多CPU实现线程并发调度的核心数据结构,对于同一工作队列,每个CPU上都存在一个这样的一个结构体。其中

wait_queue_head_t more_work;

       wait_queue_head_t work_done;

涉及到工作队列中的回调函数执行同步和调度机制,下面2.2节将有详细介绍。

1.3 工作者任务信息

struct work_struct {

       unsigned long pending;

       struct list_head entry; /*通用链表指针 */

       void (*func)(void *); /* 回调函数 */

       void *data;        /* 参数 */

       void *wq_data; /* 一般指向 struct cpu_workqueue_struc */

       struct timer_list timer;

};

这是针对每个工作队列,工作任务所要完成动作的最终数据结构,void (*func)(void *);就是下面叙述中提到的工作线程执行的回调函数指针。

1.4  数据结构之间关系

一个工作队列可以由多个工作线程实现,一个工作线程上可以执行多个工作任务。工作队列全局信息,工作者线程信息,工作任务信息,相关数据结构关系下图:

 

2. 工作者队列实现

2.1 工作队列和工作任务创建

如果要使用新的工作队列,首先是建立属于自己的工作队列,系统提供了两个接口

#define create_workqueue(name) __create_workqueue((name), 0)

#define create_singlethread_workqueue(name) __create_workqueue((name), 1)

很明显,从名称上可以知道第一个是创建多线程的工作队列,也就是说在所有CPU上都建立直接工作者线程数据结构struct cpu_workqueue_struct,而第二个是只在本地CPU上建立工作者线程。

__create_workqueu是建立工作者队列的核心处理函数,根据当前CPU信息通过系统调用create_workqueue_thread创建该CPU的工作者线程。并指定该线程的入口函数为worker_thread,它是工作队列执行回调功能的总入口。

上面只是完成了工作队列框架,还没有把自己的让工作线程完成的动作加入到工作者线程回调函数链表中,这就是queue_work所要完成的功能。它把一个具体工作者所要完成的功能,采用回调函数方式加入到struct work_struct链表中,等待调度器执行到该工作者线程时完成该动作。下图标示了工作者线程创建流程:

 

 

2.2  工作队列调度和同步机制

 工作队列创建时分配了结构体workqueue_struct,创建了对应该工作者线程work_thread。工作任务初始化时候需要挂接工作任务work_struct对应的函数到工作队列链表中。通常对一个新创建的工作队列调度主要有下面几个接口函数:

flush_workqueue 遍历当前工作队列上所有工作任务。

queue_work:把新建工作任务结构体work_struct加入工作队列,等待调度执行

run_workqueue:执行工作队列中的工作任务回调函数

Work_thread:所有工作队列对应的线程入口函数

destroy_workqueue:从工作队列链表中移除一个工作队列

前面在1.2节提到得结构体struct cpu_workqueue_struct中的两个成员Work_morework_done,前者是表明当前工作队列中是否还有工作任务等待处理,后者标明工作任务是否完成。通常我们在新建立一个工作任务加入工作队列时候需要调用queue_work,它通过work_up唤醒在work_more上睡眠的work_thread线程。基于work_more的调度和同步机制如图:

一般的,加入工作队列的工作任务会在工作线程下一次调度时别执行。但是有时候,在下一步执行之前需求保证所有工作队列要执行完毕。而work_done就是用来标示当前工作队列当中所有工作任务回调函数是否执行一遍,从而为这个工作队列的下一步执行做准备。比如我们要卸载某个工作队列时需要调用destroy_workqueue,这个函数就需要首先把当前所有工作队列上的工作任务执行一遍,flush_workqueue就是提供这样功能的接口。这一点和我们交换机上使用的插拔线卡板回调函数机制非常相似,当系统收到插拔卡事件时候,需要通知各个模块做相应的处理工作后才可以真正宣布插拔卡这一事件完成。基于work_done调度和同步机制入下图:

3.使用工作队列

工作队列描述比较繁琐,但是使用起来非常简单。特别是为了简化工作队列使用和减少创建内核线程数量,在最新的2.6内核上,系统为用户提供了一个名字为“events”的默认的工作队列。也就是说这个工作队列框架在系统初始化时候已经建立完成,用户只需要根据自己需要挂接自己工作任务的回调处理函数即可完成相关功能。

下面的代码简单描述了如何使用默认工作队列和新建工作队列在console界面打印出告警信息:

static void work_handler(int *pflag)
{

if (1==*pflag)

 printk(KERN_ALERT, now we are using default work_queue to print\n");

else

printk(KERN_ALERT, now we are using new work_queue to print\n");

}

static int __init test_init(int flag)
{

        /* 初始化,工作任务work,回调函数work_handler,附加参数flag */

INIT_WORK(&work, work_handler&flag);

   

/* 使用系统默认工作队列 */

if (1 == flag)

{  

    /* schedule_work是针对默认events工作队列,执行queue_work */

     schedule_work(&work);

}

/* 创建新的工作队列 */

else

{

/*创建一个单线程的工作队列*/
        queue = create_singlethread_workqueue("hellow ZTE ");       

 if (!queue)
                goto err;

/* 工作任务work加入工作队列queue等待调度 */

queue_work(queue, &work);
}

err:

…….

 

 

 } 

 

4. 小结

前篇技术积累《软中断和小任务机制》中提到的基于软中断的tasklet是一种把时延性较长的工作延迟到后期完成方法。但是tasklet同这里说的工作队列(workqueue)还是不同的,因为tasklet是在中断环境中,它是软中断的补充,通常必须在很短时间里面完成,不可以休眠或者使用信号量。而工作队列是单独在进程上下文中,可以借助于进程调度的优势完成休眠,当然也可以使用信号量。另外,虽然tasklet和工作队列都支持多处理器方,但是tasklet由于中断上下文限制,tasklet创建和调度总是在同一CPU上运行,而工作队列却不一定是这样,另外tasklet通常是作为中断处理的补充,而工作队列应用却不受这一限制。从而真正实现了多线程并发调度。

 

参考文献:

1. LINUX设备驱动程序》.     魏永明, .

2. LINUX内核设计与实现》.   陈莉君,.

 

阅读(1065) | 评论(4) | 转发(0) |
0

上一篇:软中断和小任务机制

下一篇:没有了

给主人留下些什么吧!~~