内核workqueue框架

news2024/12/25 1:20:10

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.coloradmin.cn/o/1643636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript的操作符运算符

前言&#xff1a; JavaScript的运算符与C/C一致 算数运算符&#xff1a; 算数运算符说明加-减*乘%除/取余 递增递减运算符&#xff1a; 运算符说明递增1-- 递减1 补充&#xff1a; 令a1&#xff0c;b1 运算a b ab12ab22ab--10a--b00 比较(关系)运算符&#xff1a; 运算…

服务器端集群优化-集群还是主从

7、服务器端集群优化-集群还是主从 集群虽然具备高可用特性&#xff0c;能实现自动故障恢复&#xff0c;但是如果使用不当&#xff0c;也会存在一些问题&#xff1a; 集群完整性问题集群带宽问题数据倾斜问题客户端性能问题命令的集群兼容性问题lua和事务问题 问题1、在Redi…

[Spring Cloud] (6)gateway整体加解密

文章目录 简述整体效果后端增加配置nacos增加配置GlobalConfig 添加请求整体解密拦截器DecryptionFilter添加响应整体解密拦截器EncryptionFilter 前端请求拦截器添加整体加密逻辑请求头中添加sessionId 响应拦截器添加整体解密逻辑 简述 本文网关gateway&#xff0c;微服务&a…

VsCode插件 -- Power Mode

一、安装插件 1. 首先在扩展市场里搜索 Power Mode 插件&#xff0c;如下图 二、配置插件 设置 点击小齿轮 打上勾 就可以了 第二种设置方法 1. 安装完成之后&#xff0c;使用快捷键 Ctrl Shift P 打开命令面板&#xff0c;在命令行中输入 settings.json &#xff0c; 选择首…

扩展学习|结合故事的力量和数字的力量:混合方法研究和混合研究综述

文献来源&#xff1a;Pluye, Pierre, and Quan Nha Hong. "Combining the power of stories and the power of numbers: mixed methods research and mixed studies reviews." Annual review of public health 35 (2014): 29-45. 文献获取&#xff1a;链接&#xff1…

【机器视觉】yolo-world-opencvsharp-.net4.8 C# 窗体应用程序

这段代码是基于 OpenCvSharp, OpenVinoSharp 和 .NET Framework 4.8 的 Windows Forms 应用程序。其主要目的是加载和编译机器学习模型&#xff0c;对输入数据进行推理&#xff0c;并显示结果。 下面是该程序的主要功能和方法的详细总结&#xff1a; 初始化 OpenVINO 运行时核心…

【微服务】配置管理

Nacos配置管理 配置管理配置共享配置热更新 配置管理 将微服务集群中常用&#xff0c;经常变化的配置都写到一个独立的配置文件微服务中进行统一管理 配置共享 在Nacos的界面当中进行配置管理&#xff0c;在配置列表中添加配置 比如各个服务中的jdbc的连接配置&#xff1a; …

【AI工具声音克隆】——OpenVoice一键部署modelScope一键使用

一、声音/音色克隆简介 声音或音色克隆的原理实现步骤主要基于深度学习技术&#xff0c;特别是语音合成和生成模型。以下是声音/音色克隆的大致实现步骤&#xff1a; 数据收集&#xff1a; 收集语音数据&#xff0c;作为模型的训练样本。数据应尽可能多样化&#xff0c;包括不…

基于stm32的USB虚拟U盘+FATFS+W25Q64

基于stm32的USB虚拟U盘FATFSW25Q64 本文目标&#xff1a;基于stm32的USB虚拟U盘FATFSW25Q64 按照本文的描述&#xff0c;简单跑通USB的MSC类来进行简单交互。 先决条件&#xff1a;拥有C语言基础&#xff0c;装有编译和集成的开发环境&#xff0c;比如&#xff1a;Keil uVis…

如果还有机会再跟芒格共度一天,巴菲特想做什么?

这是芒格因离世而缺席的第一年&#xff0c;但他的身影却无处不在。问答环节&#xff0c;巴菲特会不小心脱口而出“查理&#xff0c;该你回答了”&#xff0c;他也称&#xff0c;与芒格在一起的时间比独处要快乐。 可以说&#xff0c;虽然99岁的芒格因离世而缺席了2024年伯克希…

记录几种排序算法

十种常见排序算法可以分类两大类别&#xff1a;比较类排序和非比较类排序。 常见的快速排序、归并排序、堆排序以及冒泡排序等都属于比较类排序算法。比较类排序是通过比较来决定元素间的相对次序&#xff0c;其时间复杂度不能突破 O(nlogn)。在冒泡排序之类的排序中&…

Python基础详解一

一&#xff0c;print打印 print("hello word") print(hello word) 双引号和单引号都可以 二&#xff0c;数据类型 Python中常用的有6种值的类型 输出类型信息 print(type(11)) print(type("22")) print(type(22.2)) <class int> <class str&…

Mybatis进阶2

Mybatis进阶1-CSDN博客 Mybatis入门-CSDN博客 Mybatis入门2-CSDN博客 我们接下来要学习Mybatis的高级查询 我们先在数据库中准备我们需要的数据表 teacher表 课程表&#xff1a;与教师表是一对多的关系&#xff0c;所以有一个外键字段 学生表 由于学生表和课程表是多对多的…

鸿蒙ArkTs开发,仿抖音个人中心header 下拉放大

如果是iOS 或者android 上实现&#xff0c;可以用Scollview 的contentOffset 来实现&#xff0c;然而在鸿蒙ets中该如何实现&#xff1f;废话不多说开始撸代码 第一步、实现一个header // 创建header&#xff0c;准备一张背景图片BuilderHeaderBuilder(){Column() {Row() {Ima…

算法入门<一>:C++各种排序算法详解及示例源码

1、排序算法 排序算法&#xff08;sorting algorithm&#xff09;用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更高效地查找、分析和处理。 1.1 评价维度 运行效率&#xff1a;我们期望排序算法的时间复杂度尽量低&#xf…

C语言学习【C语言基本数据类型】

C语言学习【C语言基本数据类型】 整数溢出 /* 整数溢出 */ #include "stdio.h" /* Last Modified Time: 2024-05-05 17:53:49 */int main(void) {int i 2147483647;unsigned int j 4294967295;printf("%d %d %d\n", i, i1, i2);printf("%u %u %u\…

【数据结构初阶】直接插入排序

最近浅学了直接插入排序&#xff0c;写个博客做笔记&#xff01;笔记功能除外若能对读者老爷有所帮助最好不过了&#xff01; 直接插入排序是插入排序的一种&#xff0c;那么介绍直接插入排序之前先介绍一下常见的排序算法&#xff01; 目录 1.常见的排序算法 2.直接插入排…

500行代码实现贪吃蛇(1)

文章目录 目录1. Win32 API 介绍1.1 Win32 API1.2 控制台程序&#xff08;Console&#xff09;1.3 控制台屏幕上的坐标COORD1.4 [GetStdHandle](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle)1.5 [GetConsoleCursorInfo](https://learn.microsoft.com/zh-c…

项目经理【人】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 【环境】任务 【环境】绩效 【人】概述 【人】原则 一、共创模式 1.1 共创模式 二、干系人的影响力强度和态度 2.1 干系人影响力 2.2 干系人态度 2.3 干系人管理 三、干系人权力…

Java17 --- SpringCloud之Gateway

目录 一、Gateway网关创建 1.1、创建微服务子工程9527及配置和依赖 1.1.1、pom依赖 1.1.2、yml配置 1.1.3、主启动类并测试入驻consul 二、实现路由映射 2.1、服务8001新增测试代码 2.2、修改9527服务yml配置文件 2.3、远程调用接口加gateway 2.3.1、新增80服务测…