Linux Kernel | 一个睡眠中的进程被唤醒后,应该去哪个cpu上去执行?

为方便称呼,执行唤醒流程的进程是waker,被唤醒进程是wakee。

waker所在的cpu,waker与wakee可能具有某种程度的亲和性;

CPU的亲和性,进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性,进程迁移的频率小就意味着产生的负载小。亲和性一词是从affinity翻译来的,实际可以称为CPU绑定。
作用 在多核运行的机器上,每个CPU本身自己会有缓存,在缓存中存着进程使用的数据,而没有绑定CPU的话,进程可能会被操作系统调度到其他CPU上,如此CPU cache(高速缓冲存储器)命中率就低了,也就是说调到的CPU缓存区没有这类数据,要先把内存或硬盘的数据载入缓存。而当缓存区绑定CPU后,程序就会一直在指定的CPU执行,不会被操作系统调度到其他CPU,性能上会有一定的提高。

wakee上次执行的cpu,也许cache上的数据还是hot的;
同理,与waker所在cpu或者wakee上次执行的cpu共享cache的cpu也有很高的选择优先级;
处于负载均衡考虑,放在某个idle的cpu上会更高效利用系统资源;
处于功耗考虑,选择一个cpu使得耗能最少。
显而易见,选择cpu是一个非常复杂的问题。

我们从try_to_wake_up -->select_task_rq 此条路径出发,简单了解一下睡眠进程被唤醒过程中的选核思路。

select_task_rq 函数会选择wakee 的调度类中定义的select_task_rq 去执行。

这里以CFS中定义的select_task_rq_fair 函数为例进行分析。

先看函数开始部分:

staticint select_task_rq_fair( structtask_struct*p, intprev_cpu, intsd_flag, intwake_flags)
{
	structs ched_domain* tmp, *sd=NULL;
	int cpu=smp_processor_id;	//cpu 为waker 现在执行的cpu;
	int new_cpu=prev_cpu;	//new_cpu 以及prev_cpu 为wakee 上次执行的cpu;
	int want_affine=0;	//want_affine 用于表示是否满足亲和条件,在后续判断(如果满足亲和条件,则将进程唤醒到waker 现在执	行的cpu上也是一个比较优的选择);
	int sync=( wake_flags&WF_SYNC) &&!( current->flags&PF_EXITING);	
	/*
	sync 是用来表示waker 与wakee 之间的关系的。
	我们认为waker与wakee之间有两种关系:sync,以及non-sync。
	sync——waker在唤醒wakee的时候知道自己很快进入睡眠状态,故最好不要进行抢占。
	non-sync——没有同步关系,唤醒的时候,可以尝试触发一次调度。
	在这里,sync 本质上是放宽了对waker 的cpu的idle的判断条件。*/

if( sd_flag&SD_BALANCE_WAKE) {

record_wakee( p);

if( sched_energy_enabled) {

new_cpu=find_energy_efficient_cpu( p, prev_cpu);

if( new_cpu>=0)

returnnew_cpu;

new_cpu=prev_cpu;

}

want_affine=!wake_wide( p) &&cpumask_test_cpu( cpu, p->cpus_ptr);

}

进程唤醒可以视为一次主动的BALANCE。

record_wakee 主要用于更新waker 的wakee_flips ,用于后续的判断。

staticvoidrecord_wakee( structtask_struct*p)

{

/*

* Only decay a single time; tasks that have less then 1 wakeup per

* jiffy will not have built up many flips.

*/

if( time_after( jiffies, current->wakee_flip_decay_ts+HZ)) {

current->wakee_flips>>=1;

current->wakee_flip_decay_ts=jiffies;

}

if( current->last_wakee!=p) {

current->last_wakee=p;

current->wakee_flips++;

}

}

task_struct 中有三个成员:

wakee_flips_decay_ts 表示上次进行衰减的时刻;

last_wakee 为上次由它唤醒的进程;

wakee_flips 度量由它唤醒进程的数量,wakee_flips 一秒之前的计数全部除以2,有点衰减的那味儿了。

sched_energy_enabled 分支的部分,与功耗EAS调度器有关,暂不分析。

可以明确的是,find_energy_efficient_cpu 用于在开启功耗模型下寻找功耗最低的cpu去执行。

wake_wide 函数检测waker /wakee 是否符合wake_affine 模型。(最终用want_affine 表示)

当wake_wide 支持以及waker 所在的cpu可以执行wakee 时,want_affine 生效。

符合wake_affine 模型则优先去waker 现在执行以及wakee 上次执行的cpus中进行选择(放宽对waker 执行cpu的条件,最后从二者中挑选一个target cpu)。

staticintwake_wide( structtask_struct*p)

{

unsignedintmaster=current->wakee_flips;

unsignedintslave=p->wakee_flips;

intfactor=__this_cpu_read( sd_llc_size);

if( master<slave)

swap( master, slave);

if( slave<factor||master<slave*factor)

return0;

return1;

}

sd_llc_domain 为当前sched_domain 中能够共享cache的CPU数目;

将waker 和wakee 中wakee_flips 中较大的称为master ,较小的称为slave 。

当较小的wakee_flips 或者较大的wakee_flips 与较小的wakee_flips 的比值小于sd_llc_size 是可以作为wake_affine 生效的一部分判断依据。

继续看select_task_rq_fair 函数:

rcu_read_lock;

for_each_domain( cpu, tmp) {

/*

* If both 'cpu' and 'prev_cpu' are part of this domain,

* cpu is a valid SD_WAKE_AFFINE target.

*/

if( want_affine&&( tmp->flags&SD_WAKE_AFFINE) &&

cpumask_test_cpu( prev_cpu, sched_domain_span( tmp))) {

if( cpu!=prev_cpu)

new_cpu=wake_affine( tmp, p, cpu, prev_cpu, sync);

sd=NULL; /* Prefer wake_affine over balance flags */

break;

}

if( tmp->flags&sd_flag)

sd=tmp;

elseif( !want_affine)

break;

}

以waker 执行的cpu调度域逐步向上,进行如下判断:

当want_affine 成立,并且调度域满足亲和性条件,并且waker 的cpu与wakee 上次执行cpu在同一调度域中时,sd 为NULL。

如果条件均不满足,则sd 置为最后满足sd_flag 的调度域。

如果满足want_affine ,退出。

在满足第1个条件,且waker 执行的cpu与wakee 上次执行cpu不相等的情况下,执行wake_affine 进行选择。

wake_affine 本质上是在waker 现在执行的cpu以及wakee 上次执行的cpu进行对比,决定要不要给waker 现在执行的cpu一点机会。

staticintwake_affine( structsched_domain*sd, structtask_struct*p,

intthis_cpu, intprev_cpu, intsync)

{

inttarget=nr_cpumask_bits;

if( sched_feat( WA_IDLE))

target=wake_affine_idle( this_cpu, prev_cpu, sync);

if( sched_feat( WA_WEIGHT) &&target==nr_cpumask_bits)

target=wake_affine_weight( sd, p, this_cpu, prev_cpu, sync);

schedstat_inc( p->se. statistics. nr_wakeups_affine_attempts);

if( target==nr_cpumask_bits)

returnprev_cpu;

schedstat_inc( sd->ttwu_move_affine);

schedstat_inc( p->se. statistics. nr_wakeups_affine);

returntarget;

}

首先,如果waker 执行的cpu(此时waker 为idle,中断唤醒wakee )为空闲或者很快进入空闲态或者wakee 上次执行的cpu为空闲,则通过wake_affine_idle 可以找到一个合适的target cpu。

staticint wake_affine_idle( intthis_cpu, intprev_cpu, intsync)

{

if( available_idle_cpu( this_cpu) &&cpus_share_cache( this_cpu, prev_cpu))

returnavailable_idle_cpu( prev_cpu) ?prev_cpu: this_cpu;

if( sync&&cpu_rq( this_cpu) ->nr_running==1)

returnthis_cpu;

returnnr_cpumask_bits;

}

如果waker 的cpu空闲(中断唤醒)并且waker 执行的cpu与wakee 上次执行的cpu共享cache时:

wakee 上次执行的cpu也空闲,那么回到上次执行的cpu,否则是waker 的cpu。

否则sync 为1(表示waker 很快要退出)并且只有一个进程在执行,那么返回waker 执行的cpu。

如果在wake_affine_idle 中没有找到target cpu,则进入wake_affine_weight 中继续寻找。

staticint

wake_affine_weight( structsched_domain*sd, structtask_struct*p,

intthis_cpu, intprev_cpu, intsync)

{

s64this_eff_load, prev_eff_load;

unsignedlongtask_load;

this_eff_load=cpu_load( cpu_rq( this_cpu));

if( sync) {

unsignedlongcurrent_load=task_h_load( current);

if( current_load>this_eff_load)

returnthis_cpu;

this_eff_load-=current_load;

}

task_load=task_h_load( p);

this_eff_load+=task_load;

if( sched_feat( WA_BIAS))

this_eff_load*=100;

this_eff_load*=capacity_of( prev_cpu);

prev_eff_load=cpu_load( cpu_rq( prev_cpu));

prev_eff_load-=task_load;

if( sched_feat( WA_BIAS))

prev_eff_load*=100+( sd->imbalance_pct-100) /2;

prev_eff_load*=capacity_of( this_cpu);

if( sync)

prev_eff_load+=1;

returnthis_eff_load<prev_eff_load?this_cpu: nr_cpumask_bits;

}

计算 waker 的负载以及cpu负载,在 waker 即将执行完成的情况下,cpu负载减去进程负载;
waker 的cpu负载加上 wakee 的进程负载
wakee 的cpu负载减去 wakee 的进程负载(此处是假设进程从上次执行的cpu迁移到 waker 执行的cpu)
最终,哪个cpu负载小,最后return哪个cpu。

如果sd 不为空,则进入find_idlest_cpu 这条慢速路径。

否则,进入select_idle_sibling 快速路径。

if( unlikely( sd)) {

/* Slow path */

new_cpu=find_idlest_cpu( sd, p, cpu, prev_cpu, sd_flag);

} elseif( sd_flag&SD_BALANCE_WAKE) { /* XXX always ? */

/* Fast path */

new_cpu=select_idle_sibling( p, prev_cpu, new_cpu);

if( want_affine)

current->recent_used_cpu=cpu;

}

rcu_read_unlock;

returnnew_cpu;

}

find_idlest_cpu 为SMP负载均衡的常用函数,即选择负载最小的cpu去执行。

select_idle_sibling 在之前挑选出来的target cpu与上一次执行的cpu(有可能taget与上一次执行是一个cpu)共享cache的调度域中,根据负载选择一个最合适的cpu去执行。

总结

cpu的选择,主要有以下关键点(满足SD_BALANCE_WAKE):

find_energy_efficient_cpu,在EAS调度器开启下启用,选择功耗最小的cpu;
如果未开启EAS调度器,首先判断是否根据 wake_wide (依据唤醒进程的强度)以及进程可执行的cpu掩码决定满足 waker 执行cpu的亲和性;
如果满足亲和性,会通过 wake_affine 来判断要不要给唤醒 wakee 的cpu一点执行进程的机会;
不满足亲和性或者调度域不支持情况下,会走到 find_idlest_cpu (慢速路径),进行负载均衡的选择;
否则,进入快速路径,通过 select_idle_sibling ,在 wake_affine 挑选出来的target cpu以及上一次执行的cpu共享cache的调度域中,根据负载选择一个cpu进行执行。

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

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

相关文章

MinIO + Prometheus + Grafana docker部署

文章目录 说明MinIO简介MinIO 容器化部署Prometheus服务地址配置方法一&#xff1a;先部署后修改方法二&#xff1a;部署时修改compose文件&#xff08;未验证&#xff09; MinIO Access Key配置Prometheus 容器化部署MinIO 生成抓取配置修改Prometheus配置文件Grafana 容器化部…

iframe和 blob实现JS,CSS,HTML直接当前页预览

先贴效果图&#xff1a; <template><div><div class"aaa"></div><div class"btn-run" click"tres">运行</div></div></template><script>import { mapState } from vuex;export default …

在线编辑器 CodeMirror

如何优雅的在网页显示代码 如果开发在线编辑器 引入资源&#xff1a; <link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.60.0/codemirror.min.css"><script src"https://cdnjs.cloudflare.com/ajax/libs/c…

【网安小白成长之路】8.sql注入操作1

&#x1f42e;博主syst1m 带你 acquire knowledge&#xff01; ✨博客首页——syst1m的博客&#x1f498; &#x1f51e; 《网安小白成长之路(我要变成大佬&#x1f60e;&#xff01;&#xff01;)》真实小白学习历程&#xff0c;手把手带你一起从入门到入狱&#x1f6ad; &…

店前台安装水离子雾化壁炉前和装后对比

当酒店前台装上水离子雾化壁炉后&#xff0c;整体氛围和客户体验都会发生显著的变化&#xff1a; 装前&#xff1a; 普通的前台氛围&#xff1a;前台可能显得比较普通和传统&#xff0c;可能缺乏独特的装饰元素或视觉焦点。 视觉上缺乏吸引力&#xff1a;前台空间可能比较朴…

现代商业中首席人工智能官(CAIO)的角色与影响

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

万字总结!Docker简介及底层关键技术剖析

本文首发在个人博客上&#xff1a;万字总结&#xff01;Docker简介及底层关键技术剖析 Docker 简介 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#x…

PVE grub resue错误修复 lvmid BUG

服务器断电后启动不起来&#xff0c;显示grub resue 找了半天没有找到修复方法。看官方文档有一处Recovering from grub “disk not found” error when booting from LVM 极为类似。https://pve.proxmox.com/wiki/Recover_From_Grub_Failure 下面是处理过程。 使用PVE 6.4启…

使用示例解释.NET中的Mocking是什么?

让我们踏上探索.NET软件开发中Mocking概念的旅程&#xff0c;让我们深入了解Mocking是多么简单易懂、易于访问。请与我一起穿越这个主题&#xff0c;我将涵盖以下内容&#xff1a; 理解Mocking&#xff1a;为何它对于构建强大的测试策略至关重要。探索一些最常见的Mocking库&a…

python教学入门:字典和集合

字典&#xff08;Dictionary&#xff09;&#xff1a; 定义&#xff1a; 字典是 Python 中的一种数据结构&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。字典使用花括号 {} 定义&#xff0c;键值对之间用冒号 : 分隔&#xff0c;每对键值对之间用逗号 …

SQL-Oracle 获取最大值,第二大,第三大,第 N 大值

目录 1、原始数据2、获取最大值记录3、获取第二大值记录4、获取第三大值记录 1、原始数据 select * from test_2024_04_15_001 order by 销量 desc,渠道2、获取最大值记录 select 渠道,销量 from ( select a.渠道, a.销量 from test_2024_04_15_001 a order by a.销量 desc,…

AI边坡监测识别摄像机

AI边坡监测识别摄像机是一种利用人工智能技术进行边坡监测的智能设备&#xff0c;其作用是及时监测边坡变化并识别潜在的滑坡、崩塌等危险情况&#xff0c;以提供及时预警和采取必要的安全措施。这种摄像机通过高清摄像头实时捕捉边坡的图像&#xff0c;并利用AI算法对边坡的形…

实验室三大常用仪器2---函数信号发生器的基本使用方法(笔记)

目录 函数信号发生器的基本使用方法 如何连接函数信号发生器和示波器 实验室三大常用仪器1---示波器的基本使用方法&#xff08;笔记&#xff09;-CSDN博客 实验室三大常用仪器3---交流毫伏表的使用方法&#xff08;笔记&#xff09;-CSDN博客 示波器是用来显示和测量信号的…

Java | Leetcode Java题解之第35题搜索插入位置

题目&#xff1a; 题解&#xff1a; class Solution {public int searchInsert(int[] nums, int target) {int n nums.length;int left 0, right n - 1, ans n;while (left < right) {int mid ((right - left) >> 1) left;if (target < nums[mid]) {ans mi…

阿里云图片处理之 图片样式(裁剪)

文档 : https://help.aliyun.com/zh/oss/user-guide/image-styles?spma2c4g.11186623.0.0.5961fe7aq3111v 需求 : 由于客户端界面展示的图片较多, 而且每个图片都过大并且高清高分辨率的, 导致打开页面时图片加载很慢, 而且是缩略图, 对图片清晰度要求不是那么得高, 因此可以…

ASP.NET MVC企业级程序设计 (商品管理:小计,总计,删除,排序)

目录 效果图 实现过程 1创建数据库 2创建项目文件 3创建控制器&#xff0c;右键添加&#xff0c;控制器 ​编辑 注意这里要写Home​编辑 创建成功 数据模型创建过程之前作品有具体过程​编辑 4创建DAL 5创建BLL 6创建视图&#xff0c;右键添加视图 ​编辑 7HomeCont…

【问题处理】银河麒麟操作系统实例分享,adb读写缓慢问题分析

1.问题环境 处理器&#xff1a; HUAWEI Kunpeng 920 5251K 内存&#xff1a; 512 GiB 整机类型/架构&#xff1a; TaiShan 200K (Model 2280K) BIOS版本&#xff1a; Byosoft Corp. 1.81.K 内核版本 4.19.90-23.15.v2101.ky10.aarch64 第三方应用 数据库 2.问题…

Spring Security详细学习第一篇

Spring Security 前言Spring Security入门编辑Spring Security底层原理UserDetailsService接口PasswordEncoder接口 认证登录校验密码加密存储退出登录 前言 本文是作者学习三更老师的Spring Security课程所记录的学习心得和笔记知识&#xff0c;希望能帮助到大家 Spring Sec…

buuctf——[ZJCTF 2019]NiZhuanSiWei

buuctf——[ZJCTF 2019]NiZhuanSiWei 1.绕过file_get_contents()函数 file_get_contents函数介绍 定义和用法 file_get_contents() 把整个文件读入一个字符串中。 该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持&#xff0c;还会使用内存映射…

python实现将数据标准化到指定区间[a,b]+正向标准化+负向标准化

目录 一、公式介绍 (一)正向标准化公式 (二)负向标准化公式如下 (三)[a,b]取[0,1]的特例 二、构建数据集 三、自定义标准化函数 四、正向标准化 五、负向标准化 六、合并数据 一、公式介绍 将一列数据X标准化到指定区间[a,b] (一)正向标准化公式 nor_X(b-a)*(X-X_…
最新文章