CPU域的初始化
内核中有一个数据结构struct sched_domain_topology_level来描述cpu的层次关系,简称SDTL层级。
struct sched_domain_topology_level {
sched_domain_mask_f mask;
sched_domain_flags_f sd_flags;
sched_domain_energy_f energy;
int flags;
int numa_level;
struct sd_data data;
#ifdef CONFIG_SCHED_DEBUG
char *name;
#endif
};
static struct sched_domain_topology_level arm64_topology[] = {
#ifdef CONFIG_SCHED_SMT
{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) },
#endif#ifdef CONFIG_SCHED_MC
{ cpu_coregroup_mask, cpu_corepower_flags, cpu_core_energy, SD_INIT_NAME(MC) },
#endif
{ cpu_cpu_mask, cpu_cpu_flags, cpu_cluster_energy, SD_INIT_NAME(DIE) }, { NULL, },};
不同的flag代表不同的层级,以及sd的能力
cpu_cpu_flags-------------
static int cpu_cpu_flags(void){
return SD_ASYM_CPUCAPACITY;
}
cpu_corepower_flags-------------
static inline int cpu_corepower_flags(void){
return SD_SHARE_PKG_RESOURCES | SD_SHARE_POWERDOMAIN | SD_SHARE_CAP_STATES;
}
供EAS调度需要,计算energy
cpu_cluster_energy--------------
static inlineconst struct sched_group_energy * const cpu_cluster_energy(int cpu){
struct sched_group_energy *sge = sge_array[cpu][SD_LEVEL1];
}
cpu_core_energy----------
static inlineconst struct sched_group_energy * const cpu_core_energy(int cpu){
struct sched_group_energy *sge = sge_array[cpu][SD_LEVEL0];
}
cpu_possible_mask:表示系统中有多少可以运行的cpu的核心。
cpu_online_mask:表示系统中有多少正在处于运行状态的cpu核心。
cpu_present_mask:表示系统中有多少具备online条件的cpu核心,它们不一定处于online状态,有的cpu可能被热插拔了。
cpu_active_mask:表示系统中有多少活跃的cpu核心。
使用cpu_active_mask做cpu调度域的初始化
在一个4核处理器中,每个物理cpu核心拥有独立了L1 Cache且不支持超线程技术,分成两个cluster0和cluster1,每个cluster包含两个物理cpu核, cluster中cpu核共享L2 Cahce.
在linux内核里构建cpu带调度域和拓扑关系图的一些原则。
1.根据cpu物理属性分层次,从上到下,由SMT->MC->DIE的递进关系来分层,用数据结构struct sched_domain_topology_level来描述,简称SDTL层级。
2.每个SDTL层级都为调度域和调度组都建立一个per-cpu变量,并且为每个cpu分配相应的数据结构。
3.在同一个SDTL层级中,由于芯片设计决定哪些cpu是兄弟关系。调度域中有span成员来描述,调度组中cpumask来描述兄弟关系。
4.同一个cpu的不同SDTL层级的调度域有父子关系。每个调度域里面包含了相应的调度组并且这些调度组串联成一个链表,调度域的groups成员是链表头。
SMP负载均衡
每次系统处理调度tick时,会通过软中断的方式检查当前是否需要处理SMP负载均衡。
scheduler_tick->trigger_load_balance->run_rebalance_domain-> rebalance_domains
rebalance_domains函数:
如果定义了ENERGY_AWARE判断是否发生sd_overutilized,如果没有sd overutilized,表明负载在合理的区间,不需要负载均衡,就返回。
sd overutilized的判断—sd对应的所有sg中至少有一个overutilized。(sd跟sg的关系参考上一页)
获取做负载均衡的时间间距—interval(会根据情况变化),如果两次tick的时间大于interval则,做负载均衡load balance。
load_balance 将sd的busiest sg中的负载拉到local cpu上
分为以下的几个步骤:
1.should_we_balance 判断local的cpu是否能做balance
调度组中的第一个cpu或idle的cpu可以做balance
2.find_busiest_group 找到sd中最忙的调度组
根据busiest的group type判断是否要force balance
通过calculate_imbalance计算出busiest sg的imbalance。
3.find_busiest_queue 找到busiest sg中的最忙的cpu(rq)
4.detach_task
遍历最忙的rq的cfs_tasks,can_migrate_task(查看当前的p是否可以移动到target cpu).并计算task的load,如果load/2>env_imbalance. detach_task,并且enc->imbalance-=load,env<0退出detach.
然后attach上一步detach的task。
5.还要补充一些情况,如果没有出现进程的移动,还需要查看。
need_active_balance? 分为以下几种情况:
- 如果当前cpu即dst cpu,idle状态为CPU_NEWLY_IDLE,且sd的flag设置了SD_ASYM_PACKING且src cpu的mask大于dst cpu的。ASYM_PACKING是为了 将higher numbered cpus的task压到lower numbered cpus.
- 如果当前cpu即dst cpu,idle状态为CPU_NEWLY_IDLE, CPU_IDLE,且 src cpu的cfs task只有一个,且src的cpu_capcityimbalance_pct小于src的cpu_capacity_orig100(说明src cpu的RT DL调度占用了很多capacity)
----cpu_capacity_orig为当前cpu最高的计算能力*该cpu最大的频率/系统中最大的cpu频率。
----cpu_capcity为cpu在cfs调度类的计算能力cpu_capcity = cpu_capacity_orig - RT和DL调度类的计算能力。 - src-cpu_capcity 小于dst-cpu_capcity 且src-cpu_capacity_orig 小于dst-cpu_capacity_orig且src的cfs rq上只有一个task且src cpu overutilized而dst cpu没有overutilized。
- 累计balance失败的次数nr_balance_failed>cache_nice_tries+2.表示失败的次数过多需要force balance了。
stop_one_cpu_nowait使用cpu stopper去停掉当前cpu(busiest的cpu)正在执行的任务,并执行回调函数,active_load_balance_cpu_stop,将busiest的cpu上的仅有的一个cfs task push到dst cpu上。
enum group_type {
group_other = 0,
group_misfit_task,
group_imbalanced,
group_overloaded,
};
group_misfit_task:表示该task占用了内核过多的资源,该task不适合(misfit)当前运行的cpu.
条件:capacity_of(cpu)/capacity_marign(1525)>util(task的load)/1024
group_imbalanced:表示有上次没有处理(移动)的imbalance
条件:由于task_allowed,有些imbalance不能移动到target cpu上.如果env.flags为LBF_SOME_PINNED且env-imbalance>0,设置sd(或者sd的parent)为imbalance.
group_overloaded:示该sg负载过重了
条件:group_is_overload(group_capacity100<group_utilimbalance_pct(确定值,初始化为125).
#define LBF_ALL_PINNED 0x01
#define LBF_NEED_BREAK 0x02
#define LBF_DST_PINNED 0x04
#define LBF_SOME_PINNED 0x08
LBF_ALL_PINNED表示:所有的imbalance都没处理(所有需要move的task都pinned)
LBF_SOME_PINNED表示:处理(移动)了一部分imbalance,还有一部分因为task_allowed,没有处理完设置状态为LBF_SOME_PINNED.
LBF_NEED_BREAK表示:处理(移动)了很多的task(个数超过loop_break),休息下接着处理.
LBF_DST_PINNED 表示:target(dst) cpu不是task_allowed,将imbalance移动到其他同组的cpu.
判定顺序LBF_NEED_BREAK->LBF_DST_PINNED->LBF_SOME_PINNED->LBF_ALL_PINNED
find_busiest_group
1.update_sd_lb_stats
2.calculate_imbalance
update_sd_lb_stats 更新sd的负载,遍历sd的sg找到busiest sg并更新group type
1.get_sd_load_idx根据该sd的idle状态,获取load index。idle分为以下几种:
enum cpu_idle_type{
CPU_IDLE,
CPU_NOT_IDLE,
CPU_NEWLY_IDLE,
CPU_MAX_IDLE_TYPES
}
主要是根据sd的这几种不同状态来完成负载的计算。
2.update_sg_lb_stats更新sg中的负载,group type。
3.update_sd_pick_busiest找到该sd中最忙的sg
calculate_imbalance计算负载不均衡imbalance的值
计算方法 具体还是看代码
fix_small_imbalance计算最小的负载不均衡的值
计算方法 具体还是看代码
can_migrate_task
查看那些task不适合移动
1.throttled_lb_pair?
2.由于进程允许运行的cpu的位图的限制(cpu_allowd)
3.当前进程正在运行
4.cache_hot
当前进程正在运行的判定task_struct.on_cpu来判定,task运行的时候会设置task_struct.on_cpu为1,退出的时候设置为0.
cache hot的判定
1.Dst task cfs rq中有进程且task为当前进程的上一个或者下一个执行的进程。
2.当前时间距离task上次开始执行的时间间隔小于sysctl_sched_migration_cost(5000000ns)
唤醒进程
wake_up_process
1.WALT算法–涉及到EAS的,会在EAS中介绍。
- walt_update_task_ravg(rq->curr, rq, TASK_UPDATE, wallclock, 0);
- walt_update_task_ravg(p, rq, TASK_WAKE, wallclock, 0);
2.通过select_task_rq_fair(CFS)选择一个合适的cpu运行wakeup的进程。
3.最后让wakeup的进程在选定的cpu上运行起来。
主要来看select_task_rq_fair
wake_affine:希望把被唤醒进程尽可能运行在wakeup cpu上,这样可以让一些有相关性的进程尽可能的运行在具有cache共享的调度域中,获得一些cache-hit带来的性能提升。
waker:一个正在运行的进程通过调用wake_up_process等唤醒函数来唤醒另外一个睡眠状态的进程。
wakee:表示将要被唤醒的进程。
当进程A唤醒另外一个进程B时,会调用record_wakee函数来比较,如果发现进程A上次唤醒的不是进程B,那么wakee_flips++.wakee_flips表示waker在切换不同唤醒进程(wakee),这个值越大说明waker唤醒了多个wakee,唤醒频率就越高。
wake_wide返回ture,说明wakeup cpu已经频繁地去唤醒了好多进程,因此不适宜继续把唤醒进程拉倒自己的cpu中。
主要通过record_wakee§和wake_wide(record_wakee)函数来实现该功能。
want_affine
需要的条件:
1.wake_wide
防止多个进程在一个cpu上唤醒。
2.wake_cap
Disable WAKE_AFFINE in the case where task @p doesn‘t fit in the capacity of either the waking CPU @cpu or the previous CPU @prev_cpu。
3. 查看当前cpu是否包含于进程的cpus_allowed。
energy_aware?
宏CONFIG_DEFAULT_USE_ENERGY_AWARE 例如 sprd_sharkl5_defconfig:CONFIG_DEFAULT_USE_ENERGY_AWARE=y
如果设置了,会调用EAS的方法选择适合的cpu。涉及到EAS的,会在EAS中介绍。
从上到下遍历wakeup cpu的调度域。
如果wakeup cpu和prev cpu在一个调度域且这个调度域包含SD_WAKE_AFFINE标志位,那么affine_sd调度域具有亲和性。
如果affine_sd调度域具有亲和性,那么通过wake_affine函数计算wake_up cpu和prev cpu的负载情况。如果wake_up cpu的负载加上唤醒进程的负载比prev cpu的负载小,那么wakeup cpu是可以唤醒进程的。
select_idle_sibling函数优先选择idle cpu,如果没有找到idle cpu,那么只能选择prev cpu和wakeup cpu.
select_idle_sibling
首先看target cpu是否是idle,如果是直接返回。
其次prev cpu和target不为同一个cpu,且prev cpu和target cpu具有cache亲缘性,且prev cpu为idle那么返回prev cpu。
pre_cpus_share_cache
判断两个cpu是否有cache亲缘性–id = cpumask_first(sched_domain_span(sd)); per_cpu(sd_llc_id, cpu) = id;
再次遍历MC层级中的每一个sd和sg找到idle cpu,如果idle cpu为task allowed则返回。
如果没有wake_affine且sd_flag设置了SD_BALANCE_WAKE的情况:
那么找一个最空闲的cpu
find_idlest_cpu
首先遍历sd对应的sg链表,先找到最闲的sg:
find_idlest_group
sd中找到最闲的group–(avg_load * SCHED_CAPACITY_SCALE) / group->sgc->capacity;
其次在找到的sg中找最悠闲的cpu:
find_idlest_group_cpu
遍历调度组中的cpu,如果cpu idle,找一个调度组中cpu的exit_latency最小的cpu,或者找一个调度组中cpu闲置最久的cpu。
如果没有idle的cpu,找一个负载最小的cpu。