一文读懂|Linux 进程管理之CFS负载均衡
什么是负载均衡?
前面的调度学习都是默认在单个CPU上的调度策略。我们知道为了CPU之间减少“干扰”,每个CPU上都有一个任务队列。运行的过程种可能会出现有的CPU“忙的一笔”,有的CPU“闲的蛋疼”,于是便需要负载均衡。
将task从负载较重的CPU上转移到负载相对较轻的CPU上执行,这个过程就是负载均衡的过程。
在了解负载均衡前有必要了解soc上对CPU的拓扑关系。
我们知道一个多核心的soc片上系统,内部结构是很复杂的,内核采用CPU拓扑结构来描述一个SOC的架构。内核使用调度域来描述CPU之间的层次关系,对于低级别的调度域来说,CPU之间的负载均衡处理开销比较小,而对于越高级别的调度域,其负载均衡的开销就越大。
比如一个4核心的SOC,两个核心是一个cluster,共享L2 cache,那么每个cluster可以认为是一个MC调度域,每个MC调度域中有两个调度组,每个调度组中只有一个CPU。而整个SOC可以认为是高一级别的DIE调度域,其中有两个调度组,cluster0属于一个调度组,cluster1属于另一个调度组。跨cluster的负载均衡是需要清除L2 cache的,开销是很大的,因此SOC级别的DIE调度域进行负载均衡的开销会更大一些。
CPU对应的调度域和调度组可通过在设备模型文件 /proc/sys/kernel/sched_domain 里查看。
调度域 sched_domain 主要的成员如下:
成员 | 描述 |
---|---|
parent 和 child | sched domain会形成层级结构,parent和child建立了不同层级结构的父子关系。对于base domain而言,其child等于NULL;对于top domain而言,其parent等于NULL。 |
groups | 一个调度域中有若干个调度组,这些调度组形成一个环形链表,groups成员就是链表头 |
min_interval 和 max_interval | 做均衡也是需要开销的,不能时刻去检查调度域的均衡状态,这两个参数定义了检查该sched domain均衡状态的时间间隔的范围 |
balance_interval | 定义了该sched domain均衡的时间间隔 |
busy_factor | 正常情况下,balance_interval定义了均衡的时间间隔,如果cpu繁忙,那么均衡要时间间隔长一些,即时间间隔定义为busy_factor x balance_interval |
imbalance_pct | 调度域内的不均衡状态达到了一定的程度之后就开始进行负载均衡的操作,imbalance_pct定义了不均衡的water mark。 |
level | 该sched domain在整个调度域层级结构中的level |
span_weight | 该sched domain中cpu的个数 |
span | 该调度域的跨度 |
调度组 sched_group 主要的成员如下:
成员 | 描述 |
---|---|
next | sched domain中的所有sched group会形成环形链表,next指向groups链表中的下一个节点 |
group_weight | 该调度组中有多少个cpu |
sgc | 该调度组的算力信息 |
cpumask | 该调度组包含哪些cpu |
CPU拓扑示例
为了减少锁的竞争,每一个cpu都有自己的MC domain、DIE domain(sched domain是分成两个level,base domain称为MC domain(multi core domain),顶层的domain称为DIE domain)以及sched group,并且形成了sched domain之间的层级结构,sched group的环形链表结构。可以通过/sys/devices/system/cpu/cpuX/topology查看cpu topology信息。
在上面的结构中,sched domain是分成两个level,base domain称为MC domain,顶层的domain称为DIE domain。顶层的DIE domain覆盖了系统中所有的CPU,小核cluster的MC domain包括所有小核cluster中的cpu,大核cluster的MC domain包括所有大核cluster中的cpu。
通过DTS和CPU topo子系统,可以构建sched domain层级结构,用于具体的均衡算法。流程是:kernel_init() -> kernel_init_freeable() -> smp_prepare_cpus() -> init_cpu_topology() -> parse_dt_topology()
负载均衡的软件架构
图中可以看出左边主要分为CPU负载跟踪和task负载跟踪。
CPU负载跟踪:考虑每一个CPU的负载。汇聚cluster上所有负载,方便计算cluster之间负载的不均衡状况。 task负载跟踪:判断该任务是否适合当前CPU算力。如果判定需要均衡,那么需要在CPU之间迁移多少的任务才能达到平衡。
右边是通过DTS和CPU topo子系统,构建的sched domain层级结构。流程是:kernel_init() -> kernel_init_freeable() -> smp_prepare_cpus() -> init_cpu_topology() -> parse_dt_topology()
有了左右两边的基础设施,那么什么时候触发负载均衡呢?这主要和调度事件相关,当发生任务唤醒、任务创建、tick到来等调度事件的时候,就可以检查当前系统的不均衡情况,并酌情进行任务迁移,以便让系统负载处于平衡状态。
何时做负载均衡?
CFS任务的负载均衡器有两种。一种是为繁忙CPU们准备的periodic balancer,用于CFS任务在busy cpu上的均衡;一种是为idle cpu们准备的idle balancer,用于把繁忙CPU上的任务均衡到idle cpu上来。
周期性负载均衡(periodic load balance或者tick load balance)是指在tick中,周期性的检测系统的负载均衡状况,找到系统中负载最重的domain、group和CPU,将其上的runnable任务拉到本CPU以便让系统的负载处于均衡的状态。
nohz load balance是指其他的cpu已经进入idle,本CPU任务太重,需要通过 IPI 将其他idle的CPUs唤醒来进行负载均衡。nohz idle load balance也是通过busy cpu上tick驱动的,如果需要kick idle load balancer,那么就会通过GIC发送一个ipi中断给选中的idle cpu,让它代表系统所有的idle cpu们进行负载均衡。
new idle load balance 比较好理解,就是在CPU上没有任务执行,马上要进入idle状态的时候,看看其他CPU是否需要帮忙,来从busy cpu上拉任务,让整个系统的负载处于均衡状态。
负载均衡的基本过程
当一个CPU上进行负载均衡的时候,总是从base domain开始,检查其所属sched group之间的负载均衡情况,如果有不均衡情况,那么会在该cpu所属cluster之间进行迁移,以便维护cluster内各个cpu core的任务负载均衡。
load_balance是处理负载均衡的核心函数,它的处理单元是一个调度域,也就是sched domain,其中会包含对调度组的处理。
在该domain中找到最忙的sched group 在最忙的group中挑选最忙的CPU runqueue,该CPU就成为任务迁移的src 从该队列中选择要迁移的任务(判断的依据主要是task load的大小,优先选择load重的任务) 向着作为dst的CPU runqueue迁移