内核workqueue框架

workqueue驱动的底半部实现方式之一就是工作队列,作为内核的标准模块,它的使用接口也非常简单,schedule_work或者指定派生到哪个cpu的schedule_work_on。

还有部分场景会使用自定义的workqueue,这种情况会直接调用queue_work和queue_work_on接口。

static inline bool schedule_work_on(int cpu, struct work_struct *work)
{
	return queue_work_on(cpu, system_wq, work);        //workqueue_struct使用通用的system_wq
}

虽然使用起来简单,但其实内核内部的整个workqueue框架还是比较复杂的。

后面摘录一些关键性的代码,辅助理解上面的框架:

1、queue_work接口如何选择pwq

如果workqueue使能了WQ_UNBOUND标识,则根据当前cpu选择一个numa node。unbounded pwq是已numa node来划分的。

如果是普通的workqueue,则按照用户指定的cpu(没有指定则使用当前cpu)找到相应的pwq。

	/* pwq which will be used unless @work is executing elsewhere */
	if (wq->flags & WQ_UNBOUND) {
		if (req_cpu == WORK_CPU_UNBOUND)
			cpu = wq_select_unbound_cpu(raw_smp_processor_id());
		pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
	} else {
		if (req_cpu == WORK_CPU_UNBOUND)
			cpu = raw_smp_processor_id();
		pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
	}

2、queue work时work struct挂载到哪个队列

1)当前pwq里pending的任务数量不多,直接挂载到worker pool的任务链表等待执行

2)pending的任务大于pwq的最大活跃数,先挂载到pwq的inactive_works链表;

	if (likely(pwq->nr_active < pwq->max_active)) {
		trace_workqueue_activate_work(work);
		pwq->nr_active++;
		worklist = &pwq->pool->worklist;        //pending任务不多,直接挂载到worker pool的任务链表等待执行
		if (list_empty(worklist))
			pwq->pool->watchdog_ts = jiffies;
	} else {
		work_flags |= WORK_STRUCT_INACTIVE;
		worklist = &pwq->inactive_works;    //pending的任务大于最大活跃数,则挂载到pwq的inactive_works链表;
	}

2、初始化pwq的worker_pool

alloc workqueue时,如何指定worker_pool?

1)per-cpu pwq和per-cpu worker pool

如果没有使能WQ_UNBOUND标识,则申请per-cpu pwqs,链接到per-cpu work_pools。

注意1:并不是为每个workqueue/pwq新创建了worker_pool。不管创建多少workqueue,他们指向的都是一组worker_pool。

那有人可能会想,如果有很多工作队列,只有一组worker_pool忙得过来吗?

其实worker_pool定义的是相同属性的一类工作者线程,比如工作在哪个cpu上/以哪个优先级运行/绑定哪个numa(unbounded绑定numa)。worker_pool中链表维护的worker才对应单个工作者线程。worker thread运行期间会根据当前的忙闲动态创删新的worker,动态启动和停止新的worker thread。

注意2:对于per-cpu worker_pool来说,每个cpu其实对应两个worker_pool,分别是highpri和lowpri,nice值分别为-20和0。实际配置给pwq的是高优先级worker_pool。

static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
	if (!(wq->flags & WQ_UNBOUND)) {
		wq->cpu_pwqs = alloc_percpu(struct pool_workqueue);    //per_cpu pwq
		if (!wq->cpu_pwqs)
			return -ENOMEM;

		for_each_possible_cpu(cpu) {
			struct pool_workqueue *pwq =
				per_cpu_ptr(wq->cpu_pwqs, cpu);
			struct worker_pool *cpu_pools =        //从cpu_worker_pools找到worker_pool
				per_cpu(cpu_worker_pools, cpu);

			init_pwq(pwq, wq, &cpu_pools[highpri]);    //链接到高优先级的worker_pool

			mutex_lock(&wq->mutex);
			link_pwq(pwq);
			mutex_unlock(&wq->mutex);
		}
		return 0;
	}
    /* 申请unbouned pwq*/
}

2)unbounded pwq和unbounded worker pool

使能了WQ_UNBOUND标识,则分配unbounded pwq。并从全局unbound_pool_hash中找到一个attrs属性指定的work_pool,如果没有会新建一个,将这个worker_pool配置给pwq。

alloc_and_link_pwqs  //先申请per cpu pwq,后申请unbounded pwq
    --->apply_workqueue_attrs
     --->apply_wqattrs_prepare
      --->alloc_unbound_pwq
       

static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
					const struct workqueue_attrs *attrs)
{
	struct worker_pool *pool;
	struct pool_workqueue *pwq;

	lockdep_assert_held(&wq_pool_mutex);

	pool = get_unbound_pool(attrs);    //找到相同attrs属性的worker_pool,没有则新建一个
	if (!pool)
		return NULL;

	pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
	if (!pwq) {
		put_unbound_pool(pool);
		return NULL;
	}

	init_pwq(pwq, wq, pool);    //pwq->pool = pool
	return pwq;
}

3、创建worker pool

1)per cpu的worker pool

per cpu的worker pool是一个全局性的初始化,在workqueue_init_early接口中实现。这明显是kernel_init启动过程中调用的初始化接口。

void __init workqueue_init_early(void)
{
	/* initialize CPU pools */
	for_each_possible_cpu(cpu) {
		struct worker_pool *pool;

		i = 0;
		for_each_cpu_worker_pool(pool, cpu) {    //遍历每个worker pool
			BUG_ON(init_worker_pool(pool));    //初始化worker pool
			pool->cpu = cpu;
			cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
			pool->attrs->nice = std_nice[i++];
			pool->node = cpu_to_node(cpu);

			/* alloc pool ID */
			mutex_lock(&wq_pool_mutex);
			BUG_ON(worker_pool_assign_id(pool));
			mutex_unlock(&wq_pool_mutex);
		}
	}
}

2)unbounded worker pool

前面讲为pwq链接worker pool的时候提过,unbounded worker pool是根据动态按需分配的。就是在使用的时候,如果已经创建了相同属性的worker pool,则使用;没有则新建一个。

static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
{
    /* 已有同属性的worker pool,则返回*/

    /* nope, create a new one */
	pool = kzalloc_node(sizeof(*pool), GFP_KERNEL, target_node);    //没有则创建
	if (!pool || init_worker_pool(pool) < 0)    //初始化worker pool
		goto fail;

}

4、创建workqueue

通用的创建接口就是alloc_workqueue。

struct workqueue_struct *alloc_workqueue(const char *fmt,
					 unsigned int flags,
					 int max_active, ...)
{
   --->alloc_and_link_pwqs //申请和初始化pwq,建立pwq和worker pool的链接
}

我们用schedule_work时只需要传入work_struct,并没有指定workqueue。是因为内核提供了一些系统级工作队列,开放给用户直接使用。

比如schedule_work就是直接使用了system_wq。这些工作队列也是在workqueue_init_early注册的。

void __init workqueue_init_early(void)
{
	system_wq = alloc_workqueue("events", 0, 0);
	system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
	system_long_wq = alloc_workqueue("events_long", 0, 0);
	system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
					    WQ_UNBOUND_MAX_ACTIVE);
	system_freezable_wq = alloc_workqueue("events_freezable",
					      WQ_FREEZABLE, 0);
	system_power_efficient_wq = alloc_workqueue("events_power_efficient",
					      WQ_POWER_EFFICIENT, 0);
	system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
					      WQ_FREEZABLE | WQ_POWER_EFFICIENT,
					      0);
}

我们最常用的是system_wq。内核按需申请了不同类型的workqueue,也有一些位置会专门使用这些workqueue。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/593185.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sql 中having和where区别

where 是用于筛选表中满足条件的行&#xff0c;不可以和聚类函数一起使用 having 是用于筛选满足条件的组 &#xff0c;可与聚合函数一起使用 所以having语句中不能使用select中定义的名字

QT:QT与操作系统

文章目录 信号槽与事件 信号槽与事件 在之前的信号槽中&#xff0c;已经有了一个基本的认识&#xff0c;那么对于QT中事件的理解其实就非常的类似&#xff0c;当用户进行某种操作的时候&#xff0c;就会触发事件&#xff0c;去执行一些对应的方法 QT对于事件又进行了封装&…

Lucene从入门到精通

**************************************************************************************************************************************************************************** 1、概述 【1】入门&#xff1a;作用、有点与缺点 【2】应用&#xff1a;索引、搜索、fie…

【软件开发规范篇】JAVA后端开发编程规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

Python数据分析案例43——Fama-French回归模型资产定价(三因子/五因子)

案例背景 最近看到要做三因子模型的同学还挺多的&#xff0c;就是所谓的Fama-French回归模型&#xff0c;也就是CAMP资本资产定价模型的升级版&#xff0c;然后后面还升级为了五因子模型。 看起来眼花缭乱&#xff0c;其实抛开金融资产定价的背景&#xff0c;从机器学习角度来…

04_jvm性能调优_并行收集器介绍

并行收集器&#xff08;此处也称为吞吐量收集器&#xff09;是类似于串行收集器的分代收集器。串行和并行收集器之间的主要区别在于并行收集器具有多个线程&#xff0c;用于加速垃圾回收过程。 通过命令行选项-XX:UseParallelGC 可启用并行收集器。默认情况下&#xff0c;使用…

消息队列与信号量(基本概念及操作接口介绍)

一、消息队列 基本概念 System V消息队列是Unix系统中一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;它允许进程互相发送和接收数据块&#xff08;消息&#xff09; 操作系统可以在内部申请一个消息队列&#xff0c;可以让不同的进程向消息队列中发送数据块&…

Linux Systemd基础教程

一、什么是systemd&#xff1f; systemd是Linux系统的一套基本构建模块。它提供了一个系统和服务管理器&#xff0c;作为PID 1运行并启动系统的其余部分。 systemd提供积极的并行化功能&#xff0c;使用套接字和D-Bus激活来启动服务&#xff0c;提供按需启动守护进程&#xf…

《自动机理论、语言和计算导论》阅读笔记:p352-P401

《自动机理论、语言和计算导论》学习第 12 天&#xff0c;p352-P401总结&#xff0c;总计 50 页。 一、技术总结 1.Turing Machine ™ 2.undecidability ​ a.Ld(the diagonalization language) 3.reduction p392, In general, if we have an algorithm to convert insta…

Git系列:config 配置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

面试中算法(最大公约数)

高效求出两个整数的最大公约数&#xff0c;要尽量优化算法的性能。 def getDiv(a,b):mamax(a,b)mimin(a,b)#判断能被整除if ma%mi0:return mi#递归return getDiv(ma%mi,mi)if __name__ __main__:# print(getDiv(10, 25))print(getDiv(1000, 50))没错&#xff0c;这确实是辗转…

C++笔试强训day14

目录 1.乒乓球框 2.组队竞赛 3.删除相邻数字的最⼤分数 1.乒乓球框 链接 哈希表直接秒了&#xff1a; #include <iostream> #include <string> using namespace std; int main() {string s1, s2;while (cin >> s1 >> s2) { // 未知组数的输⼊int h…

新芯计划(1)时钟资源——MMCM与PLL

系列文章目录 1、同步设计——亚稳态 文章目录 系列文章目录前言一、时钟管理资源二、MMCM与PLLMMCM内部结构&#xff1a;PLL内部结构:区别 前言 本节围绕时钟资源展开&#xff0c;主要描述和比较MMCM和PLL&#xff0c;若内容有误&#xff0c;欢迎和感谢各位指正 参考视频&am…

IoTDB 入门教程 基础篇③——基于Linux系统快速安装启动和上手

文章目录 一、前文二、下载三、解压四、上传五、启动六、执行七、停止八、参考 一、前文 IoTDB入门教程——导读 二、下载 下载二进制可运行程序&#xff1a;https://dlcdn.apache.org/iotdb/1.3.1/apache-iotdb-1.3.1-all-bin.zip 历史版本下载&#xff1a;https://archive.…

Mysql中索引的概念

索引相关概念 基础概念&#xff1a; 在MySQL中&#xff0c;索引是一种数据结构&#xff0c;用于加快数据库查询的速度和性能。索引可以帮助MySQL快速定位和访问表中的特定数据&#xff0c;就像书籍的索引一样&#xff0c;通过存储指向数据行的指针&#xff0c;可以快速…

《老相册》读后感

外面在下着瓢泼大雨&#xff0c;豆粒大的雨点打在窗户上&#xff0c;发出啪啪的巨响。这样的雨天&#xff0c;是不适宜外出的&#xff0c;最惬意的方式就是一个人待在宿舍里&#xff0c;打开一本书&#xff0c;慢慢地看&#xff0c;静静地想&#xff0c;让所有的烦恼融化在这雨…

二叉树的迭代遍历 | LeetCode 144. 二叉树的前序遍历、LeetCode 94. 二叉树的中序遍历、LeetCode 145. 二叉树的后序遍历

二叉树的前序遍历&#xff08;迭代法&#xff09; 1、题目 题目链接&#xff1a;144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#x…

Docker Compose 部署若依前后端分离版

准备一台服务器 本次使用虚拟机&#xff0c;虚拟机系统 Ubuntu20.04&#xff0c;内存 4G&#xff0c;4核。 确保虚拟机能连接互联网。 Ubuntu20.04 安装 Docker 添加 Docker 的官方 GPG key&#xff1a; sudo apt-get update sudo apt-get install ca-certificates curl su…

1850H-The Third Letter

题目链接&#xff1a;The Third Letter 本道题目就是带权并查集的模板题&#xff0c;但又好久没学忘了&#xff0c;再复习一遍。。。 路径压缩函数模板&#xff1a; int root(int x){if(pre[x]!x){int troot(pre[x]);d[x]d[pre[x]];pre[x]t;}return pre[x]; } 之后就模拟一…

eNSP-浮动静态路由配置

ip route-static 192.168.1.0 24 192.168.3.2 preference 60 #设置路由 目标网络地址 和 下一跳地址 preference值越大 优先级越低 一、搭建拓扑结构 二、主机配置 pc1 pc2 三、配置路由器 1.AR1路由器配置 <Huawei>sys #进入系统视图 [Huawei]int g0/0/0 #进入接…
最新文章