2.6内核中进程调度模块的负载均衡行为分为“拉”和“推”,推这里不考虑,关于拉均衡有一篇文章特别好,具体出处就不记得了,我当时用的百度快照,那篇文章我认为最精彩的部分就是下面摘录的这段话:
当某个 cpu 负载过轻而另一个 cpu 负载较重时,系统会从重载 cpu 上"拉"进程过来,这个"拉"的负载平衡操作实现在 load_balance() 函数中。load_balance() 有两种调用方式,分别用于当前 cpu 不空闲和空闲两种状态,我们称之为"忙平衡"和"空闲平衡":
无论当前 cpu 是否繁忙或空闲,时钟中断(rebalance_tick()函数中)每隔一段时间(BUSY_REBALANCE_TICK)都会启动一次 load_balance() 平衡负载,这种平衡称为"忙平衡"。
Linux 2.6 倾向于尽可能不做负载平衡,因此在判断是否应该"拉"的时候做了很多限制:
1.系统最繁忙的 cpu 的负载超过当前 cpu 负载的 25% 时才进行负载平衡;
2.当前 cpu 的负载取当前真实负载和上一次执行负载平衡时的负载的较大值,平滑负载凹值;
3.各 cpu 的负载情况取当前真实负载和上一次执行负载平衡时的负载的较小值,平滑负载峰值;
4.对源、目的两个就绪队列加锁之后,再确认一次源就绪队列负载没有减小,否则取消负载平衡动作;
5.源就绪队列中以下三类进程参与负载情况计算,但不做实际迁移:
5.1.正在运行的进程
5.2.不允许迁移到本 cpu 的进程(根据 cpu_allowed 属性)
5.3.进程所在 cpu 上一次调度事件发生的时间(runqueue::timestamp_last_tick,在时钟中断中取值)与进程被切换下来的时间(task_struct::timestamp)之差小于某个阀值(cache_decay_ticks的nanosecond值),--该进程还比较活跃,cache 中的信息还不够凉。
...
以上的论述是基于2.6.2内核的,我看了那篇文章之后,感觉内核负载均衡的设计非常完美,然后我很迫切的翻起代码寻找一些蛛丝马迹,可是却没有找到明显的上面说到的东西,很是郁闷,然后我又研究了一下新内核的代码,发现了linux内核在做负载均衡的时候一个很巧妙的技巧,相比2.6.2内核只能说有过之而无不及。首先先说说负载均衡的策略,和2.6.2一样,也是尽量避免做负载均衡,除非非做不可了,因为负载均衡只是一个优化吞吐量和性能手段,它不是目的,因此没有必要让它占用太多的处理器时间,当然也没有必要提供编程接口了,机器有几个cpu,是否做负载均衡这些信息对用户是不可见的,但是用户可以通过设置一些微调参数从而影响内核负载均衡的行为,这种方式在linux中是很常见的。下面就来说一下新内核的负载均衡,linux有一个特点,如果一个特性是好的,那么即使高版本的内核也要向下兼容它,这里的兼容只是原理上,新版本的内核可能通过一种不同的方式实现这种老的理念,那么我们就在2.6.28内核中找一下2.6.2的负载均衡的影子吧。
在新内核中,有一个函数特别有意思,就是update_cpu_load,这个函数更新了rq数据结构中的cpu_load数组字段,这个cpu_load数组在负载均衡中很重要,它代表了该队列所属cpu的负载,其实负载在cfs之前就是该cpu上运行的进程的数量,在cfs中代表当前cpu上所有的进程的权值之和,不过一个负载为何用一个数组而不仅仅是一个数字呢,这就是技巧所在,linux不仅仅根据当前这次负载均衡时的各个cpu负载来决定如何行动,linux还不丢失cpu以前的负载,因为这个涉及到缓存的热度等等cpu亲和相关的概念,但是负载均衡并不是在一个条件下触发的,它在很多条件下都会被触发,而这些条件对cpu亲和的要求并不相同,比如新创建进程的时候就不用考虑什么亲和的问题,还有如果是在运行idle的时候进行的负载均衡,那可能要考虑cpu亲和,但是考虑的程度绝对没有运行非idle进程时进行的负载均衡考虑cpu亲和的程度高,于是一个数字就不能代表这一切了,必须有个地方保留曾经的cpu负载信息,如果仅仅为了将曾经的负载信息考虑进去,那么也没有必要用一个数组,类比LRU的理论算法,用一个整数就可以了,每个时钟嘀嗒中,原来的cpu负载右移若干位,然后当前的负载加上去,负载均衡的时候用这个值作为参考进行一些变换后(因为要和当前cpu负载的单位一致,如果不变换的话,那么曾经的负载信息就会无故地加到上次时钟嘀嗒时cpu负载的低位上)和当前的cpu负载进行平滑行为就可以了,平滑行为和上述摘录的文章段落一致,我感觉比2.6.2实现的更好,因为2.6.2的实现只是用这次和上次的cpu负载信息进行平滑操作,上次以前的cpu负载信息将失效,虽然不是那么容易,但是还是可能出现大的跳跃。还没有完,上面说不能用一个数来保存cpu负载信息用于负载均衡的原因就在于还要区分不同的情况,比如空闲均衡,忙均衡或者exec均衡等,可是上面想到的那个用移位的方式保存以前的负载信息很绝妙以至于不能不用,不就是还需要保存一个情况信息吗,其实很好办,将不同的情况都用刚才的那个移位算法,每种情况保存一个数字,多种情况就成了一个数组,于是这个cpu_load数组就出来了,我估计作者就是这么想到的,另外为了省去负载均衡时和当时的cpu负载进行平滑操作时还要进行变换,在向cpu_load数组中的元素赋值的时候就做好变换,于是新的算法不用移位实现,用比例实现,也就是说按照不同的情况,也就是数组的不同元素,老的负载信息在cpu_load元素最终结果中所占的比例不同,最后我们来看看这个函数:
static void update_cpu_load(struct rq *this_rq)
{