zoukankan      html  css  js  c++  java
  • 转: workqueue

    2011-07-18 23:01:09|  分类: 软件_Android_Lin |  标签:linux  kernel  内核  调度  workqueue  |字号 订阅

    1. 什么是workqueue
    Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程.

    工作队列(workqueue)是另外一种将工作推后执行的形式.工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。

    2. 数据结构
    我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,
    struct work_struct {
        atomic_long_t data;       /*工作处理函数func的参数*/
    #define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
    #define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */
    #define WORK_STRUCT_FLAG_MASK (3UL)
    #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
        struct list_head entry;        /*连接工作的指针*/
        work_func_t func;              /*工作处理函数*/
    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    };


    这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,
    struct workqueue_struct {
     struct cpu_workqueue_struct *cpu_wq;
     struct list_head list;
     const char *name;   /*workqueue name*/
     int singlethread;   /*是不是单线程 - 单线程我们首选第一个CPU -0表示采用默认的工作者线程event*/
     int freezeable;  /* Freeze threads during suspend */
     int rt;
    }; 

    如果是多线程,Linux根据当前系统CPU的个数创建cpu_workqueue_struct 其结构体就是,
    truct cpu_workqueue_struct {
     spinlock_t lock;/*因为工作者线程需要频繁的处理连接到其上的工作,所以需要枷锁保护*/
     struct list_head worklist;
     wait_queue_head_t more_work;
     struct work_struct *current_work; /*当前的work*/
     struct workqueue_struct *wq;   /*所属的workqueue*/
     struct task_struct *thread; /*任务的上下文*/
    } ____cacheline_aligned;
    在在该结构主要维护了一个任务队列,以及内核线程需要睡眠的等待队列,另外还维护了一个任务上下文,即task_struct。
    三者之间的关系如下:

    workqueue - G-tech - G-tech的博客

     
    3. 创建工作
    3.1 创建工作queue
    a. create_singlethread_workqueue(name)
    该函数的实现机制如下图所示,函数返回一个类型为struct workqueue_struct的指针变量,该指针变量所指向的内存地址在函数内部调用kzalloc动态生成。所以driver在不再使用该work queue的情况下调用void destroy_workqueue(struct workqueue_struct *wq)来释放此处的内存地址。

    workqueue - G-tech - G-tech的博客

     
    图中的cwq是一per-CPU类型的地址空间。对于create_singlethread_workqueue而言,即使是对于多CPU系统,内核也只负责创建一个worker_thread内核进程。该内核进程被创建之后,会先定义一个图中的wait节点,然后在一循环体中检查cwq中的worklist,如果该队列为空,那么就会把wait节点加入到cwq中的more_work中,然后休眠在该等待队列中。

    Driver调用queue_work(struct workqueue_struct *wq, struct work_struct *work)向wq中加入工作节点。work会依次加在cwq->worklist所指向的链表中。queue_work向cwq->worklist中加入一个work节点,同时会调用wake_up来唤醒休眠在cwq->more_work上的worker_thread进程。wake_up会先调用wait节点上的autoremove_wake_function函数,然后将wait节点从cwq->more_work中移走。

    worker_thread再次被调度,开始处理cwq->worklist中的所有work节点...当所有work节点处理完毕,worker_thread重新将wait节点加入到cwq->more_work,然后再次休眠在该等待队列中直到Driver调用queue_work...

    b. create_workqueue

    workqueue - G-tech - G-tech的博客

    相对于create_singlethread_workqueue, create_workqueue同样会分配一个wq的工作队列,但是不同之处在于,对于多CPU系统而言,对每一个CPU,都会为之创建一个per-CPU的cwq结构,对应每一个cwq,都会生成一个新的worker_thread进程。但是当用queue_work向cwq上提交work节点时,是哪个CPU调用该函数,那么便向该CPU对应的cwq上的worklist上增加work节点。

    c.小结
    当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对workqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。

     上述描述的workqueue内核实现原理可以描述如下:

    workqueue - G-tech - G-tech的博客

    3.2  创建工作
    要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:
    DECLARE_WORK(name,void (*func) (void *), void *data);
    这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。
    同样,也可以在运行时通过指针创建一个工作:
    INIT_WORK(structwork_struct *work, woid(*func) (void *), void *data);

    4. 调度
    a. schedule_work

    在大多数情况下, 并不需要自己建立工作队列,而是只定义工作, 将工作结构挂接到内核预定义的事件工作队列中调度, 在kernel/workqueue.c中定义了一个静态全局量的工作队列static struct workqueue_struct *keventd_wq;默认的工作者线程叫做events/n,这里n是处理器的编号,每个处理器对应一个线程。比如,单处理器的系统只有events/0这样一个线程。而双处理器的系统就会多一个events/1线程。
    调度工作结构, 将工作结构添加到全局的事件工作队列keventd_wq,调用了queue_work通用模块。对外屏蔽了keventd_wq的接口,用户无需知道此参数,相当于使用了默认参数。keventd_wq由内核自己维护,创建,销毁。这样work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

    b. schedule_delayed_work(&work,delay);
    有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,同时也可以利用timer来进行延时调度,到期后才由默认的定时器回调函数进行工作注册。延迟delay后,被定时器唤醒,将work添加到工作队列wq中。

    工作队列是没有优先级的,基本按照FIFO的方式进行处理。

    5. 示例:
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/workqueue.h>

    static struct workqueue_struct *queue=NULL;
    static struct work_struct   work;

    staticvoid work_handler(struct work_struct *data)
    {
           printk(KERN_ALERT"work handler function.\n");
    }

    static int __init test_init(void)
    {
          queue=create_singlethread_workqueue("hello world");/*创建一个单线程的工作队列*/
          if (!queue)
                goto err;

           INIT_WORK(&work,work_handler);
           schedule_work(&work);

          return0;
    err:
          return-1;
    }

    static   void __exit test_exit(void)
    {
           destroy_workqueue(queue);
    }
    MODULE_LICENSE("GPL");
    module_init(test_init);
    module_exit(test_exit);

  • 相关阅读:
    Python元组、列表、字典
    测试通过Word直接发布博文
    Python环境搭建(windows)
    hdu 4003 Find Metal Mineral 树形DP
    poj 1986 Distance Queries LCA
    poj 1470 Closest Common Ancestors LCA
    poj 1330 Nearest Common Ancestors LCA
    hdu 3046 Pleasant sheep and big big wolf 最小割
    poj 3281 Dining 最大流
    zoj 2760 How Many Shortest Path 最大流
  • 原文地址:https://www.cnblogs.com/zylthinking/p/2944322.html
Copyright © 2011-2022 走看看