深入讲解CFS组调度!(上)

news2024/11/29 0:42:58

注:本文缩写说明

一、CFS组调度简介

1.1. 存在的原因

总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢,比如多用户计算机系统每个用户的所有任务划分到一个分组中,A用户90个相同任务,而B用户只有10个相同任务,在CPU完全跑满的情况下,那么A用户将占90%的CPU时间,而B用户只占到了10%的CPU时间,这对B用户显然是不公平的。再或者同一个用户,既想-j64快速编译,又不想被编译任务影响使用体验,也可将编译任务设置到对应分组中,限制其CPU资源。

1.2. 手机设备上的分组状态

/dev/cpuctl 目录使用struct task_group root_task_group 表示。其下的每一层级的子目录都抽象为一个task_group结构。有几个需要注意的点:

  1. 根组下的cpu.shares 文件默认值是1024,而且不支持设置。根组的负载也不会更新。
  2. 根组也是一个task group,根组下的任务也是被分过组的,不会再属于其它组。
  3. 内核线程默认在根组下,其直接从根cfs_rq上分配时间片,有非常大的优势,若是其一直跑,那在trace上看就几乎就是”一直跑”,比如常见的kswapd内核线程。
  4. 默认cpu.shares配置下,若全是nice=0的任务,且只考虑单核的情况下,根组下的每个任务在完全满载情况下能得到的时间片等于其它分组下所有任务能分配到的时间片的总和。

注意:task和task group都是通过权重来分配时间片的,但是task的权重来自其优先级,而task group的权重则来自与其cgroup目录下cpu.shares文件设置的值。使能组调度后,看任务分得的时间片,就不能单看其prio对应的权重了,还要看其task group分得的权重和本group中其它任务的运行情况。

二、任务的task group分组

CFS组调度功能主要是通过对任务进行分组体现出来的,一个分组由一个struct task_group来表示。

2.1. 如何设置分组

task group分组配置接口由cpu cgroup子系统通过cgroup目录层次结构导出到用户空间。

如何从task group中移除一个任务呢,没有办法直接移除的,在cgroup语义下,一个任务某一时刻必须属于一个task group,只有通过将其echo到其它分组中才能将其从当前分组中移除。

2.2. Android中如何设置分组

Process.java中向其它模块提供 setProcessGroup(int pid, int group) 将pid进程设置进group参数指定的分组中,供其它模块进行调用设置。比如OomAdjuster.java 中将任务切前/后台分别调用传参group=THREAD_GROUP_TOP_APP/THREAD_GROUP_BACKGROUND。

libprocessgroup 中提供了一个名为 task_profiles.json 的配置文件,它里面 AggregateProfiles 聚合属性字段配置了上层设置下来后的对应的行为。比如 THREAD_GROUP_TOP_APP 对应的聚合属性为 SCHED_SP_TOP_APP,其中的MaxPerformance属性对应的行为就是加入到cpu top-app分组。

”MaxPerformance”属性的配置可读性非常强,可以看出是加入到cpu子系统的top-app分组中。

2.3. Android中设置为TOP-APP分组,对cgroup设置了什么

因为有多个cgroup子系统,除了我们正在讲的CFS组调度依附的cpu cgroup子系统外,还有cpuset cgroup子系统(限制任务可运行的CPU和可使用的内存节点),blkio cgroup子系统(限制进程的块设备io),freezer cgroup子系统(提供进程冻结功能)等。上层配置分组下来可能不只切一个cgroup,具体切了哪些子系统体现在集合属性 AggregateProfiles 的数组成员上,比如上例中另外两个属性对应的行为分别是加入blkio子系统的根组和将任务的timer_slack_ns(一个平衡hrtimer定时唤醒及时性与功耗的参数)设置为50000ns。

2.4. 服务启动后就放在指定分组

在启动服务的时候使用 task_profiles进行配置,举例。

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

三、内核实现概述

上面我们讨论了对task group分组的配置,本节开始将进入到内核中,了解其实现。

3.1. 相关功能依赖关系

内核相关CONFIG_*依赖如下:

图1:

CGROUP提供cgroup目录层次结构的功能;CGROUP_SCHED提供cpu cgroup目录层次结构(如 /dev/cpuctl/top-app),并为每个cpu cgroup目录提供task group的概念;FAIR_GROUP_SCHED基于cpu cgroup提供的task group提供CFS任务组调度功能。图1中灰色的虚线框图是Android手机内核中默认不使能的。

3.2. task group数据结构框图

如下图2所示,展示了一个task group在内核中维护的主要数据结构的框图,对照着图下面概念更容易理解:

  1. 由于不能确定一个分组内的任务跑在哪个CPU上,因此一个task group在每个CPU上都维护了一个group cfs_rq,由于task group也要参与调度(要先选中task group才能选中其group cfs_rq上的任务) ,因此在每个CPU上也都维护了一个group se。
  2. task se的my_q成员为NULL,而group se的my_q成员指向其对应的group cfs_rq,其分组在本CPU上的就绪任务就挂在这个group cfs_rq上。
  3. task group可以嵌套,由 parent/siblings/children 成员构成一个倒立树状层次结构,根task group的parent指向NULL。
  4. 所有CPU的根cfs_rq属于root task group。

图2:

注:画图比较麻烦,此图是借鉴蜗窝的。

四、相关结构体

4.1. struct task_group

一个 struct task_group 就表示一个cpu cgroup分组。在使能FAIR_GROUP_SCHED的情况下,一个 struct task_group 就表示CFS组调度中的一个任务组。

css: 该task group对应的cgroup状态信息,通过它依附到cgroup目录层次结构中。

se: group se,是个数组指针,数组大小为CPU的个数。因为一个task group中有多个任务,且可以跑在所有CPU上,因此需要每个CPU上都要有本task group的一个 se。

cfs_rq: group se的cfs_rq, 是本task group的在各个CPU上的cfs_rq,也是个数组指针,数组大小为CPU的个数。当本task group的任务就绪后就挂在这个cfs_rq上。它在各个CPU上对应的指针和task group在各个CPU上的se->my_q具有相同指向,见 init_tg_cfs_entry().

shares: task group的权重,默认为scale_up(1024)。和task se的权重类似,值越大task group能获取的CPU时间片就越多。但是和task 不同的是,task group 在每个CPU上都有一个group se,因此需要按照一定规则分配给各CPU上的group se, 下面会讲解分配规则。

load_avg: 此task group的负载,而且只是一个load_avg 变量(不像se和cfs_rq是一个结构),下面会对其进行讲解。注意它不是per-cpu的,此task group的任务在各个CPU上进行更新时都会更新它,因此需要注意对性能的影响。

parent/siblings/children: 构成task group的层次结构。

内核中有个全局变量 struct task_group root_task_group, 表示根组。其cfs_rq[]就是各个cpu的cfs_rq, 其se[]都为NULL。其权重不允许被设置,见 sched_group_set_shares()。负载也不会被更新,见 update_tg_load_avg()。

系统中的所有task group 结构都会被添加到task_groups链表上,在CFS带宽控制时使用。

初学者可能会混淆组调度和调度组这两个概念,以及 struct task_group 与struct sched_group 的区别。组调度对应struct task_group,用来描述一组任务,主要用于util uclamp(对一组任务的算力需求进行钳制)和CPU资源使用限制,也是本文中要讲解的内容。调度组对应 struct sched_group,是CPU拓扑结构sched_domain中的概念,用来描述一个CPU(MC层级)/Cluster(DIE层级)的属性,主要用于选核和负载均衡。

4.2. struct sched_entity

一个sched_entity 既可以表示一个task se, 又可以表示一个group se。下面主要介绍使能组调度后新增的一些成员。

load: 表示se的权重,对于gse, 新建时初始化为NICE_0_LOAD, 见init_tg_cfs_entry()。

depth: 表示task group的嵌套深度,根组下的se的深度为0,每嵌套深一层就加1。比如/dev/cpuctl目录下有个tg1目录,tg1目录下又有一个tg2目录,tg1对应的group se的深度为0,tg1下的task se的深度是1,tg2下的task se的深度是2。更新位置见 init_tg_cfs_entry()/attach_entity_cfs_rq()。

parent: 指向父se节点, 父子se节点都是对应同一cpu的。根组下任务的指向为NULL。

cfs_rq: 该se挂载到的cfs_rq。对于根组下的任务指向rq的cfs_rq,非根组的任务指向其parent->my_rq,见init_tg_cfs_entry()。

my_q: 该se的cfs_rq, 只有group se才有cfs_rq,task se的为NULL,entity_is_task()宏通过这个成员来判断是task se还是group se。

runnable_weight: 缓存 gse->my_q->h_nr_running 的值,在计算gse的runnable负载时使用。

avg: se的负载,对于tse会初始化为其权重(创建时假设其负载很高),而gse则会初始化为0,见init_entity_runnable_average()。task se的和group se的有一定区别,下面第五章会进行讲解。

4.3. struct cfs_rq

struct cfs_rq 既可以表示per-cpu的CFS就绪队列,又可以用来表示gse的my_q队列。下面列出对组调度比较关键的一些成员进行讲解。

load: 表示cfs_rq的权重,无论是根cfs_rq还是grq,这里的权重都等于其队列上挂的所有任务的权重之和。

nr_running: 当前层级下 task se 和 group se的个数和。

h_nr_running: 当前层级以及所有子层级下task se的个数和,不包括group se。

avg: cfs_rq的负载。下面将对比task se、group se、cfs_rq讲解负载。

removed: 当一个任务退出或者唤醒后迁移到到其他cpu上的时候,原来CPU的cfs rq上需要移除该任务带来的负载。这个移除动作会先把移除的负载记录在这个removed成员中,在下次调用update_cfs_rq_load_avg()更新cfs_rq负载时再移除。nr表示要移除的se的个数,*_avg则表示要移除的各类负载之和。

tg_load_avg_contrib: 是对 grq->avg.load_avg 的一个缓存,表示当前grq的load负载对tg的贡献值。用于在更新 tg->load_avg 的同时降低对 tg->load_avg 的访问次数。在计算gse从tg分得的权重配额时的近似算法中也有用到,见 calc_group_shares()/update_tg_load_avg()。

propagate: 标记是否有负载需要向上层传播。下面7.3节会进行讲解。

prop_runnable_sum: 在负载沿着task group层级结构向上层传播的时候,表示要上传的tse/gse的load_sum值。

h_load: 层次负载hierarchy load,表示本层cfs_rq的load_avg对CPU的load_avg的贡献值,主要在负载均衡路径中使用。下面会对其进行讲解。

last_h_load_update: 表示上一次更新h_load的时间点(单位jiffies)。

h_load_next: 指向子gse,为了获取任务的hierarchy load(task_h_load函数),需要从顶层cfs向下,依次更新各个level的cfs rq的h_load。因此,这里的h_load_next就是为了形成一个从顶层cfs rq到底层cfs rq的cfs rq--se--cfs rq--se的关系链。

rq: 使能组调度后才加的这个成员,若没有使能组调度的话,cfs_rq就是rq的一个成员,使用 container_of进行路由,使能组调度后,增加了一个rq成员进行cfs_rq到rq的路由。

on_list/leaf_cfs_rq_list: 尝试将叶cfs_rq串联起来,在CFS负载均衡和带宽控制相关逻辑中使用。

tg: 该cfs_rq隶属的task group。

五、task group权重

task group的权重使用struct task_group 的 shares 成员来表示,默认值是scale_load(1024)。可以通过cgroup目录下的cpu.shares文件进行读写,echo weight > cpu.shares 就是将task group权重配置为weight,保存到shares 成员变量中的值是scale_load(weight)。root_task_group不支持设置权重。

不同task group的权重大小表示系统CPU跑满后,哪个task group组可以跑多一些,哪个task group组要跑的少一些。

5.1. gse的权重

task group在每个CPU上都有一个group se,那么就需要将task group的权重 tg->shares 按照一定的规则分配到各个gse上。规则就是公式(1):

* tg->weight * grq->load.weight

* ge->load.weight = ----------------------------------------- (1)

* \Sum grq->load.weight

其中 tg->weight 就是tg->shares, grq->load.weight 表示tg在各个CPU上的grq的权重。也就是每个gse根据其cfs_rq的权重比例来分配tg的权重。cfs_rq的权重等于其上挂载的任务的权重之和。假设tg的权重是1024,系统中只有2个CPU,因此有两个gse, 若其grq上任务状态如下如下图3,则gse[0]分得的权重为 1024 * (1024+2048+3072)/(1024+2048+3072+1024+1024) = 768;gse[1]分得的权重为 1024 * (1024+1024)/(1024+2048+3072+1024+1024) = 256。

图3:

gse的权重更新函数为 update_cfs_group(),下面看其具体实现:

tg的权重向gse[X]的分配动作是在 calc_group_shares() 中完成的。

公式(1)中使用到 \Sum grq->load.weight,也就是说一个gse权重的更新需要访问各个CPU上的grq,锁竞争代价比较高,因此进行了一系列的近似计算。

首先进行替换:

* grq->load.weight --> grq->avg.load_avg (2)

然后得到:

* tg->weight * grq->avg.load_avg

* ge->load.weight = ---------------------------------------- (3)

* tg->load_avg

*

* Where: tg->load_avg ~= \Sum grq->avg.load_avg

由于cfs_rq->avg.load_avg = cfs_rq->avg.load_sum/divider。而 cfs_rq->avg.load_sum 等于 cfs_rq->load.weight 乘以非idle状态下的几何级数。这个近似是在tg的每个CPU上的grq的非idle状态的时间级数是相同的前提下才严格相等的。也就是说tg的任务在各个CPU上的运行状态越一致,越接近这个近似值。

task group 空闲的情况下,启动一个任务。grq->avg.load_avg 需要时间来建立,在建立时间这种特殊情况下公式1简化为:

* tg->weight * grq->load.weight

* ge->load.weight = --------------------------------------- = tg->weight (4)

* grp->load.weight

相当于一个单核系统下的状态了。为了让公式(3)在这种特殊情况下更贴近与公式(4),又做了一次近似,得到:

* tg->weight * grq->load.weight

* ge->load.weight = -------------------------------------------------------------------- (5)

* tg->load_avg - grq->avg.load_avg + grq->load.weight

但是因为grq上没有任务时,grq->load.weight 可以下降到 0,导致除以零,需要使用 grq->avg.load_avg作为它的下限,然后给出:

* tg->weight * grq->load.weight

* ge->load.weight = ------------------------------------------ (6)

* tg_load_avg'

*

* 其中:

* tg_load_avg' = tg->load_avg - grq->avg.load_avg + max(grq->load.weight, grq->avg.load_avg)

max(grq->load.weight, grq->avg.load_avg) 一般都是取grq->load.weight,因为只有grq上一直有任务running+runnable才会趋近于grq->load.weight。

calc_group_shares() 函数是通过公式(6)近似计算各个gse分得的权重:

由于tg中的每个任务都对gse的权重有贡献,因此grq上任务个数变更时都要更新gse的权重,近似过程中使用到了se的负载,在entity_tick()中也进行了一次更新。调用路径:

5.2. gse上每个tse分到的权重

任务组中的任务也是按其权重比例分配gse的权重。如上图2中gse[0]的grq上挂的3个任务,tse1分得的权重就是7681024/(1024+2048+3072)=128, tse2分得的权重就是7682048/(1024+2048+3072)=256, tse3分得的权重就是768*3072/(1024+2048+3072)=384。

在tg中的任务分配tg分得的时间片的时候,会使用到这个按比例分得的权重。分组嵌套的越深,能按比例分得的权重就越小,由此可见,在高负载时task group中的任务是不利于分配时间片的。

文章篇幅过长,下文继续讲解!

 

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

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

相关文章

NIO蔚来 面试——IP地址你了解多少?

目录 前言 1、IP地址 1.1、什么是IP地址 1.2、IP地址的格式 1.2.1、32位二进制数表示IP地址,够用吗? 1.3、IP地址的组成 1.4、为什么会出现IPv6 1.4.1、为什么IPv6还没有大量普及呢? 1.5、子网掩码 1.6、特殊的IP地址 2、路由选择 …

微信小程序 之 云开发

一、概念1. 传统开发模式2. 新开发模式 ( 云开发模式 )3. 传统、云开发的模式对比4. 传统、云开发的项目流程对比5. 云开发的定位1. 个人的项目或者想法,不想开发服务器,直接使用云开发2. 某些公司的小程序项目是使用云开发的,但是不多&#…

Python自动化测试之登录脚本

登录脚本环境准备1、安装selenium模块2、安装浏览器驱动器代码1、登录代码2、xpath定位元素标签环境准备 前提已经安装好python、pycharm,配置了对应的环境变量。 1、安装selenium模块 文件–>设置—>项目:script---->python解释器---->s…

Spring自动装配的底层逻辑

Spring是如何自动装配Bean的?看源码一些自己的理解,如有错漏,请指正 使用Spring之前我们要先去web.xml中设置一下Spring的配置文件,在Spring的配置文件中,是通过component-scan扫描器去扫描base-package底下所有的类装…

【基础算法】哈希表(拉链法)

🌹作者:云小逸 📝个人主页:云小逸的主页 📝Github:云小逸的Github 🤟motto:要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前…

【C++的OpenCV】第四课-OpenCV图像常用操作(一):Mat对象深化学习、灰度、ROI

我们开始图像处理的基本操作的了解一、图像对象本身的加深学习1.1 Mat对象和ROI1.1.1 创建一个明确的Mat对象1.1.2 感兴趣的区域ROI二、图像的灰度处理2.1 概念2.2 cvtColor()函数2.3 示例一、图像对象本身的加深学习 1.1 Mat对象和ROI 这是一个技术经验的浅尝,所以…

什么是 CSAT?这份客户满意度流程指南请查收

什么是 CSAT?如何计算我的客户满意度分数?大中型公司应该熟悉这些术语。以下文章旨在教您有关客户满意度流程的所有内容 - 基本的CSAT概念、创建CSAT调查的好处、如何创建CSAT调查。配图来源: SaleSmartly(ss客服) 一、什么是 CSAT&#xff1…

算法笔记(十二)—— Manacher算法(回文子串)

计算字符串内的最大回文子串,常用的暴力扩散在应对长度为偶数的回文时会遇到一些问题。 Manacher基础:对字符串进行填充,在字符串开头结尾以及字符间填充‘#’,以来应对偶数回文时的问题。(这是采用暴力扩再除2&#x…

[黑马程序员SSM框架教程]03 spring核心概念

IOC/DI 书写现状:耦合度偏高 如图:传统书写代码左边业务层需要new一个对象进行业务实现。当数据层优化代码BookDaoImpl2就需要动业务层代码重新修改new的对象。导致代码耦合度偏高。 解决办法:使用对象,不要主动new对象&#xff…

kubernetes traefik ingress 安装部署以及使用和注意点

1、简介 Traefik 是一款 open-source 边缘路由器,可让您轻松地发布服务. 它接收来自您的系统请求,并找出负责处理它们的后端服务组件。 traefik 与众不同在于它能够自动发现适合您服务的配置。 当 Traefik 检查您的基础设施时,它会发现相关信…

Redisson实现分布式锁

目录Redisson简介Redisson实现分布式锁步骤引入依赖application.ymlRedisson 配置类Redisson分布式锁实现Redisson简介 Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,没有说“最好”)的编程语言之一。…

Matthew Ball:十多年后AR/VR为何依然发展缓慢?

2010年,Magic Leap和微软就开始研发AR技术,直到2012年Oculus才成立,AR/VR经过了13年左右的时间,虽然受到越来越多人关注,但发展依然缓慢。VR的主要应用场景还是游戏,但VR游戏只是游戏市场的一个分支&#x…

第七章.深度学习

第七章.深度学习 7.1 深度学习 深度学习是加深了层的深度神经网络。 1.加深层的好处 1).可以减少网络的参数数量 5*5的卷积运算示例: 重复两次3*3的卷积层示例: 图像说明: ①.一次5 * 5的卷积运算的区域可以由两次3 * 3的卷积运算抵消&a…

服务端开发Java之备战秋招面试篇1

在这个面试造火箭工作拧螺丝的时代背景下,感觉不是很好,不过还好也是拿到了还行的offer,准备去实习了,接下来就是边实习边准备秋招了,这半年把(技术栈八股文面经算法题项目)吃透,希望…

打破数据孤岛,Apache Doris 助力纵腾集团快速构建流批一体数仓架构|最佳实践

福建纵腾网络有限公司(简称“纵腾集团”)成立于 2009 年, 以“全球跨境电商基础设施服务商”为企业定位,聚焦跨境仓储与物流, 为全球跨境电商商户、出口贸易企业、出海品牌商提供海外仓储、商业专线物流、定制化物流等…

【C++】vector 模拟实现

vectorvector 容器vector 基本使用vector 定义库中各类接口的使用迭代器容量相关接口元素访问相关接口元素修改相关接口模拟实现 vector前期准备构造与析构赋值运算符重载迭代器相关容量相关元素访问相关元素的修改相关二维数组的创建对于自定义类型数据的测试vector 容器 C S…

Python实战之小说下载神器(二)整本小说下载:看小说不用这个程序,我实在替你感到可惜*(小说爱好者必备)

前言 这次的是一个系列内容给大家讲解一下何一步一步实现一个完整的实战项目案例系列之小说下载神器(二)(GUI界面化程序) 单章小说下载保存数据——整本小说下载 你有看小说“中毒”的经历嘛?小编多多少少还是爱看小说…

基于react+nodejs+mysql开发用户中心,用于项管理加入的项目的用户认证

基于reactnodejsmysql开发用户中心,用于项管理加入的项目的用户认证用户中心功能介绍页面截图后端采用架构user表projects表project_user表仓库地址用户中心功能介绍 用户中心项目,用于统一管理用户信息、登录、注册、鉴权等 功能如下: 用…

[qiankun]实战问题汇总

[qiankun]实战问题汇总ERROR SyntaxError: Cannot use import statement outside a module问题分析解决方案子应用命名问题问题分析解决方案jsonpFunction详细错误信息问题分析解决方案微应用的注册问题Uncaught Error: application cli5-beta6-test-name died in status LOADI…

2月,真的不要跳槽。

新年已经过去,马上就到金三银四跳槽季了,一些不满现状,被外界的“高薪”“好福利”吸引的人,一般就在这时候毅然决然地跳槽了。 在此展示一套学习笔记 / 面试手册,年后跳槽的朋友可以好好刷一刷,还是挺有必…