工作队列是另一种将工作推后执行的形式,它可以把工作交给一个内核线程去执行,这个下半部是在进程上下文中执行的,因此,它可以重新调度还有睡眠。

区分使用软中断/tasklet还是工作队列比较简单,如果推后的工作不需要睡眠,那么就选择软中断或tasklet,但如果需要一个可以重新调度,可以睡眠,可以获取内存,可以获取信号量,可以执行阻塞式I/O操作时,那么,请选择工作队列吧!

在老的内核当中(2.6.36之前)工作队列是内核创建一个专门的工作者线程,它有一条任务链表,当设备或者内核某进程有部分任务需要推后处理的时候就把任务挂载在工作者线程的任务链表上,然后会在未来的某个时刻,工作者线程被调度,它就会去处理挂载在任务链表上的任务了。内核有一个缺省的工作者线程叫events/n,n是处理器的编号:每个处理器对应一个线程。如单处理器只有一个events/0,而双处理器会多出一个events/1。当然,我们也可以创建我们自己的工作者线程。假如有某个任务会被频繁的触发,那么便可以为它创建一个专门的工作者线程,比如触摸屏CTP。

然而在2.6.36之后的内核当中对工作队列子系统作了改变,采用的机制改变为并发管理工作队列机制(Concurrency Managed Workqueue (cmwq))。在原来的机制当中,当kernel需要创建一个workqueue(create_workqueue()方式)的时候,它会在每一个cpu上创建一个work_thread,为每一个cpu分配一个struct cpu_workqueue_struct,随着Kernel创建越来越多的workqueue,这将占用大量的的内存资源,并且加重了进程调度的任务量。而在新的工作队列机制中它不再在每次create_workqueue时都为workqueue创建一个work thread,而是在系统启动的时候给每个cpu创建一个work thread,当有任务项work_struct需要处理时,系统会将任务项work_struct交给某个处理器的work thread上去处理。

本文基于linux 3.14.77描述,与2.6内核在work_struct定义上不同,但原理相同。

1. 工作\工作队列\工作者线程关系

推后执行的任务叫工作work_struct,一个work_struct代表一个工作队列节点。

工作以队列结构组织成工作队列(workqueue),workqueue_struct。

工作线程就是负责执行工作队列中的工作。

2.定义

work_struct定义在linux/workqueue.h中,实现在kernel/workqueue.c中。

  1. typedef void (*work_func_t)(struct work_struct *work);

这个函数会有一个工作者线程执行,因此函数运行在进程上下文。默认情况下,允许先响应中断,并且不持有锁。
尽管该函数运行在进程上下文,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。(通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有此时它才会映射用户空间的内存)。

  1. struct work_struct {
  2. atomic_long_t data;
  3. struct list_head entry; /*连接所有工作的链表*/
  4. work_func_t func;
  5. #ifdef CONFIG_LOCKDEP
  6. struct lockdep_map lockdep_map;
  7. #endif
  8. };

这些结构被连接成链表,当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移走。当链表上不再有对象时,它就会继续休眠。

初始化并绑定处理函数(宏定义)

  1. DECLARE_WORK(name,work_func_t*); // 静态
  2. INIT_WORK(struct work_struct *work, work_func_t *); // 静态

工作队列调度

  1. static inline bool schedule_work(struct work_struct *work)
  2. {
  3. return queue_work(system_wq, work);
  4. }

如果当前work_struct已经被调度了,schedule_work返回0,否则返回非0。当使用schedule_work方法将工作节点添加到系统工作队列,实际上就是添加到当前CPU的工作队列中。

  1. /*
  2. * System-wide workqueues which are always present.
  3. *
  4. * system_wq is the one used by schedule[_delayed]_work[_on]().
  5. * Multi-CPU multi-threaded. There are users which expect relatively
  6. * short queue flush time. Don't queue works which can run for too
  7. * long.
  8. *
  9. * system_long_wq is similar to system_wq but may host long running
  10. * works. Queue flushing might take relatively long.
  11. *
  12. * system_unbound_wq is unbound workqueue. Workers are not bound to
  13. * any specific CPU, not concurrency managed, and all queued works are
  14. * executed immediately as long as max_active limit is not reached and
  15. * resources are available.
  16. *
  17. * system_freezable_wq is equivalent to system_wq except that it's
  18. * freezable.
  19. *
  20. * *_power_efficient_wq are inclined towards saving power and converted
  21. * into WQ_UNBOUND variants if 'wq_power_efficient' is enabled; otherwise,
  22. * they are same as their non-power-efficient counterparts - e.g.
  23. * system_power_efficient_wq is identical to system_wq if
  24. * 'wq_power_efficient' is disabled. See WQ_POWER_EFFICIENT for more info.
  25. */
  26. extern struct workqueue_struct *system_wq;
  27. extern struct workqueue_struct *system_long_wq;
  28. extern struct workqueue_struct *system_unbound_wq;
  29. extern struct workqueue_struct *system_freezable_wq;
  30. extern struct workqueue_struct *system_power_efficient_wq;
  31. extern struct workqueue_struct *system_freezable_power_efficient_wq;

Linux内核会为每个处理器创建一个线程,用来处理工作队列中的工作。这类线程实际上就是普通的内核线程(调用worker_thread函数创建)。除了创建工作线程外,Linux内核还会创建一个全局的工作队列(system_wq),这些工作在Linux内核启动时就已经做完了。因此,如果工作不紧急的话,可以直接使用system_wq作为工作队列,也就是将工作节点(work_struct)直接添加到system_wq中。schedule_work函数的任务实际上就是将work参数指定的工作节点添加到系统的工作队列中(system_wq)。

  1. /*通过传递不同的参数来创建用途不同的工作队列,实现在kernel/workqueue.c
  2.  
  3. events:普通工作队列,大部分的延迟处理函数都在这个工作队列上运行,要求任务执行时间短,避免互相影响。
  4.  
  5. events_long:需要长时间运行的工作项可在这个工作队列上运行。
  6.  
  7. events_unbound:该工作队列上的任务不会绑定在特定CPU上,只要某处理器空闲,就可以处理该工作队列上的任务。
  8.  
  9. events_freezable:类似于events,但工作项都是被挂起的 */
  10.  
  11. system_wq = alloc_workqueue("events", 0, 0);
  12.  
  13. system_long_wq = alloc_workqueue("events_long", 0, 0);
  14.  
  15. system_unbound_wq = alloc_workqueue("events_unbound",WQ_UNBOUND,WQ_UNBOUND_MAX_ACTIVE);

这些由Linux内核创建的工作线程的命名规则如下:kworker/cpu_id:thread_id 。对于未绑定CPU的线程池中的线程,则显示为kworker/u:thread_id。

大部分格式都是  kworker /u2:0 或者  kworker /0:0H, 查看资料得知:

内核中有很多kworker,有绑定cpu的和不绑定cpu的,它支持cpu的hotplug时work的迁移。

u:是unbound的缩写,代表没有绑定特定的CPU,kworker /u2:0中的 2 是 work_pool 的ID。

不带u的就是绑定特定cpu的workerq,它在init_workqueues中初始化,给每个cpu分配worker,如果该worker的nice小于0,说明它的优先级很高,所以就加了H属性。

参考:http://www.cnblogs.com/rohens-hbg/p/6129069.html

3. 应用

  1. struct work_struct mywork;
  2. void work_handler(struct work_struct *work); // 底半部处理函数
  3.  
  4. INIT_WORK(&mywork, work_handler);
  5. schedule_work(&mywork); //调度工作

4. 工作队列扩展

默认情况下可直接采用系统工作队列处理工作,当然也可以创建新的工作线程和工作队列来处理工作。

  1. struct workqueue_struct;
  2. create_workqueue(name); // 宏定义,创建一个新的工作队列
  3. create_singlethread_workqueue("helloworld"); //宏定义, 创建一个单线程的工作队列不与任何CPU绑定
  4. int queue_work(struct workqueue_struct *wq, struct work_struct *work); //当前CPU
  5. int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work); // 指定CPU
  6. void flush_workqueue(struct workqueue_struct *wq); // 确保工作队列中的工作都完成
  7. void destroy_workqueue(struct workqueue_struct *wq); // 销毁工作队列

应用

  1. struct workqueue_struct *queue = NULL;
  2. queue = create_workqueue("newProcess");//创建多个CPU上的工作者进程
  3. struct work_struct mywork;
  4. void work_handler(struct work_struct *work); // 底半部处理函数
  5. INIT_WORK(&mywork, work_handler); //初始化work
  6. queue_work(queue, &mywork); // 把工作添加到当前cpu的工作队列中
  7. flush_workqueue(queue); //刷新
  8. destroy_workqueue(queue);//销毁新创建的工作者进程

5. delayed_work

对于周期性的任务,内核提供了一套封装好的快捷机制,本质上利用了工作队列和定时器实现,这套机制就是delayed_work。

  1. void delayed_work_timer_fn(unsigned long __data);
  2. struct delayed_work {
  3. struct work_struct work;
  4. struct timer_list timer;
  5.  
  6. /* target workqueue and CPU ->timer uses to queue ->work */
  7. struct workqueue_struct *wq;
  8. int cpu;
  9. };
  1. DECLARE_DELAYED_WORK(n, f); //宏定义
  2. INIT_DELAYED_WORK(_work, _func); //宏定义
  3. static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay); // 指定延时后执行调度

其中delay的单位是jiffies,因此一种常见的用法如下:

  1. schedule_delayed_work(&work, msecs_to_jiffies(poll_interval));

如果要周期性的执行任务,通常会在delayed_work的工作函数中再次调用schedule_delayed_work(),周而复始。

  1. bool cancel_delayed_work(struct delayed_work *dwork); //取消延迟工作
  2. bool cancel_delayed_work_sync(struct delayed_work *dwork); // 取消延迟工作

参考:

1. linux内核对中断的处理方式

2. tasklet_workqueue

3. 工作队列

4. kworker内核工作队列详解

工作队列workqueue应用的更多相关文章

  1. 工作队列(workqueue) create_workqueue/schedule_work/queue_work

    --- 项目需要,在驱动模块里用内核计时器timer_list实现了一个状态机.郁闷的是,运行时总报错"Scheduling while atomic",网上搜了一下:" ...

  2. linux工作队列 - workqueue总览【转】

    转自:https://blog.csdn.net/cc289123557/article/details/52551176 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载 ...

  3. kobox : key_wq.c -v1 如何使用工作队列 workqueue

    kobox: key_wq.c - v1 说明: TQ2440主要驱动因素,四个按键驱动的处理 key_wq.c和key.c类别似,与key.c之间的差异的主要驱动力: key.c使用计时器,在中断发 ...

  4. java线程池-工作队列workQueue

    线程池之工作队列 ArrayBlockingQueue 采用数组来实现,并采用可重入锁ReentrantLock来做并发控制,无论是添加还是读取,都先要获得锁才能进行操作 可看出进行读写操作都使用了R ...

  5. [内核]Linux workqueue

    转自:http://blog.chinaunix.net/uid-24148050-id-296982.html 一.workqueue简介workqueue与tasklet类似,都是允许内核代码请求 ...

  6. rabbitmq 重复ACK导致消息丢失

    rabbitmq 重复确认导致消息丢失 背景 rabbitmq 在应用场景中,大多采用工作队列 work-queue的模式. 在一个常见的工作队列模式中,消费者 worker 将不断的轮询从队列中拉取 ...

  7. 异步框架asyn4j的原理

    启动时调用init方法 public void init(){ if (!run){ run = true; //工作队列 workQueue = newPriorityBlockingQueue(m ...

  8. sd 卡驱动--基于高通平台

    点击打开链接 内容来自以下博客: http://blog.csdn.net/qianjin0703/article/details/5918041 Linux设备驱动子系统第二弹 - SD卡 (有介绍 ...

  9. java网络编程serversocket

    转载:http://www.blogjava.net/landon/archive/2013/07/24/401911.html Java网络编程精解笔记3:ServerSocket详解ServerS ...

随机推荐

  1. 使用Promise

    Promise所要解决的问题:回调地狱 asyncTask1(data, function (data1){ asyncTask2(data1, function (data2){ asyncTask ...

  2. eclipse 在weblogic部署的工程项目开启远程调试remote config eclipse远程调试配置

    确认你的工程在weblogic中跑的起来,然后再结合eclipse debug配置+java debug运行模式搞个调试. 工程能跑起来没问题后,先在eclipse中,点击debug图标 然后点击De ...

  3. xp看系统位数

      运行cmd,看打开的cmd窗口标题一般c:\windows\system32代表32位操作系统,64位就是system64,详细系统信息运行命令“systeminfo”  

  4. eclipse.ini的相关说明

    http://www.cnblogs.com/yan5lang/archive/2011/05/24/2055867.htmlEclipse的启动由$ECLIPSE_HOME/eclipse.ini控 ...

  5. python webdriver API学习笔记

    浏览器操作 driver.maximize_window() #浏览器最大化 driver.set_window_size(480,800) #设置浏览器宽,高 driver.back() & ...

  6. CFS调度器

    一.前言 随着内核版本的演进,其源代码的膨胀速度也在递增,这让Linux的学习曲线变得越来越陡峭了.这对初识内核的同学而言当然不是什么好事情,满腔热情很容易被当头浇灭.我有一个循序渐进的方法,那就是先 ...

  7. nginx实战三

    nginx正向代理 https://coding.net/u/aminglinux/p/nginx/git/blob/master/proxy/z_proxy.md Nginx正向代理使用场景并不多见 ...

  8. [转]Hspice 语法手册

    一.HSPICE基础知识Avant! Start-Hspice(现在属于Synopsys公司)是IC设计中最常使用的电路仿真工具,是目前业界使用最为广泛的IC设计工具,甚至可以说是事实上的标准.目前, ...

  9. WinForm窗体键盘事件,支持方向键和回车键

    /// <summary> /// 快捷键操作 /// </summary> protected override bool ProcessCmdKey(ref Message ...

  10. 机器学习 Top 20 Python 开源项目

    转自:http://mp.weixin.qq.com/s?__biz=MzA4MjEyNTA5Mw==&mid=2652565022&idx=1&sn=9aa035097120 ...