简介

讨论了 tasklet的使用,现在我们来讨论一下工作队列的使用。和task不同的是,Workqueue 可睡眠,有延迟。
而且比tasklet复杂。工作队列与任务小程序一样,用于延迟处理中断(但我们也可以将其用于其他目的),工作队列的大致工作流程,Tasklet由调度功能处理,工作队列由称为工作程序(workqueue)的特殊线程处理。 workqueue的主要设计思想:一个是并行,多个work不要相互阻塞;另外一个是节省资源,多个work尽量共享资源(进程、调度、内存),不要造成系统过多的资源浪费。5e49be28fe.png
image.png
我们先来描述几个工作队列中使用到的名词。

  1. work :任务。
  2. workqueue :任务的集合(队列)。workqueue和work是一对多的关系。
  3. worker :执行任务的线程,在代码中worker对应一个work_thread()内核线程。
  4. worker_pool:线程的集合。worker_pool和worker是一对多的关系。
  5. pwq(pool_workqueue):中间人/中介,负责建立起workqueue和worker_pool之间的关系。workqueue和pwq是一对多的关系,pwq和worker_pool是一对一的关系。
    1. [root@imx6ull:~]# ps|grep kworker
    2. 5 root [kworker/0:0H]
    3. 6 root [kworker/u2:0]
    4. 110 root [kworker/0:3]
    5. 127 root [kworker/u2:2]

    worker_pool

    首先执行work的线程我们称作worker,多个worker集合在一起称作 worker_poolworker_pool可以分为两种绑定队列(bound queues)和非绑定队列(unbound queues)。
  • 绑定队列只能在当前核心上面执行
  • 非绑定队列可以在多有核心上面运行

    bound queues

    这个类型的线程是作为默认创建的worker模板。系统会给每一个CPU(核心)创建 NR_STD_WORKER_POOLS个pool。其两个pools的默认nice为** 0** 以及 HIGHPRI_NICE_LEVEL。在添加过程中将作品绑定到当前CPU。因此,在这种队列中,工作是在对其进行调度的核心上执行的。在这方面,绑定队列类似于任务集。
    1. kernel/workqueue.c
    2. NR_STD_WORKER_POOLS = 2, /* # standard pools per cpu */
    3. static int __init init_workqueues(void)
    4. int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };

    unbound worker_pool

    这个类型的线程可以在任何核心上运行。
    **3-工作队列使用-Workqueue - 图3

    使用workqueue的大致步骤

    首先使用 INIT_WORK 定义workqueue,其次使用 shudule_work或是queue_work 来激活这一个workqueue。
    值得注意的是即使使用queue_work等激活了这个work。但还是不会马上运行。需要等到你绑定的事件触发以后,就可有运行了。

    创建队列

    ```c include/linux/workqueue.h

    define alloc_workqueue(fmt, flags, max_active, args…) \

    __alloc_workqueue_key((fmt), (flags), (max_active), \
    1. NULL, NULL, ##args)

    调用例程

    rxrpc_workqueue = alloc_workqueue(“krxrpcd”, 0, 1); l2tp_wq = alloc_workqueue(“l2tp”, WQ_UNBOUND, 0);
  1. - fmtargs参数是名称和参数的printf格式。
  2. - max_activate 表示可以同时从这个队列中的单个CPU上执行工作的最大数量。
  3. - 下面是另外的一些可变参数
  4. <a name="DgRKs"></a>
  5. ### 创建标志的讲解
  6. - WQ_UNBOUND
  7. - 此标志会将队列分为绑定和非绑定队列两种形式
  8. ```c
  9. include/linux/workqueue.h
  10. WQ_UNBOUND
  11. WQ_FREEZABLE
  12. WQ_MEM_RECLAIM
  13. WQ_HIGHPRI
  14. WQ_CPU_INTENSIVE
  15. WQ_SYSFS

创建域销毁

  1. include/linux/workqueue.h
  2. #define DECLARE_WORK(n, f) \
  3. struct work_struct n = __WORK_INITIALIZER(n, f)
  4. #define DECLARE_DELAYED_WORK(n, f) \
  5. struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
  6. #define DECLARE_DEFERRABLE_WORK(n, f) \
  7. struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, TIMER_DEFERRABLE)

简单的demo

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/delay.h>
  4. #include <linux/workqueue.h>
  5. struct workqueue_struct *workqueue_test;
  6. struct work_struct work_test;
  7. void work_test_func(struct work_struct *work) { printk("%s()\n", __func__); }
  8. static int test_init(void) {
  9. printk("Hello,world!\n");
  10. /* 1. 自己创建一个workqueue, 中间参数为0,默认配置 */
  11. workqueue_test = alloc_workqueue("workqueue_test", 0, 0);
  12. /* 2. 初始化一个工作项,并添加自己实现的函数 */
  13. INIT_WORK(&work_test, work_test_func);
  14. /* 3. 将自己的工作项添加到指定的工作队列去, 同时唤醒相应线程处理 */
  15. queue_work(workqueue_test, &work_test);
  16. return 0;
  17. }
  18. static void test_exit(void) {
  19. printk("Goodbye,cruel world!\n");
  20. destroy_workqueue(workqueue_test);
  21. }
  22. module_init(test_init);
  23. module_exit(test_exit);
  24. MODULE_LICENSE("GPL");
  25. MODULE_AUTHOR("zhouchengzhu <1073355312@qq.com>");
  26. MODULE_DESCRIPTION("A simple workqueue driver");
  27. MODULE_VERSION("2:1.0");

参考资料

Linux Workqueue
Introduction to deferred interrupts (Softirq, Tasklets and Workqueues)
Multitasking Management in the Operating System Kernel
Multitasking in the Linux Kernel. Workqueues
workqueue —最清晰的讲解
Linux-workqueue讲解