linux内核SMP负载均衡浅析( 四 )


(调度域可以支持非常复杂的硬件系统,但是我们通常遇到的SMP一般是:一个物理CPU包含N个核心 。这种情况下,所有CPU之间的亲缘性都是相同的,引入调度域的意义其实并不大 。)
进程在两个很亲近的CPU之间迁移,代价较小,因为还有一部分cache可以继续使用;在属于同一NUMA结点上的两个CPU之间迁移,虽然cache会全部丢失,但是好歹内存访问的速度是相同的;如果进程在属于不同NUMA结点的两个CPU之间迁移,那么这个进程将在新NUMA结点的CPU上被执行,却还是要访问旧NUMA结点的内存(进程可以迁移,内存却没法迁移),速度就要慢很多了 。
通过调度域的描述,内核就可以知道CPU与CPU的亲缘关系 。对于关系远的CPU,尽量少在它们之间迁移进程;而对于关系近的CPU,则可以容忍较多一些的进程迁移 。
对于实时进程的负载均衡,调度域的作用比较小,主要是在push_rt_task将当前run_queue中的实时进程推到其他run_queue时,如果有多个run_queue可以接收实时进程,则按照调度域的描述,选择亲缘性最高的那个CPU对应的run_queue(如果这样的CPU有多个,那么约定选择编号最小那一个) 。所以,下面着重讨论普通进程的负载均衡 。
首先,调度域具体是如何描述CPU之间的亲缘关系的呢?假设系统中有两个物理CPU、每个物理CPU有两个核心、每个核心又通过超线程技术虚拟出两个CPU,则调度域的结构如下:

linux内核SMP负载均衡浅析

文章插图
 
1、一个调度域是若干CPU的集合,这些CPU都是满足一定的亲缘关系的(比如至少是属于同一NUMA结点的);
2、调度域之间存在层次关系,一个调度域可能包括多个子调度域,每个子调度域包含了父调度域的一个CPU子集,并且子调度域中的CPU满足比父调度域更严格的亲缘关系(比如父调度域中的CPU至少是属于同一NUMA结点的,子调度域中的CPU至少是属于同一物理CPU的);
3、每个CPU分别具有其对应的一组sched_domain结构,这些调度域处于不同层次,但是都包含了这个CPU;
4、每个调度域被依次划分成多个组,每个组代表调度域的一个CPU子集;
5、最低层次的调度域包含了亲缘性最近的几个CPU、而最低层次的调度组则只包含一个CPU;
对于普通进程的负载均衡来说,在一个CPU上,每次触发load_balance总是在某个sched_domain上进行的 。低层次的sched_domain包含的CPU有着较高的亲缘性,将以较高的频率被触发load_balance;而高层次的sched_domain包含的CPU有着较低的亲缘性,将以较低的频率被触发load_balance 。为了实现这个,sched_domain里面记录着每次load_balance的时间间隔,以及下次触发load_balance的时间 。
前面讨论过,普通进程的load_balance第一步是需要找出一个最繁忙的CPU,实际上这是通过两个步骤来实现的:
1、找出sched_domain下最繁忙的一个sched_group(组内的CPU对应的run_queue的load之和最高);
2、从该sched_group下找出最繁忙的CPU;
可见,load_balance实际上是实现了对应sched_domain下的sched_group之间的平衡 。较高层次的sched_domain包含了很多CPU,但是在这个sched_domain上的load_balance并不直接解决这些CPU之间的负载均衡,而只是解决sched_group之间的平衡(这又是load_balance的一大简化) 。而最底层的sched_group是跟CPU一一对应的,所以最终还是实现了CPU之间的平衡 。
其他问题CPU亲和力
linux下的进程可以通过sched_setaffinity系统调用设置进程亲和力,限定进程只能在某些特定的CPU上运行 。负载均衡必须考虑遵守这个限制(前面也多次提到) 。
迁移线程
前面说到,在普通进程的load_balance过程中,如果负载不均衡,当前CPU会试图从最繁忙的run_queue中pull几个进程到自己的run_queue来 。
但是如果进程迁移失败呢?当失败达到一定次数的时候,内核会试图让目标CPU主动push几个进程过来,这个过程叫做active_load_balance 。这里的“一定次数”也是跟调度域的层次有关的,越低层次,则“一定次数”的值越小,越容易触发active_load_balance 。
这里需要先解释一下,为什么load_balance的过程中迁移进程会失败呢?最繁忙run_queue中的进程,如果符合以下限制,则不能迁移:
1、进程的CPU亲和力限制了它不能在当前CPU上运行;
2、进程正在目标CPU上运行(正在运行的进程显然是不能直接迁移的);
(此外,如果进程在目标CPU上前一次运行的时间距离当前时间很小,那么该进程被cache的数据可能还有很多未被淘汰,则称该进程的cache还是热的 。对于cache热的进程,也尽量不要迁移它们 。但是在满足触发active_load_balance的条件之前,还是会先试图迁移它们 。)


推荐阅读