2.6.28内核的进程load_balance

进程负载均衡已经提到过不止一次了,这个特性很重要,因为有多个cpu,我们不能让一个cpu过于空闲,当然也不能让它过于繁忙,这就需要负载均衡来完成,前面写过一篇文章简单说明了一下负载均衡的策略,主要就是不能太频繁做这件事,而且原则就是能不做尽量不做,在做负载均衡的时候,有个cpu_load数组很重要,那篇文章很细化,本文将从大框架上理解新内核的负载均衡的思想。

众所周知,2.6.22内核以后就陆续引入了nohz这种节能的节拍方式,大体来说就是如果没有任务的话,就不再频繁的时钟中断了,而是将cpu彻底停掉,呵呵,当然不能掉电,而是执行halt,这个halt只有中断可以唤醒,nohz重要的就是将时钟中断停掉,那么何时打开呢?新内核实现了hrtimer,这种时钟非常精确,而且时钟硬件也在进化,通过软硬结合,我们可以将硬件编程,然后扫描系统中该cpu所属的所有排队的hrtimer,请求这个cpu的控制时钟在这个hrtimer队列中最早到期的hrtimer到期时来一次中断,这样就可以了,不过这种方式要考虑的东西不仅仅是这些,所有的触发机制都要用hrtimer实现,从代码中可以看到,原来在时钟中断中的处理现在都搬到了一个hrtimer中了,就连进程的睡眠唤醒也搬到了hrtimer里面,cpu只要开始执行idle之前就会判断是否要停掉cpu的时钟,如果最近的触发时间离现在很远,那么停掉时钟会省下不少电,另外,在每次中断完成,也要检查是否有进程需要运行,如果需要,那么不再halt,而是调度进程,并且排入一个时钟,什么时钟呢?就是这个进程运行时间够了时要中断它一下,否则它就要一直运行了,以前都是靠周期性的时钟来完成的,现在是nohz了,所以就必须手工对硬件编一次程序,其实就是排入一个hrtimer,那么这个进程运行多久怎么计算呢?看看cfs的调度,里面有,如果是普通的调度算法,比如O(1),那么就是时间片了。

另外对于nohz,还有一个重要的东西,就是idle load balance,简称ilb,这个过程就是在多个cpu中,不能全部都进入nohz,因为这样的话,可能时间长了,各个cpu负载会极端的不均衡,因此必须有一个cpu看家,这样别的cpu才能安心睡眠,否则所有的cpu都只能等待下次的唤醒事件了,其实别的cpu都进入nohz而长眠了,那么那些cpu上就已经没有什么可以运行的进程了,因此load_balance的意义不是很大。这里的意义在于,虽然别的cpu进入nohz了,那仅仅意味着其时钟不再是周期的了,只要有进程运行,那么就有必要进行load_balance。如果系统所有的cpu都无事可做了,那么这个ilb也就该去睡觉了。

再解释一下上面的这一段的后半部分,为何ilb要帮别的cpu做load_balance呢?因为别的cpu都长睡了,进入nohz了,那么cpu都进入长睡了,那还需要负载均衡干甚,其实需要负载均衡的不是这些进入nohz的cpu,而是和它们一个domain的cpu们,也就是一个domain中还没有长睡的cpu们需要负载均衡,本来这些事情都是需要这个长睡的cpu做的,这下倒好,叫起它没有必要,这就违背了nohz的初衷了,于是必然有一个进程来承担这一切阿,于是这就是ilb进程,其实就是一个cpu上的idle进程不进入nohz,而是做这个负载均衡。

static inline void trigger_load_balance(struct rq *rq, int cpu)

{

#ifdef CONFIG_NO_HZ //如果我们进入tick的时候有事可做,那么就不再作为ilb了,ilb只有idle可以

if (rq->in_nohz_recently && !rq->idle_at_tick) {

rq->in_nohz_recently = 0;

if (atomic_read(&nohz.load_balancer) == cpu) {

cpu_clear(cpu, nohz.cpu_mask);

atomic_set(&nohz.load_balancer, -1);

}

if (atomic_read(&nohz.load_balancer) == -1) {

int ilb = first_cpu(nohz.cpu_mask); //寻找一个ilb

if (ilb < nr_cpu_ids)

resched_cpu(ilb); //这个函数会发送ipi,从而唤醒那个选出来的进程,然后执行idle load balance

}

} //如果系统所有的cpu都进入nohz,那么就不做load_balance了

if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) == cpu && cpus_weight(nohz.cpu_mask) == num_online_cpus()) {

resched_cpu(cpu);

return;

} //如果这个cpu是idle,而且它不是ilb,那么它就不必进行load_balance了

if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) != cpu && cpu_isset(cpu, nohz.cpu_mask))

return;

#endif

if (time_after_eq(jiffies, rq->next_balance))

raise_softirq(SCHED_SOFTIRQ);

}

可以看出ilb设计的精妙,idle再也不idle了,它也有了自己的任务,就是负载均衡。主要上面的倒数第二个和第三个if判断,如果该cpu不是idle状态,那么也可以做laod_balance,这几代内核将laod_balance作为softirq来执行了,效率自然有些不错了,以往都是在时钟中断或者schedule调度器中执行的,很延迟的,那么系统中就会有idle balance和non-idle balance两种负载均衡,后者是传统的负载均衡,而前者是应对nohz的负载均衡。有人提出一个补丁,说是将timer迁移到ilb所在的cpu上,这样就可以通过将timer的到期和ilb的周期唤醒的时间重合来减少在别的cpu上timer的唤醒,从而可以最小限度的影响nohz的状态,达到节能,这个想法是不错的,大动timer的代码不现实,于是就专门写了一个迁移timer的函数来执行就可以了,这样本来需要在5秒后由于timer到期而被唤醒的nohz状态的cpu的这个timer可以被迁移到这个ilb所在的cpu上,从而那个cpu不用被唤醒了,由于timer的精度小,这个cpu恰好周期执行的时候它到期,非常不错,不过按照上面的那个函数,如果这个ilb需要执行timer了,那么是需要再选出一个cpu作为ilb的执行者呢,还是让这个cpu执行完timer之后继续作为ilb的cpu,测试见分晓。

最后想一下,既然有non-idle balance,那么就可以说明这些执行non-idle balance的cpu,也就是没有进入nohz睡眠的cpu自己就可以进行负载均衡,那么还何必用这些进入nohz的cpu参与呢?这里有两个意义,第一个就是load_balance是负载均衡的“拉”操作,也就是主动地将别的比较忙的cpu上的进程拉到这个进行load_balance的cpu上,第二个意义就是如果和进入nohz的cpu同domain的cpu们有一个或者多个都很忙的话,那么是继续为了省电而让别的cpu置身于繁忙的工作呢还是唤醒这个处于idle-nohz的cpu分担一些工作呢?答案当然是后者,因为如果就是为了省电,那么你完全可以生活在男耕女织的时代,既然系统里面有那么多的cpu,那么就要有效利用,那么这种情况下,很抱歉,长睡的进程必须醒来来分担一些工作了。拉方式的负载均衡就是尝试在同调度域选择一个最繁忙的cpu组,然后在这个组选择一个最繁忙的cpu,最后将这个忙cpu上的任务分给这个执行拉平衡的cpu,在ilb的方式下,是ilb owner来代替所有的处于长睡的cpu执行load balance的,那么执行拉平衡的cpu是ilb所在的cpu可是为忙cpu分担任务的cpu却是这个被代理的处于长睡的cpu,一旦迁移成功,那么马上就会用ipi唤醒这个处于nohz的长睡进程,于是这个进程就不再睡了,马上投入工作

你可能感兴趣的:(工作,timer,负载均衡,struct,domain,任务)