1.软文推荐

2.软文推荐

3.软文推荐

在内核编程中,workqueue机制是最常用的异步处理方式。下面良许教程网为大家详细讲解一下Linux系统中的workqueue机制。

workqueue简介:

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

Linux中的workqueue机制

Linux中的workqueue机制是中断底半部的一种实现,同时也是一种通用的任务异步处理的手段。进入workqueue队列处理的任务(work item)在代码中由”work_struct “结构体表示(定义在include/linux/workqueue.h):

 struct work_struct {
  struct list_head entry;
  work_func_t func;
  atomic_long_t data;
 };

其中,”entry”表示其所挂载的workqueue队列的节点,”func”就是要执行的任务的入口函数。而”data”表示的意义就比较丰富了。最后的4个bits是作为”flags”标志位使用的,中间的4个bits是用于flush功能的”color”。flush功能简单地说就是:等待workqueue队列上的任务都处理完,并清空workqueue队列(由于笔者也没有深入研究过这一块的具体实现原理,在本文的叙述中就不涉及这一部分内容了)。

剩下的bits在不同的场景下有不同的含义(相当于C语言里的”union”),它可以指向work item所在的workqueue队列的地址,由于低8位被挪作他用,因此要求workqueue队列的地址是按照256字节对齐的。它还可以表示处理work item的worker线程所在的pool的ID(关于pool将在本文的后半部分介绍)。

这种在一个C语言变量里塞入不同的类型的数据的方法在Linux的代码实现中还是不难见到的,在目前的workqueue机制中,”flags”和”color”所需的bits都较少,单独使用整形变量去表示确实会增加一定的内存消耗。但这种牺牲可读性的做法也被一些内核开发者认为是比较”ugly”的。

为了充分利用locality,通常选择将处理hardirq的CPU作为该hardirq对应的workqueue底半部的执行CPU,在早期Linux的实现中,每个CPU对应一个workqueue队列,并且每个CPU上只有一个worker线程来处理这个workqueue队列,也就是说workqueue队列和worker线程都是per-CPU的,且一一对应。

让我们看看这种设计存在什么问题。假设现在一个work item(设为w0)被添加到了workqueue队列上。w0需要运行5ms后休眠10ms,接着再运行5ms。在w0开始运行5ms和10ms后,另外两个work items(设为w1和w2)也分别加入了workqueue队列,w1和w2都是需要运行5ms,然后再休眠10ms(该示例来自内核Documentation/core-api/workqueue.rst文档)。

因为只有1个worker线程,所以即便在执行某个work item的时候休眠,其他的work item也得不到执行,因此将这3个work item执行完毕将总共需要55ms的时间。

假设现在一个CPU上有2个worker线程,分别为worker 1和worker 2,那么整个执行时间将缩短到35ms:

如果一个CPU上有3个worker线程,执行时间将进一步缩短到25ms:

cmwq

这种在一个CPU上运行多个worker线程的做法,就是2.6.36版本引入的,也是现在Linux内核所采用的concurrency managed workqueue,简称cmwq。一个CPU上是不可能“同时”运行多个线程的,所以这里的名称是concurrency(并发),而不是parallelism(并行)。

显然,设置合适的worker线程数目是很关键的,多了浪费资源,少了又不能充分利用CPU。大体的原则就是:如果现在一个CPU上的所有worker线程都进入了睡眠状态,但workqueue队列上还有未处理的work item,那么就再启动一个worker线程。

一个CPU上的所有worker线程共同构成了一个worker pool(此概念由内核v3.8引入),我们可能比较熟悉memory pool,当需要内存时,就从空余的memory pool中去获取,同样地,当workqueue上有work item待处理时,我们就从worker pool里挑选一个空闲的worker线程来服务这个work item。

worker pool在代码中由”worker_pool “结构体表示(定义在kernel/workqueue.c):

 struct worker_pool {
  spinlock_t  lock;  /* the pool lock */
  int   cpu;  /* the associated cpu */
  int   id;  /* pool ID */
 
  struct list_head idle_list/* list of idle workers */
  DECLARE_HASHTABLE(busy_hash6);        /* hash of busy workers */
     ...
 }

如果一个worker正在处理work item,那么它就是busy的状态,将挂载在busy workers组成的6阶的hash表上。既然是hash表,那么就需要key,充当这个key的是正在被处理的work item的内存地址。

如果一个worker没有处理work item,那么它就是idle的状态,将挂载在idle workers组成的链表上。因为空闲的worker线程数目较少,用链表管理就可以了,而busy的worker线程可能较多,所以用hash表来组织,以加快查找的速度。

前面说过,有未处理的work item,内核就会启动一个新的worker线程,以提高效率。有创建就有消亡,当现在空闲的worker线程过多的时候,就需要销毁一部分worker线程,以节省CPU资源。就像一家公司,在项目紧张,人员不足的时候需要招人,在项目不足,人员过剩的时候可能就会裁员。至于保留多少空闲线程可以取得较理想的平衡,则涉及到一个颇为复杂的算法,在此就不展开了。

worker线程在代码中由”worker “结构体表示(定义在kernel/workqueue_internal.h):

 struct worker {
  struct worker_pool  *pool;  /* the associated pool */
  union {
   struct list_head  entry/* while idle */
   struct hlist_node hentry/* while busy */
  };
 
  struct work_struct *current_work;   /* work being processed */
  work_func_t   current_func;   /* current_work's fn */
  struct task_struct *task;    /* worker task */
 
  struct pool_workqueue *current_pwq;     /* current_work's pwq */
         ...
 }

其中,”pool”是这个worker线程所在的worker pool,根据worker线程所处的状态,它要么在idle worker组成的空闲链表中,要么在busy worker组成的hash表中。

“current_work”和”current_func”分别是worker线程正在处理的work item和其对应的入口函数。既然worker线程是一个内核线程,那么不管它是idle,还是busy的,都会对应一个task_struct(由”task”表示)。

“current_pwq”指向被服务的work item所在的workqueue队列,

以上就是良许教程网为各位朋友分享的Linux系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你!

本文来源:www.lxlinux.net/1572.html,若引用不当,请联系修改。

相关文章 8

1

Linux常用命令—lpstat命令 1分钟前

Linux常用命令 lpstat命令 用于显示CUPS中打印机的状态信息,下面良许教程网为大家分享一下Linux常用命令lpstat命令具体使用方法。 lpstat 显示...

2

清除本地dns缓存(dns缓存怎么清) 2分钟前

目录:1、怎么清理dns缓存 清理dns缓存的方法2、Windows清除本地DNS缓存3、清除dns缓存的命令4、如何清理dns缓存5、如何清除本地DNS缓存 windo...

3

详解Lua基本类型 3分钟前

Lua是一种动态类型的语言。在语言中没有类型定义的语法,每个值都带有其自身的类型信息。在Lua中有8中基本类型。 数据类型 描述 nil 这个...

4

洛阳小程序开发(洛阳小程序开发,洛阳公众号搭建,洛阳软件开发公司) 4分钟前

目录:1、小程序开发哪家公司做的好?2、洛阳做的比较好一点的网站制,小程序制作!的公司那家技术好!3、洛阳开发小程序的公司有哪...

5

Linux下的json文件操作 7分钟前

python脚本中读取配置文件通常将配置文件放在json格式的文件中,然后做后续的处理,那么在linux服务器上编辑和读取json文件有什么技巧呢?...

6

讲解一下C# 程序结构 7分钟前

一个 C# 程序主要包括以下部分:命名空间声明(Namespace declaration) 一个 class;Class 方法;Class 属性;一个 Main 方法;语句(Statements) 表达...

7

python3.9编译器(菜鸟教程python3在线编译器) 10分钟前

目录:1、自学python 用什么编译器2、python的编辑器有哪些?3、python有什么好的入门教程?用什么编译器呢4、python一般用什么编译器5、Pyth...

8

生产环境中常用的shell脚本(下) 11分钟前

本文档介绍30个关于Shell脚本的经典案例(下) 本文目录 21、从FTP服务器下载文件22、连续输入5个100以内的数字,统计和、最小和最大23、将...