Linux内核源码分析-进程调度(五)-组调度

news2025/1/10 3:02:14

出现的背景

总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢,假设多用户计算机系统每个用户的所有任务划分到一个分组中,A用户90个任务,而B用户只有10个任务(这100个任务假设都是优先级一样的普通进程),在CPU完全跑满的情况下,那么A用户将占90%的CPU时间,而B用户只占到了10%的CPU时间,这对B用户显然是不公平的。再或者同一个用户,既想-j64快速编译,又不想被编译任务影响使用体验,也可将编译任务设置到对应分组中,限制其CPU资源。组调度的引入,正是解决此问题的,即可以将任务分配给不同的任务组来实现CPU资源的合理利用。

我们举个例子来进一步阐述一下上面这段话。现在的计算机都支持多用户登录,如果一台计算机被两个用户A和B同时使用,假设用户A运行8个进程,用户B运行2个进程,按照之前对CFS的理解,CFS的调度粒度都是一个个的进程,我们认为用户A获得80%的cpu时间,用户B获得20%的cpu时间。随着用户A不停的增加运行进程数量,用户B可使用的CPU时间越来越少,这就完全不符合我们的预期了。因此,我们引入了组调度(Group Scheduling)的概念。我们将一个用户的任务放在同一个任务组中,这样用户A和用户B各获得50%cpu时间。用户A中的每个进程获得6.25%(50% / 8)cpu时间,用户B的每个进程获得25%(50% / 2)cpu时间,这样的结果是符合我们预期的。

task group

前面我们说过,CFS调度器管理的是调度实体。每一个进程通过task_struct描述,task_struct对应一个调度实体sched_entity。针对task_struct对应的调度实体,我们称之为task se。现在我们引入任务组的概念,我们使用task_group描述一个任务组,这个组管理组内所有的进程。因为CFS就绪队列管理的单位是调度实体,因此,task_group也脱离不了sched_entity,即task_group也映射为一个调度实体,我们称这种调度实体为group se。

/* Task group related information */
/*
 * 组调度,Linux支持将任务分组来对CPU资源进行分配管理。
 * 该结构中为系统中的每个CPU都分配了struct sched_entity调度实体和struct cfs_rq运行队列,
 * 其中struct sched_entity用于参与CFS的调度。
 */
struct task_group {
	struct cgroup_subsys_state css;

#ifdef CONFIG_FAIR_GROUP_SCHED
	/* schedulable entities of this group on each CPU */
	/*
	 * 所以se代表cpu数量个group se。
	 * 在alloc_fair_sched_group()中进程初始化及分配内存。
	 */
	struct sched_entity	**se;
	/* runqueue "owned" by this group on each CPU */
	struct cfs_rq		**cfs_rq;  // 每一个se[cpu]对应一个group cfs_rq。
	unsigned long		shares;  // 整个调度组的权重。

#ifdef	CONFIG_SMP
	/*
	 * load_avg can be heavily contended at clock tick time, so put
	 * it in its own cacheline separated from the fields above which
	 * will also be accessed at each tick.
	 */
	atomic_long_t		load_avg ____cacheline_aligned;  // 整个tg的负载贡献总和。
#endif
#endif

	struct cfs_bandwidth	cfs_bandwidth;  // cpu带宽控制
};
struct task_group root_task_group;

task group与cpu rq的cfs_rq的关系

在这里插入图片描述
现在我们来详细说一下这张图:
系统启动后默认有一个root_task_group,管理系统中最顶层CFS就绪队列cfs_rq(即cpu rq对应的CFS就绪队列),对应上图即为root tg的cfs_rq[cpu1]和cfs_rq[cpu2]。
cpu2 rq的cfs_rq队列包含9个task se和一个group se,此group se又包含9个task se,且此group se会对应一个task group,即有几层group se,就会有几层tg(不包含root tg)。
tg的parent指向了上一级tg,se维护了cpu数量的group se,cfs_rq[cpu]维护了对应的group se下属的se。
cfs_rq的nr_running:就绪队列上调度实体的个数(包括task se和group se,比如上图中的cpu rq的cfs_rq的task se个数为9,group se个数为1,则nr_running为10)。
cfs_rq的h_nr_running:就绪队列上真实的调度实体的个数(比如上图中的cpu rq的cfs_rq的task se个数为9,group se个数为1(group se中包含9个task se,则),则h_nr_running为18)。

组进程调度

组内的进程该如何调度呢?通过上面的分析,我们可以通过cpu rq的CFS就绪队列(也称根就绪队列)一层层往下遍历选择合适进程。例如,先从根就绪队列选择适合运行的group se,然后找到对应的group cfs_rq,再从group cfs_rq上选择task se。在CFS调度类中,选择进程的函数是pick_next_task_fair()。

struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
	struct cfs_rq *cfs_rq = &rq->cfs;  // 从per-cpu rq中获取cfs就绪队列
	struct sched_entity *se;
	struct task_struct *p;
	int new_tasks;

again:
	if (!sched_fair_runnable(rq))  // 判断per-cpu rq的cfs就绪队列上是否还有调度实体,若无则选择idle调度器。
		goto idle;

#ifdef CONFIG_FAIR_GROUP_SCHED
	if (!prev || prev->sched_class != &fair_sched_class)
		goto simple;

	/*
	 * Because of the set_next_buddy() in dequeue_task_fair() it is rather
	 * likely that a next task is from the same cgroup as the current.
	 *
	 * Therefore attempt to avoid putting and setting the entire cgroup
	 * hierarchy, only change the part that actually changes.
	 */

	do {
		struct sched_entity *curr = cfs_rq->curr;  // 获取当前正在cpu上运行的调度实体。

		/*
		 * Since we got here without doing put_prev_entity() we also
		 * have to consider cfs_rq->curr. If it is still a runnable
		 * entity, update_curr() will update its vruntime, otherwise
		 * forget we've ever seen it.
		 */
		if (curr) {
			if (curr->on_rq)
				update_curr(cfs_rq);
			else
				curr = NULL;

			/*
			 * This call to check_cfs_rq_runtime() will do the
			 * throttle and dequeue its entity in the parent(s).
			 * Therefore the nr_running test will indeed
			 * be correct.
			 */
			if (unlikely(check_cfs_rq_runtime(cfs_rq))) {
				cfs_rq = &rq->cfs;

				if (!cfs_rq->nr_running)
					goto idle;

				goto simple;
			}
		}

		se = pick_next_entity(cfs_rq, curr);  // 从rq的cfs队列中选取虚拟运行时间最小的调度实体。
		cfs_rq = group_cfs_rq(se);  // 返回se->my_q成员(即获取se的就绪队列)。如果是task se,则返回NULL;如果是group se,则返回group se中的csf_rq就绪队列。
	} while (cfs_rq);  // 如果是group se,则需要继续在group se中的cfs_rq上选择虚拟运行时间最小的se,直到找到可以最小虚拟运行时间的task se。

	p = task_of(se);  // 获取调度实体se对应的进程p。

	return p;
}

组进程抢占

组进程抢占即为周期性调度,会调用task_tick_fair()函数。

/*
 * 周期性调度
 */
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &curr->se;  // 

	for_each_sched_entity(se) {  // for_each_sched_entity(se)是一个宏定义for (; se; se = se->parent),顺着se的parent链表往上走。更新子调度实体的同时必须更新父调度实体
		cfs_rq = cfs_rq_of(se);  // 获取se所属的cfs_rq;
		entity_tick(cfs_rq, se, queued);  // 完成周期性调度
	}
}

entity_tick的函数实现可以参考前面的文章。
entity_tick()函数继续调用check_preempt_tick()函数,这部分在之前的文章已经说过了。check_preempt_tick()函数会根据满足抢占当前进程的条件下设置TIF_NEED_RESCHED标志位。满足抢占条件也很简单,只要顺着se->parent这条链表便利下去,如果有一个se运行时间超过分配限额时间就需要重新调度。

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

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

相关文章

物联网手势控制小车所遇问题与解决方案

LCD1602无法显示问题 问题描述:按照开源社区教程完成LCD1602驱动显示程序的编写,成功点亮屏幕,开启背光,但无法观察到显示数据。 分析过程与解决方案: 1.是否IIC地址不对 使用以下代码扫描IIC总线上的设备&#xf…

Go学习圣经:队列削峰+批量写入 超高并发原理和实操

说在前面: 本文是《Go学习圣经》 的第二部分。 第一部分请参见:Go学习圣经:0基础精通GO开发与高并发架构(1) 现在拿到offer超级难,甚至连面试电话,一个都搞不到。 尼恩的技术社群中&#xf…

K近邻算法实现红酒数据集分类

目录 1. 作者介绍2. K近邻算法介绍2.1 K基本原理2.2 算法优缺点 3. KNN红酒数据集分类实验3.1 获取红酒数据集3.2 KNN算法3.3 完整代码 4. 问题分析参考链接(可供参考的链接和引用文献) 1. 作者介绍 路治东,男,西安工程大学电子信…

面试问题汇总

最近面试了几家公司,对问到的问题汇总一下。 Unity 是左手坐标系还是右手坐标系? 这个题靠记忆答的答错了,是左手坐标系。 大拇指指的方向是X轴,食指指的方向是Y轴方向,中指指的方向Z轴方向。 场景中游戏物体Activity为false,G…

C语言字符串函数和内存函数的介绍与模拟实现

0.前言 C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数. 1.字符串函数介绍与模拟实现 C语言本身就带有一些库函数,所…

研发工程师玩转Kubernetes——CPU配额

在一个Pod中,可以有多个容器,比如一个主要业务容器和若干辅助业务容器。如果辅助业务容器内程序有问题,导致占用了大量的CPU资源,进而影响了主要业务容器的执行效率,那就需要进行干涉了。本节我们将使用“资源配额”来…

Edgedetect

边缘检测,检测上升沿 对于 8 位矢量中的每个位,检测输入信号何时从一个时钟周期中的 0 变为下一个时钟周期的 1(类似于正边沿检测)。输出位应在发生 0 到 1 转换后设置周期。 以下是一些示例。为清楚起见,in[1] 和 pe…

Jenkins+GitLab+Docker搭建前端自动化构建镜像容器部署(无本地证书,映射版本)

前言 🚀 需提前安装环境及知识点: 1、Docker搭建及基础操作 2、DockerFile文件描述 3、Jenkins搭建及基础点 🚀 目的: 将我们的前端项目打包成一个镜像容器并自动发布部署,可供随时pull访问 一、手动部署镜像及容器 1…

【Linux】线程分离 | 线程库 | C++调用线程 | 线程局部存储

文章目录 1. 线程分离1. 为什么要线程分离?2. 具体使用3. 为什么有时候分离在调用join 会正常运行? 2. 如何理解线程库?如何理解 先描述 在组织? 3. C中使用多线程4. 线程局部存储局部变量全局变量 1. 线程分离 1. 为什么要线程分…

服务器虚拟化部署

服务器虚拟化部署 1、背景2、目的3、环境4、部署4.1、部署VMware ESXi4.1.1、准备工作4.1.2、部署ESXi4.1.3、配置ESXi4.1.4 、部署虚拟机 1、背景 项目上利旧9台服务器,项目需要使用15台服务器,外购已经没有项目硬件采购预算,只能从目前的…

自定义HikariCP连接池

文章目录 一、简介1、概述2、地址 二、配置参数1、Hikari原生参数2、Springboot中参数 三、springboot中使用四、自定义数据源1、各模块2、完整代码3、多数据源 五、多数据源dynamic中使用1、简介2、引入依赖3、参数配置 六、XMind整理 一、简介 1、概述 官方解释&#xff1a…

zabbix 自动发现与自动注册、部署 zabbix 代理服务器及部署 Zabbix 高可用集群

目录 一、zabbix 自动发现二、zabbix 自动注册(对于 agent2 是主动模式)三、部署 zabbix 代理服务器四、部署 Zabbix 高可用集群五、Zabbix 监控 Windows 系统六、Zabbix 监控 java 应用七、Zabbix 监控 SNMP 一、zabbix 自动发现 zabbix 自动发现&…

Nautilus Chain开启全球行,普及Layer3概念加速其采用

在去年,在 2022 年,Vitalik 进一步提出了 Layer3 的概念与早期形态,期盼弥补目前链体系存在的不足,并为 Layer3 提出了三大目标,即Layer2 用于扩展,Layer3 用于定制功能,如隐私;Laye…

【C++】哈希表特性总结及unordered_map和unordered_set的模拟实现

✍作者:阿润菜菜 📖专栏:C 文章目录 前言一、哈希表的特性 - 哈希函数和哈希冲突1 哈希函数2. 哈希冲突 二、闭散列的实现 -- 开放地址法1. 定义数据结构2.insert()3.Find()4. Erase()5.仿函数处理key值不能取模无法映射 --- BKDRHash 三、开…

【Linux系列P4】Linux需要什么?编辑器?软件包?一文帮你了解掌握 [yum][vim]———基础开发工具篇

前言 大家好,这里是YY的Linux系列part4;本章主要内容面向接触过Linux的老铁,主要内容含【学习yum工具,进行软件安装】【拓展yum源安装】【掌握vim编辑器使用,基本命令】【命令集】【懒人配置文件安装教程】 在下一章节…

Java高并发编程—可见性与有序性原理

原子性、可见性和有序性是并发编程所面临的三大问题。 Java通过CAS操作已解决了并发编程中的原子性问题,本章为大家介绍Java如何解决剩余的另外两个问题——可见性和有序性。 CPU物理缓存结构 由于CPU的运算速度比主存(物理内存)的存取速度…

Android系统原理性问题分析 - 多路并发情况下的C/S模型

声明 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下。Android系统中很多地方都采用了I/O多路复用的机制,为了引出I/O多路复用机制,先来分析多路并发情况下的C/S模型。此篇参考一些博客和书籍,代码基于A…

C++条件变量condition_variable

一、问题 假设没有条件变量,对于一个生产者消费者问题,消费线程在得知队列中没有产品时,将阻塞自己。生产者线程可以给队列中放入产品,但是没有办法激活消费者线程,而消费者线程处于阻塞状态也没有办法自己激活自己。…

RocketMQ 领域模型概述

本文为您介绍 Apache RocketMQ 的领域模型。 Apache RocketMQ 是一款典型的分布式架构下的中间件产品,使用异步通信方式和发布订阅的消息传输模型。通信方式和传输模型的具体说明,请参见下文通信方式介绍和消息传输模型介绍。 Apache RocketMQ 产品具备…

IOS开发指南之自定义TableViewCell使用

演示效果: 1.自定义TableViewCell创建 File->new->File... 在iOS模板中选择Empty来创建一个空的XIB文件,然后点击下一步 输入XIB文件名Cell,然后点击Create创建 创建XIB文件成功后如下: 同时按钮Shift+command+L弹出库,然后输入 table筛选,选择Table View Cell 拖到下…