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

news2024/11/27 20:40:48

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

六、task group时间片

6.1. 时间片分配

若使能CFS组调度会从上到下逐层通过权重比例来分配上层分得的时间片,分配函数是sched_slice()。但是从上到下不便于遍历,因此改为从下到上进行遍历,毕竟 ABC 和 CBA 是相等的。

sched_slice的主要路径如下:

在tick中断中,若发现se此次运行时间已经超过了其分得的时间片,就触发抢占,以便让其让出CPU。

如下图4,假设tg嵌套2层,且在当前CPU上各层gse从tg那里分得的权重都是1024,且假设直接通过任务个数来计算周期,5个tse,period 就是 3 * 5 = 15ms那么:

tse1 获得 1024/(1024+1024) * 15 = 7.5ms;

tse2 获得 [1024/(1024+1024+1024)] * {[1024/(1024+1024)] * 15 }= 2.5ms

tse4 获得 [1024/(1024+1024)] * {[1024/(1024+1024+1024)] * [1024/(1024+1024)] * 15} = 1.25ms

图4:

注:tg1和tg2的权重通过 cpu.shares 文件进行配置,然后各个cpu上的gse从 cpu.shares 配置的权重中按其上的grq的权重比例分配权重。gse的权重不再和nice值挂钩。

6.2. 运行时间传导

pick_next_task_fair() 会优先pick虚拟时间最小的se。gse的虚拟时间是怎么更新的呢。虚拟时间是在 update_curr()中进行更新,然后通过 for_each_sched_entity 向上逐层遍历更新gse的虚拟时间。若tse运行5ms,则其父级各gse都运行5ms,然后各层级根据自己的权重更新虚拟时间。

主要调用路径:

在选择下一个任务出来运行时逐层级选择虚拟时间最小的se,若选到gse就从其grq上继续选,直到选到tse。

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

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

七、task group的PELT负载

7.1. 计算负载使用的timeline

计算负载使用的timeline和计算虚拟时间使用的timeline不同。计算虚拟时间时使用的timeline是 rq->clock_task, 这个是运行多长时间就是多长时间。而计算负载使用的timeline是rq->clock_pelt,它是根据CPU的算力和当前频点scale后的,在CPU进idle是会同步到rq->clock_task上。因此PELT计算出来的负载可以直接使用,而不用像WALT计算出来的负载那样还需要scale。更新rq->clock_pelt这个timeline的函数是 update_rq_clock_pelt()

最终计算的 delta= delta * (capacity_cpu / capacity_max(1024)) * (cur_cpu_freq / max_cpu_freq) 也就是将当前cpu在当前频点上运行得到的delta时间值,缩放到最大性能CPU的最大频点上对应的delta时间值。然后累加到 clock_pelt 上。比如在小核上1GHz下跑了5ms,可能只等效于在超大核上运行1ms,因此在不同Cluster的CPU核上跑相同的时间,负载增加量是不一样的。

7.2. 负载定义与计算

load_avg 定义为:load_avg = runnable% * scale_load_down(load)。

runnable_avg 定义为:runnable_avg = runnable% * SCHED_CAPACITY_SCALE。

util_avg 定义为:util_avg = running% * SCHED_CAPACITY_SCALE。

这些负载值保存在struct sched_avg结构中,此结构内嵌到se和cfs_rq结构中。此外,struct sched_avg中还引入了load_sum、runnable_sum、util_sum成员来辅助计算。不同实体(tse/gse/grq/cfs_rq)的负载只是其runnable% 多么想运行,和 running% 运行了多少的表现形式不同。这两个因数只对tse取值是[0,1]的,对其它实体则超出了这个范围。

7.2. 1. tse负载

下面看一下tse负载计算公式,为了加深印象,举一个跑死循环的例子。计算函数见 update_load_avg --> __update_load_avg_se().

load_avg: 等于 weight * load_sum / divider, 其中 weight = sched_prio_to_weight[prio-100]。由于 load_sum 是任务 running+runnable 状态的几何级数,divider 近似为几何级数最大值,因此一个死循环任务的 load_avg 接近于其权重。

runnable_avg: 等于 runnable_sum / divider。由于 runnable_sum 是任务 running+runnable 状态的几何级数然后scale up后的值,divider 近似为几何级数最大值,因此一个死循环任务的 runnable_avg 接近于 SCHED_CAPACITY_SCALE。

util_avg: 等于 util_sum / divider。由于 util_sum 是任务 running 状态的几何级数然后scale up后的值,divider 近似为几何级数最大值,因此一个死循环任务的 util_avg 接近于 SCHED_CAPACITY_SCALE。

load_sum: 是对任务是单纯的 running+runnable 状态的几何级数累加值。对于一个死循环,此值趋近于 LOAD_AVG_MAX 。

runnable_sum: 是对任务 running+runnable 状态的几何级数累加值然后scale up后的值。对于一个死循环,此值趋近于 LOAD_AVG_MAX * SCHED_CAPACITY_SCALE 。

util_sum: 是对任务 running 状态的几何级数累加值然后scale up后的值。对于一个独占某个核的死循环,此值趋近于 LOAD_AVG_MAX * SCHED_CAPACITY_SCALE,若不能独占,会比此值小。

7.2.2. cfs_rq的负载

下面看一下cfs_rq负载计算公式,为了加深印象,举一个跑死循环的例子。计算函数见 update_load_avg --> update_cfs_rq_load_avg --> __update_load_avg_cfs_rq()。

load_avg: 直接等于 load_sum / divider。cfs_rq 跑满(跑一个死循环或多个死循环),趋近于cfs_rq的权重,cfs_rq的权重也就是其上挂的所有调度实体的权重之和,即Sum(sched_prio_to_weight[prio-100]) 。

runnable_avg: 等于 runnable_sum / divider。cfs_rq 跑满(跑一个死循环或多个死循环),趋近于cfs_rq上任务个数乘以 SCHED_CAPACITY_SCALE。

util_avg: 等于 util_sum / divider。cfs_rq 跑满(跑一个死循环或多个死循环),趋近于 SCHED_CAPACITY_SCALE。

load_sum: cfs_rq 的 weight,也就是本层级下所有se的权重之和乘以非idle状态下的几何级数。注意是本层级,下面讲解层次负载h_load时有用到。

runnable_sum: cfs_rq上所有层级的runnable+running 状态任务个数和乘以非idle状态下的几何级数,然后再乘以 SCHED_CAPACITY_SCALE 后的值。见 __update_load_avg_cfs_rq().

util_sum: cfs_rq 上所有任务 running 状态下的几何级数之和再乘以 SCHED_CAPACITY_SCALE 后的值。

load_avg、runnable_avg、util_avg分别从权重(优先级)、任务个数、CPU时间片占用三个维度来描述CPU的负载。

7.2.3. gse 负载

对比着tse来讲解gse:

(1) gse会和tse走一样的负载更新流程(逐层向上更新,就会更新到gse)。

(2) gse的runnable负载与tse是不同的。tse的 runnable_sum是任务 running+runnable 状态的几何级数累加值然后scale up后的值。而gse是其当前层级下所有层级的tse的个数之和乘以时间几何级数然后scale up后的值,见 __update_load_avg_se() 函数 runnable 参数的差异。

(3) gse 和tse的 load_avg 虽然都等于 se->weight * load_sum/divider, 见 ___update_load_avg() 的参数差异。但是weight 来源不同,因此也算的上是一个差异点,tse->weight来源于其优先级,而gse来源于其从tg中分得的配额。

(4) gse会比tse多出了一个负载传导更新过程,放到下面讲解(若不使能CFS组调度,只有一层,没有tg的层次结构,因此不需要传导,只需要更新到cfs_rq上即可)。

7.2. 4. grq 负载

grq的负载和cfs_rq的负载在更新上没有什么不同。grq会比cfs_rq多了一个负载传导更新过程,放到下面讲解。

7.2.5. tg的负载

tg只有一个load负载,就是tg->load_avg,取值为\Sum tg->cfs_rq[]->avg.load_avg,也即tg所有CPU上的grq的 load_avg 之和。tg负载更新是在update_tg_load_avg()中实现的,主要用于给gse[]分配权重。

调用路径:

7.3. 负载传导

负载传导是使能CFS组调度后才有的概念。当tg层次结构上插入或删除一个tse的时候,整个层次结构的负载都变化了,因此需要逐层向上层进行传导。

7.3.1. 负载传导触发条件

是否需要进行负载传导是通过struct cfs_rq 的 propagate 成员进行标记。grq上增加/删除的tse时会触发负载传导过程。tse的负载load_sum值会记录在 struct cfs_rq 的 prop_runnable_sum 成员上,然后逐层向上传导。其它负载(runnable_、util_)则会通过tse-->grq-->gse-->grq...逐层向上层传导。

在 add_tg_cfs_propagate() 中标记需要进行负载传导:

此函数调用路径:

由上可见,当从非CSF调度类变为CFS调度类、移到当前tg中来、新建的任务开始挂到cfs_rq上、迁移到当前CPU都会触发负载传导过程,此时会向整个层次结构中传导添加这个任务带来的负载。当任务从当前CPU迁移走、变为非CFS调度类、从tg迁移走,此时会向整个层次结构中传导移除这个任务降低的负载。

注意,任务休眠时并没有将其负载移除,只是休眠期间其负载不增加了,随时间衰减。

7.3.2. 负载传导过程

负载传导过程体现在逐层更新负载的过程中。如下,负载更新函数update_load_avg() 在主要路径下,每层都会进行调用:

load负载传导函数和标记需要进行传导的函数是同一个,为 add_tg_cfs_propagate(), 其调用路径如下:

7.3.2.1. update_tg_cfs_util() 更新gse和grq的util_* 负载,并负责将负载传递给上层。

可见gse的util负载在传导时直接取的是其grq上的util负载。然后通过更新上层 grq 的 util_avg 向上层传导。

7.3.2.2. update_tg_cfs_runnable() 更新gse和grq的runnable_*负载,并负责将负载传递给上层。

可见gse的runnable负载在传导时也是直接取的是其grq上的runnable负载。然后通过更新上层 grq 的 runnable_avg 向上层传导。

7.3.2.3. update_tg_cfs_load() 更新gse和grq的load_*负载,并负责将负载传递给上层。

load负载比较特殊,负载传导时并不是直接取自grq的load负载,而是在向grq添加/删除任务时就记录了tse 的load_sum值,然后在 add_tg_cfs_propagate() 中逐层向上传导,传导位置调用路径:

对load负载的标记和传导都是这个函数:

load负载更新函数:

删除任务就是将grq上的se的平均load_sum赋值给gse。添加任务是将gse的load_sum直接加上delta值。

load_avg和普通tse计算方式一样,为load_sum*se_weight(gse)/divider。

对比可见,runnable负载和util负载的传导方向是由grq-->gse,分别通过runnable_avg/util_avg进行传导,gse直接取grq的值。而load负载的传导方向是由gse-->grq进行传导,且是通过load_sum进行传导的。

load负载传导赋值方式上为什么和runnable负载和util负载有差异,可能和其统计算法有关。对于runnable_avg,gse计算的是当前层级下所有层级上tse的个数和乘以runnable状态时间级数的比值,底层增加一个tse对上层相当于tse个数增加了一个;对于util_avg,gse计算的是其下所有tse的running状态几何级数和与时间级数的比值,底层增加一个tse对上层就相当于增加了tse的running状态的几何级数;而 load_avg 和se的权重有关,gse和tse的权重来源不同,前者来自从tg->shares中分得的配额,而后者来源于优先级,不能直接相加减。而load_sum对于se来说是一个单纯的runnable状态的时间级数,不涉及权重,因此tse和gse都可以使用它。

对于load_avg的传导举个例子,如下图5,假如ts2一直休眠,ts1和ts3是两个死循环,那么gse1的grq1的load_avg将趋近于4096,而根cfs_rq的负载将趋近于2048,若此时要将ts3迁移走,若像计算runnable和util负载那样直接想减,得到的delta值是-4096,那么根cfs_rq的load_avg将会是个负值(2048-4096<0),这显然是不合理的。若通过load_sum进行传导,它只是个时间级数,相减后根cfs_rq上只相当于损失了50%的负载。

图5:

注: 这只是在tg的层次结构中添加/删除任务时的负载的传导更新路径,随着时间的流逝,即使没有添加/移除任务,gse/grq的负载也会更新,因为普通的负载更新函数 __update_load_avg_se()/update_cfs_rq_load_avg() 并没有区分是tse还是gse,是cfs_rq还是grq。

7.4. 层次负载

在负载均衡的时候,需要迁移CPU上的负载以便达到均衡,为了达成这个目的,需要在CPU之间进行任务迁移。然而各个task se的load avg并不能真实反映它对root cfs rq(即对该CPU)的负载贡献,因为task se/cfs rq总是在某个具体level上计算其load avg。比如grq的load_avg并不会等于其上挂的所有tse的load_avg的和,因为runnable的时间级数肯定是Sum(tse) > grq的(有runnable等待运行的状态存在)。

为了计算task对CPU的负载(h_load),在各个cfs rq上引入了hierarchy load的概念,对于顶层cfs rq而言,其hierarchy load等于该cfs rq的load avg,随着层级的递进,cfs rq的hierarchy load定义如下:

下一层的cfs rq的h_load = 上一层cfs rq的h_load x gse负载在上一层cfs负载中的占比

在计算最底层tse的h_load的时候,我们就使用如下公式:

tse的h_load = grq的h_load x tse的load avg / grq的load avg

获取和更新task的h_load的函数如下:

更新grq的h_load的函数如下:

调用路径:

可以看到,主要是唤醒wake_affine_weight机制和负载均衡逻辑中使用。比如迁移类型为load的负载均衡中,要迁移多少load_avg可以使负载达到均衡,使用的就是task_h_load(),见 detach_tasks()。

八、总结

本文介绍了CFS组调度功能引入的原因,配置方法,和一些实现细节。此功能可以在高负载下"软限制"(相比与CFS带宽控制)各分组任务对CPU资源的使用占比,以达到各组之间公平使用CPU资源的目的。在老版原生Android代码中对后台分组限制的较狠(甚是将 background/cpu.shares 设置到52),将CPU资源重点向前台分组进行倾斜,但这个配置可能会在某些场景下出现前台任务被后台任务卡住的情况,对于普适性配置,最新的一些Android版本中将各个分组的 cpu.shares 都设置为1024以追求CPU资源在各组之间的公平。

 

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

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

相关文章

盘点全网好评最多的7款团队协同软件,你用过哪款?

能亲自带团队管理项目当然是一件开心和兴奋的事&#xff0c;但是突然成为团队负责人后开始不大适应。如何转换角色&#xff0c;还有自己和团队成员之间在心理、行为等方面的互动也变得很敏感。新手领导上任的过程&#xff0c;是团队秩序再造的过程&#xff1b;是晋升者个人职业…

Python----------字符串

1.转义字符 注&#xff1a;转义字符放在你所想效果字符前 2.原始字符串 print(r"D:\three\two\one\now") ->D:\three\two\one\now注&#xff1a; 在使用原始字符串时&#xff0c;转义字符不再有效&#xff0c;只能当作原始的字符&#xff0c;每个字符都没有特殊…

MySQL(一)基础使用

MySQL基础使用概念数据库相关概念关系型数据库SQL通用语法SQL分类DDL数据库操作表操作表操作-数据类型表操作-修改表操作-删除DML添加数据修改数据删除数据DQL基本查询&#xff08;不带任何条件&#xff09;查询多个字段&#xff1a;字段设置别名去除重复记录条件查询&#xff…

2月第3周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站UP主。飞瓜数据UP主充电榜排行榜&…

EasyExcel 几十万导入报错问题——java.lang.NoClassDefFoundError

EasyExcel 报错 NoClassDefFoundError org/ehcache/config/builders/CacheManagerBuilder 特此郑重声明&#xff01;该文章是原创作品&#xff0c;小编编写实属不易 &#xff0c;帮忙点赞关注一下~转载小伙伴请注明出处&#xff01; EasyExcel 导入几十万数据报错 今天在执行…

【java实现Word模板导出】Xdocreport和Freemaker

如果只是生成简单的word文件的话可以使用 Hutool 上手简单使用方便。 但如果需要导出内容比较复杂的word文件的话用那个就不合适了&#xff0c;这时候就需要Xdocreport这玩意了。 制作模板 新建一个word文档在需要插入变量的地方使用快捷键 Crtl F9 来生成一个域 然后右键单…

【软件工具】Source Insight 4.0编辑keil工程代码

0.前言 最近在学习过程中&#xff0c;发现诸多课程老师均使用Source Insight 4.0进行开发演示&#xff0c;为了方便课程的学习&#xff0c;也为了提高个人开发水平及效率&#xff0c;故学习Source Insight 4.0软件&#xff0c;此文章主要作为软件使用的流程总结&#xff0c;同…

第十一章 - 模糊匹配(like)、正则匹配(REGEXP)、文本处理函数、时间处理函数

第十一章 - 模糊匹配&#xff08;like&#xff09;、正则匹配&#xff08;REGEXP&#xff09;、文本处理函数、时间处理函数模糊匹配和正则匹配like%通配符_通配符REGEXP 正则匹配文本拼接concat&#xff08;&#xff09;substring()substring_index()一些文本处理函数时间处理…

Autosar MCAL-ADC配置PWM硬件触发采样

文章目录前言ADC配置AdcGroupRequestSourceAdcGroupTriggSrcAdcHwExtTrigSelectAdcHwGatePinAdcGeneral-AdcHwTriggerApiAdcHwGateSignalAdcHwTrigSignalAdcHwTrigTypeGtmGtmConnectionsPWM实际使用总结前言 在实际项目开发过程中&#xff0c;关于ADC采样&#xff0c;大部分使…

存储器与cpu的连接

1. 只读存储器 只读存储器中一般用于保存系统程序或者系统的配置信息&#xff1b; 早期的只读存储器&#xff0c; 在厂家就写好了内容改进&#xff11;&#xff0c; 用户可以自己写&#xff0c; 一次性改进2, 可以多次写-- 要能对信息进行擦除&#xff1b;改进3,  电可擦…

【Leetcode 剑指Offer】第3天 字符串(简单)

字符串剑指 Offer 05. 替换空格字符串构造函数和析构函数操作函数剑指 Offer 58 - II. 左旋转字符串剑指 Offer 05. 替换空格 题&#xff1a;实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20"。 class Solution { public:string replaceSpace(string s…

华为OD机试真题 用 C++ 实现 - 单词反转

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

Springcloud-Seata分布式事务

目录 一、什么是Seata&#xff1f; 二、Seata的执行流程 三、搭建Seata服务器 四、配置微服务客户端 分布式事务的解决办法: 使用消息中间件手写代码解决分布式事务使用第三方组件--->Seata阿里巴巴的产品 这里只介绍通过第三方插件-----Seata解决分布式事务的问题 一、什…

说说 React 中 fiber、DOM、ReactElement、实例对象之间的引用关系

原生组件 fiber 原生组件 fiber&#xff0c;指的就是 type 为 “span”、“div” 的 fiber。 1.fiber.stateNode 指向真实 DOM 节点&#xff1b;2.node["__reactFiber$" randomKey] 指向对应 fiber&#xff0c;使用随机数是防止和业务代码的属性名冲突&#xff0c;…

【系统设计】攻击技术

一、跨站脚本攻击 概念 跨站脚本攻击&#xff08;Cross-Site Scripting, XSS&#xff09;&#xff0c;是指恶意攻击者在Web页面中插入恶意javascript代码&#xff08;也可能包含html代码&#xff09;&#xff0c;当用户浏览网页之时&#xff0c;嵌入其中Web里面的javascript代…

python | 第八、九章考试题

本篇文章是对北京理工大学嵩天老师的《Python语言程序设计》第八章&#xff1a;程序设计方法学、第九章&#xff1a;Python计算生态纵览考试题的学习记录。 目录 一、第八章考试题 1、英文字符的鲁棒输入 2、数字的鲁棒输入 二、第九章考试题 1、系统基本信息获取 2、二…

JavaScript学习第2天:JS内置对象、简单数据类型与复杂类型

一、内置对象 JavaScript 中的对象分为3种&#xff1a;自定义对象 、内置对象、 浏览器对象 前面两种对象是JS 基础内容&#xff0c;属于 ECMAScript&#xff1b; 第三个浏览器对象属于JS 独有的 1、内置对象 内置对象就是指 JS 语言自带的一些对象。 JavaScript 提供了多个…

关于java8的List的stream流的foreach()方法问题探究(坑)与替代方案

一、起因 今天发现线上系统出现了一个bug&#xff0c; 我有一个“定时任务”每天凌晨触发&#xff0c;任务内容&#xff1a; ① 定时调用的系统暴漏的接口&#xff0c;来定时获取List<Object>数据。 ② 然后我会筛选出该List中符合条件的Object&#xff0c;对筛选出来的…

数组类模板

要求&#xff1a;设计一个数组模板类&#xff08;MyArray&#xff09;&#xff0c;完成对不同类型元素的管理操作步骤设计头文件在 qtcreate下先创建03_code的项目&#xff0c;然后右键点击03_code添加新文件&#xff0c;点击头文件&#xff0c;点击Choose命名为 myarry.hpp&am…

产品运营︱用户活跃度低的解决方法

app用户活跃度低&#xff0c;产品拉新变现效率慢&#xff0c;这是运营app时难免会遇到的情况。要想解决这类问题&#xff0c;就要从可能的原因下手&#xff0c;进行产品的优化改进&#xff0c;记录下改变后的关键数据变化&#xff0c;定期做好复盘工作进行调整。 一、app用户量…