Linux SMP负载均衡

本文详细探讨了Linux SMP(对称多处理)系统的负载均衡工作原理,包括CPU亲和性和调度器如何在多核处理器中分配任务,以实现高效资源利用和系统性能优化。通过了解负载均衡策略,如轮转调度和CPU亲和性设置,读者可以更好地理解和调整Linux环境下的多线程应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

周期性调度器scheduler_tick周期性调度器由中断实现,系统定时产生一个中断,然后启动周期性调度器,周期性调度器执行过程中要关闭中断, 周期性调度器执行完毕后再打开中断。当周期性调度器的相关统计量rq->next_balance到时触发负载均衡。在trigger_load_balance中可以发现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。在触发负载平衡后触发软中断(SCHED_SOFTIRQ),软中断的处理函数为run_rebalance_domains,首先自下而上遍历cpu所属的sched domain,对其进行负载均衡.如果本cpu负责idle load balance,代替停用周期时钟的cpu执行load balance。
//	部分参考:http://blog.youkuaiyun.com/dog250/article/details/5303561
//	负载均衡
//		当rq->next_balance到时,触发负载均衡	
//	调用路径:scheduler_tick->trigger_load_balance
//	注:
//		nohz.cpu_mask中的cpu表示停用了周期时钟
//	函数任务:
//		1.如果进入tick的时候rq变得有事可做,并且之前由本cpu执行idle load balance
//			1.1 不再做idle load balance
//			1.2 nohz.cpu_mask中选择一个cpu负责idle load balance
//			1.3 通过ipi通知彼cpu负责ilb
//		2.如果所有cpu均处于idle状态,之前由本cpu做idle load balance
//			2.1 没有必要再做idb,通知本cpu停止idle load balance
//		3.如果本cpu处于idle状态,其他cpu做idle load balance
//			3.1 ilb的cpu会代此cpu执行load balance,不需要raise SCHED_SOFTIRQ
//		4.本cpu没有加入到任何domain,则不需要在domain间load balance
//		5.如果到达执行load balance时间点
//			5.1 raise SCHED_SOFTIRQ
1.1 static inline void trigger_load_balance(struct rq *rq, int cpu)
{
#ifdef CONFIG_NO_HZ
	//	rq->idle_at_tick = 0表示rq上运行的非idle进程
	//	rq->in_nohz_recently表示最近关闭了周期时钟
	if (rq->in_nohz_recently && !rq->idle_at_tick) {
		rq->in_nohz_recently = 0;
		//如果之前由本cpu执行idle load balance
		if (atomic_read(&nohz.load_balancer) == cpu) {
			//nohz.cpu_mask中的cpu表示停用了周期时钟,在select_nohz_load_balancer中被加入
			cpumask_clear_cpu(cpu, nohz.cpu_mask);
			//进入tick的时候rq变得有事可做,则不再做idle load balance
			atomic_set(&nohz.load_balancer, -1);
		}
		//从nohz.cpu_mask中选择一个cpu做idle load balance 
		if (atomic_read(&nohz.load_balancer) == -1) {
			int ilb = find_new_ilb(cpu);
			if (ilb < nr_cpu_ids)
			{
				//向该cpu发送ipi
				resched_cpu(ilb);
			}
		}
	}
	//如果所有cpu均处于idle状态,本cpu做idle load balance,则通知本cpu停止idle load balance
	if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) == cpu &&
	    cpumask_weight(nohz.cpu_mask) == num_online_cpus()) {
		resched_cpu(cpu);
		return;
	}
	//如果本cpu处于idle状态,其他cpu做idle load balance,则不需要raise其SCHED_SOFTIRQ
	if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) != cpu &&
	    cpumask_test_cpu(cpu, nohz.cpu_mask))
		return;
#endif
	//本cpu没有加入到任何domain,则不需要raise其SCHED_SOFTIRQ
	if (time_after_eq(jiffies, rq->next_balance) &&
	    likely(!on_null_domain(cpu)))
	{
		//raise其SCHED_SOFTIRQ
		raise_softirq(SCHED_SOFTIRQ);
	}
}
//	负载均衡软中断
//		由trigger_load_balance函数触发
//	函数任务:
//		1.自下而上遍历cpu所属的sched domain,对其进行负载均衡
//		2.如果本cpu负责idle load balance,代替停用周期时钟的cpu执行load balance
//			2.1 遍历nohz.cpu_mask中所有的idle cpu
//				2.2.1 代其执行步骤1
//				2.2.2 如果这这段时间内本cpu有非idle进程就绪,退出ilb,下一次负载均衡时发生时再ilb
//				2.2.3 如果idle cpu下一次进行负载均衡的时间戳大于本cpu
//					2.2.3.1 更新idle cpu下一次负载均衡的时间为本cpu进行负载均衡的时间戳
2.1 static void run_rebalance_domains(struct softirq_action *h)
{
	int this_cpu = smp_processor_id();
	struct rq *this_rq = cpu_rq(this_cpu);
	//cpu当前状态
	//	如果rq上当前运行的为idle task则cpu为idle状态
	enum cpu_idle_type idle = this_rq->idle_at_tick ?
						CPU_IDLE : CPU_NOT_IDLE;
	//为cpu在同一个domain内执行load balance
	rebalance_domains(this_cpu, idle);
#ifdef CONFIG_NO_HZ
	//如果本cpu负责idle load balance,代替停用周期时钟的cpu执行load balance
	if (this_rq->idle_at_tick &&
	    atomic_read(&nohz.load_balancer) == this_cpu) {
		struct rq *rq;
		int balance_cpu;
		for_each_cpu(balance_cpu, nohz.cpu_mask) {
			if (balance_cpu == this_cpu)
				continue;
			//非idle进程就绪,不在执行idle load balance,下一次load balance发生时再ilb
			if (need_resched())
				break;
			//代idle的cpu执行load balance
			rebalance_domains(balance_cpu, CPU_IDLE);
			//更新本rq下一次load balance的时间为所有被代理rq执行load balance中最早的
			rq = cpu_rq(balance_cpu);
			if (time_after(this_rq->next_balance, rq->next_balance))
				this_rq->next_balance = rq->next_balance;
		}
	}
#endif
}
//	负载均衡
//		在cpu所属的sched domain层次结构上执行load balance
//	调用路径:run_rebalance_domains->rebalance_domains
//	函数参数:
//		idle,cpu处于的状态
//			CPU_IDLE,cpu上运行的idle task
//			CPU_NOT_IDLE,cpu上运行的非idle task
//	函数任务:
//		1.自上而下遍历rq所属的所有sched domain
//			1.1 如果此domain不需要执行load balance(没有设置SD_LOAD_BALANCE),则跳过
//			1.2 计算domain执行load balance的时间间隔
//				1.2.1 由domain的balance_interval指定load balance在domain上执行的时间间隔
//				1.2.2 降低非idle状态的cpu通过放大时间间隔降低load balance执行的频率
//				1.2.3 在HZ*NR_CPUS/10时间内,必须对domain执行一次load balance
//			1.3 如果domain的load balance需要串行执行,则获取balance锁
//			1.4 如果当前时间到达domain load balance执行的时间点
//				1.4.1 从同一个domain的其他cpu拉进程到本cpu执行
//				1.4.2 如果成功从其他cpu上拉进程到本cpu,则设置cpu不再为idle状态
//			1.5 如果domain的load balance需要串行执行,则释放balance锁
//			1.6 通过next_balance记录rq下一次执行load balance的时间为其所属domain中
//				最近的load balance时间
//			1.7 如果返回值balance=0,说明已经完成了负载均衡,退出遍历,否则继续1.1
//		2.更新rq下一次执行load balance的时间为其所属domain中最近的load balance时间
3.1 static void rebalance_domains(int cpu, enum cpu_idle_type idle)
{
	int balance = 1;
	struct rq *rq = cpu_rq(cpu);
	unsigned long interval;
	struct sched_domain *sd;
	//rebalance执行最近的时间
	unsigned long next_balance = jiffies + 60*HZ;
	int update_next_balance = 0;
	int need_serialize;
	//遍历cpu所属的所有sched domain
	for_each_domain(cpu, sd) {
		//此domain不需要load balance
		if (!(sd->flags & SD_LOAD_BALANCE))
			continue;
		//domain执行load balance的时间间隔
		interval = sd->balance_interval;
		//降低非idle状态cpu的load balance的执行频率
		//	通过放大load balance执行的最小间隔达到降低频率的目的
		if (idle != CPU_IDLE)
			interval *= sd->busy_factor;
		interval = msecs_to_jiffies(interval);
		if (unlikely(!interval))
			interval = 1;
		//最大间隔为HZ*NR_CPUS/10
		if (interval > HZ*NR_CPUS/10)
			interval = HZ*NR_CPUS/10;
		//此domain的load balance需要串行执行
		need_serialize = sd->flags & SD_SERIALIZE;
		if (need_serialize) {
			//试图获取串行load balance失败,则放弃此次load balance
			if (!spin_trylock(&balancing))
				goto out;
		}
		//如果当前时间到达domain load balance执行的时间点
		if (time_after_eq(jiffies, sd->last_balance + interval)) {
			//从同一个domain的其他cpu拉进程到本cpu执行
			if (load_balance(cpu, rq, sd, idle, &balance)) {
				//由于已经从其他cpu上拉来了进程,因此本cpu不在是idle状态
				idle = CPU_NOT_IDLE;
			}
			//更新domain上一次load balance的时间为当前jiffies
			sd->last_balance = jiffies;
		}
		if (need_serialize)
			spin_unlock(&balancing);
out:
		if (time_after(next_balance, sd->last_balance + interval)) {
			//next_balance记录下一次执行load balance最近的时间
			next_balance = sd->last_balance + interval;
			update_next_balance = 1;
		}
		//完成均衡
		if (!balance)
			break;
	}
	//更新rq下一次load balance执行时间为所属domain中下一次load balance最早的时间
	if (likely(update_next_balance))
		rq->next_balance = next_balance;
}

//	负载均衡
//		在sched_domain中进行负载均衡,检查是否可以通过最繁忙的组中迁移一些进程到本cpu
//	函数参数:
//		this_cpu, 其上执行负载均衡的cpu
//		this_rq, 其上执行负载均衡的rq
//		sd, 其上执行负载均衡的sched domain
//		idle, this_cpu的状态
//			CPU_SCHED_IDLE,this_cpu空闲
//			CPU_NOT_IDLE,this_cpu不空闲
//		balance,sd中是否均衡
//	函数任务:
//		1.获取当前在线的cpu到本地
//		2.sched domain中寻找最忙的sched group
//		3.sched group中寻找最忙的cpu
//		4.最忙cpu就绪进程数>=2,从最忙cpu移动进程到this_cpu
//			4.1 关本cpu中断
//			4.2 获取this_cpu, 最忙cpu的rq的锁
//			4.3 移动进程到this_cpu
//			4.4 如果最忙cpu中所有进程均设置亲和性,移动进程失败
//				4.4.1 从掩码中清除最忙cpu,如果掩码不空,则重复步骤2进行负载均衡
//			4.5 如果移动进程成功
//				4.5.1 如果本cpu不是this_cpu,通过ipi唤醒this_cpu重调度
//				4.5.2 更新domain负载均衡失败次数计数器为 0
//		5.最忙cpu就绪进程数<=1,或从最忙cpu移动进程失败
//			5.1 检查是否可以进行主动负载均衡(负载均衡失败次数上限)
//				5.1.1 设置最忙cpu的active_balance标志
//				5.1.2 设置最忙cpu的push_cpu为this_cpu,表示是this_cpu向其发起了主动负载均衡
//				5.1.3 唤醒最忙cpu的migration_thread进程
//				5.1.4 更新domain负载均衡失败次数计数器为 上限-1
//		6.调整负载均衡时间间隔
//			6.1 如果没有发起主动负载均衡,下次尽早到期
//			6.2 否则,推迟下次负载均衡的时间
1.1 static int load_balance(int this_cpu, struct rq *this_rq,
			struct sched_domain *sd, enum cpu_idle_type idle,
			int *balance)
{
	int ld_moved, all_pinned = 0, active_balance = 0, sd_idle = 0;
	struct sched_group *group;
	unsigned long imbalance;
	struct rq *busiest;
	unsigned long flags;
	struct cpumask *cpus = __get_cpu_var(load_balance_tmpmask);
	//在线的cpu
	cpumask_copy(cpus, cpu_active_mask);
	//SD_SHARE_CPUPOWER,Domain members share cpu power
	//SD_POWERSAVINGS_BALANCE, Balance for power savings
	//cpu空闲,共享cpu power,power saving不进行balance
	if (idle != CPU_NOT_IDLE && sd->flags & SD_SHARE_CPUPOWER &&
	    !test_sd_parent(sd, SD_POWERSAVINGS_BALANCE))
		sd_idle = 1;
redo:
	//在sched domain中寻找最忙的sched group
	group = find_busiest_group(sd, this_cpu, &imbalance, idle, &sd_idle,
				   cpus, balance);
	//sched group之间已经均衡
	if (*balance == 0)
		goto out_balanced;

	//在sched group中寻找最忙的rq
	busiest = find_busiest_queue(group, idle, imbalance, cpus);
	
	//rq之间已经均衡
	if (!busiest) {
		goto out_balanced;
	}

	ld_moved = 0;
	//最忙cpu可运行的进程>=2,从最忙cpu移动进程到本cpu
	if (busiest->nr_running > 1) {
		//关本cpu中断
		local_irq_save(flags);
		//同时获取本rq和最忙rq的锁
		double_rq_lock(this_rq, busiest);
		//从最忙rq移动进程到本rq
		ld_moved = move_tasks(this_rq, this_cpu, busiest, imbalance, sd, idle, &all_pinned);
		double_rq_unlock(this_rq, busiest);
		local_irq_restore(flags);
		//成功移动进程到this_cpu,但运行负载均衡的cpu非当前cpu,唤醒this_cpu,
		if (ld_moved && this_cpu != smp_processor_id())
			resched_cpu(this_cpu);
		//最忙cpu中的进程全部设置亲和性被绑定
		if (unlikely(all_pinned)) {
			//不在考虑此最忙的cpu
			cpumask_clear_cpu(cpu_of(busiest), cpus);
			//继续在sched domain中寻找忙碌的cpu
			if (!cpumask_empty(cpus))
				goto redo;
			//domain中的所有cpu都不能进行负载均衡,退出
			goto out_balanced;
		}
	}
	//最忙cpu可运行的进程<=1或者移动进程失败
	if (!ld_moved) {
		//sd->nr_balance_failed > sd->cache_nice_tries+2时,启动主动负载均衡
		//	主动负载均衡:
		//		最忙的cpu主动向空闲cpu搬移进程
		//		步骤:
		//			1.从lowest-level scheduling domain遍历每一个CPU GROUP中的cpu
		//				1.1 如果cpu idle,则向其迁移一个进程
		//				1.2 继续寻找idle的cpu
		//			2.当最忙的cpu遍历完该scheduling domain中的每一个CPU GROUP的每一个cpu
		//				2.1 向higher-level scheduling domain,直到其只剩两个进程或者遇到
		//					没有设置SD_LOAD_BALANCE的scheduling domain
		if (need_active_balance(sd, sd_idle, idle)) {
			//关中断,获取最忙cpu rq的锁
			raw_spin_lock_irqsave(&busiest->lock, flags);
			//this_cpu不在最忙cpu允许的域中
			if (!cpumask_test_cpu(this_cpu,
					      &busiest->curr->cpus_allowed)) {
				raw_spin_unlock_irqrestore(&busiest->lock,
							    flags);
				//等价于全部设置了亲和性
				all_pinned = 1;
				goto out_one_pinned;
			}
			//发起主动负载均衡
			if (!busiest->active_balance) {
				//设置最忙cpu的active_balance标志
				busiest->active_balance = 1;
				//向其发起主动负载均衡的cpu
				busiest->push_cpu = this_cpu;
				active_balance = 1;
			}
			raw_spin_unlock_irqrestdore(&busiest->lock, flags);
			//唤醒最忙cpu的migration_thread进程
			if (active_balance)
				wake_up_process(busiest->migration_thread);
			//重置负载均衡失败次数
			sd->nr_balance_failed = sd->cache_nice_tries+1;
		}
	}
	else
	{
		//成功进行了负载均衡,nr_balance_failed设置为0
		sd->nr_balance_failed = 0;
	}
		
	//调整负载均衡时间间隔
	if (likely(!active_balance)) 
	{
		//没有发起主动负载均衡,下次尽早到期
		sd->balance_interval = sd->min_interval;
	} else {
		//推迟下一次负载均衡的时间
		if (sd->balance_interval < sd->max_interval)
			sd->balance_interval *= 2;
	}

	if (!ld_moved && !sd_idle && sd->flags & SD_SHARE_CPUPOWER &&
	    !test_sd_parent(sd, SD_POWERSAVINGS_BALANCE))
		ld_moved = -1;

	goto out;

out_balanced:
	schedstat_inc(sd, lb_balanced[idle]);

	sd->nr_balance_failed = 0;

out_one_pinned:
	
	if ((all_pinned && sd->balance_interval < MAX_PINNED_INTERVAL) ||
			(sd->balance_interval < sd->max_interval))
		sd->balance_interval *= 2;

	if (!sd_idle && sd->flags & SD_SHARE_CPUPOWER &&
	    !test_sd_parent(sd, SD_POWERSAVINGS_BALANCE))
		ld_moved = -1;
	else
		ld_moved = 0;
out:
	if (ld_moved)
		update_shares(sd);
	return ld_moved;
}

//	寻找sched domain中最忙的group
//	函数参数:
//		sd:待查找的sched domain
//		this_cpu:当前正在对其执行负载均衡的cpu
//		imbalance:为达到平衡需要移动的权重
//		idle:this_cpu当前的状态
//		sd_idle: sd空闲状态
//		cpus:可作为源cpu的集合
//		balance:指示this_cpu是否适合负载均衡
//	返回值:
//		如果存在不均衡,返回最忙的group
//		否则,如果用户建议power-savings balance,返回最不忙的group,
//			通过将其中cpus的进程移动到本group,使其idle
//	函数任务:
//		1.计算sd的负载信息
//		2.根据统计信息,决定是否进行负载均衡
//			2.1 this_cpu不适合在sd中进行均衡,则返回
//			2.2 没有最忙的group,或者最忙group可运行进程数为0,则返回
//			2.3 this_cpu所在group的负载大于最忙group的负载,则返回
//			2.4 计算sched domain的平均负载
//				公式:(SCHED_LOAD_SCALE * sds.total_load) / sds.total_pwr
//				2.4.1 如果this_group的负载大于等于平均负载,则返回
//			2.5 this_cpu所在group的负载阈值超过了最忙group的负载阈值,则返回
//				this_cpu所在group的负载阈值计算公式:sd->imbalance_pct * sds.this_load
//			2.6 运行到此处,说明存在失衡,计算失衡的负载量(即需要移动的负载数)
//			2.7 返回最忙的group
//		3.如果sd负载没有失衡,计算是否可以通过负载均衡来省电
//			3.1 返回最不忙的group
1.1 static struct sched_group *find_busiest_group(struct sched_domain *sd, int this_cpu,
		   unsigned long *imbalance, enum cpu_idle_type idle,
		   int *sd_idle, const struct cpumask *cpus, int *balance)
{

	struct sd_lb_stats sds;
	memset(&sds, 0, sizeof(sds));
	//计算sd的负载
	update_sd_lb_stats(sd, this_cpu, idle, sd_idle, cpus,
					balance, &sds);
	//this_cpu不适合在sd中进行均衡,则返回
	if (!(*balance))
		goto ret;
	//没有最忙的group,或者最忙group可运行进程数为0,则返回
	if (!sds.busiest || sds.busiest_nr_running == 0)
		goto out_balanced;

	//this_cpu所在group的负载大于最忙group的负载,则返回
	if (sds.this_load >= sds.max_load)
		goto out_balanced;

	//sd的平均权重
	sds.avg_load = (SCHED_LOAD_SCALE * sds.total_load) / sds.total_pwr;
	//this_cpu所在group负载大于sd的平均负载,则返回
	if (sds.this_load >= sds.avg_load)
		goto out_balanced;
	//imbalance_pct,进行负载均衡的阈值
	if (100 * sds.max_load <= sd->imbalance_pct * sds.this_load)
		goto out_balanced;
	//存在失衡,计算需要均衡的负载量
	calculate_imbalance(&sds, this_cpu, imbalance);
	//返回最忙的group
	return sds.busiest;

out_balanced:
	//没有明显的失衡,检查是否可以进行通过负载均衡省电
	if (check_power_save_busiest_group(&sds, this_cpu, imbalance))
		return sds.busiest;
ret:
	*imbalance = 0;
	return NULL;
}

//	计算sched domain负载均衡统计信息
//	函数参数:
//		sd:待计算负载统计信息的sd
//		this_cpu:当前正在对其执行负载均衡的cpu
//		idle:this_cpu的idle状态
//		sd_idle:sd的idle状态
//		cpu:可作为源cpu的掩码
//		balance:指示是否应该进行负载均衡
//		sds:保存统计信息的变量
//	函数任务:
//		1.遍历sched domain中所有的group
//			1.1 计算当前group的负载信息
//			1.2 如果this_cpu在当前group,并且当前group已经均衡,则退出
//			1.3 更新sched domain的负载统计
//				1.3.1 sds->total_load统计所有group的负载
//				1.3.2 sds->total_pwr统计所有group的cpu power
//			1.4 如果sched domain的子domain设置了SD_PREFER_SIBLING标志
//				1.4.1 说明sched domain的sibling之间移动进程
//				1.4.2 降低本group的group_capacity,之后将所有多余进程移动到其他sibling
//			1.5 如果this_cpu属于当前group,更新sched domain中关于this_cpu所在group的记录信息
//			1.6 如果当前group是sched domain中负载最重的group,记录group的负载信息
//				1.6.1 sds->max_load,记录sched domain内负载最重group的负载量
//				1.6.2 sds->busiest,记录sched domain内负载最重group的编号
//			1.7 更新sched domain power saving的信息
//	注:
//		sched domain下所有sched group组织成环形链表的形式。
1.2 static inline void update_sd_lb_stats(struct sched_domain *sd, int this_cpu,
			enum cpu_idle_type idle, int *sd_idle,
			const struct cpumask *cpus, int *balance,
			struct sd_lb_stats *sds)
{
	struct sched_domain *child = sd->child;
	struct sched_group *group = sd->groups;
	struct sg_lb_stats sgs;
	int load_idx, prefer_sibling = 0;

	if (child && child->flags & SD_PREFER_SIBLING)
		prefer_sibling = 1;
	//初始化power saving的信息
	init_sd_power_savings_stats(sd, sds, idle);
	load_idx = get_sd_load_idx(sd, idle);
	//遍历sched domain中所有的group
	do {
		int local_group;
		//判断this_cpu是否当前group中
		local_group = cpumask_test_cpu(this_cpu,
					       sched_group_cpus(group));
		memset(&sgs, 0, sizeof(sgs));
		//计算当前group的负载信息
		update_sg_lb_stats(sd, group, this_cpu, idle, load_idx, sd_idle,
				local_group, cpus, balance, &sgs);
		//this_cpu在本group内,并且group已经均衡,则返回
		if (local_group && !(*balance))
			return;
		//更新sched domain的负载统计信息
		sds->total_load += sgs.group_load;
		sds->total_pwr += group->cpu_power;

		//sched domain中的子sched domain设置SD_PREFER_SIBLING,标识sched domain的sibling之间移动进程
		//降低本group的group_capacity,将所有多余进程移动到其他sibling
		if (prefer_sibling)
			sgs.group_capacity = min(sgs.group_capacity, 1UL);
		//this_cpu属于group,更新sched domain中关于this_cpu所在group的记录信息
		if (local_group) {
			//sds->this_load,this_cpu所在group的负载
			sds->this_load = sgs.avg_load;
			//sds->this,this_cpu所在的group
			sds->this = group;
			sds->this_nr_running = sgs.sum_nr_running;
			sds->this_load_per_task = sgs.sum_weighted_load;
		//当前group的平均负载大于sched domain中已遍历group的最大的负载
		//当前group就绪进程的个数大于group的容量,或者group设置了imb标识
		} else if (sgs.avg_load > sds->max_load &&
			   (sgs.sum_nr_running > sgs.group_capacity ||
				sgs.group_imb)) {
			//更新sched domain中用于记录具有最大负载group的信息
			sds->max_load = sgs.avg_load;
			sds->busiest = group;
			sds->busiest_nr_running = sgs.sum_nr_running;
			sds->busiest_group_capacity = sgs.group_capacity;
			sds->busiest_load_per_task = sgs.sum_weighted_load;
			sds->group_imb = sgs.group_imb;
		}
		//更新sched domain power saving的信息
		update_sd_power_savings_stats(group, sds, local_group, &sgs);
		//继续遍历下一个group
		group = group->next;
	} while (group != sd->groups);
}

//	计算sched group的负载信息
//	函数参数:
//		sd,group所在的sched domain
//		group,当前要计算的group
//		this_cpu,当前对其进行负载均衡的cpu
//		idle,this_cpu的idle状态
//		load_idx,Load index of sched_domain of this_cpu for load calc
//		sd_idle,group所在sched domain的idle状态
//		local_group,指示当前group是否包含this_cpu
//		cpus,可选为源cpu的掩码集合
//		balance:指示是否应该进行负载均衡
//		sgs,收集统计信息的变量
//	函数任务:
//		1.遍历group中的候选cpu
//			1.1 如果cpu有进程运行,更新sched domain为非idle状态
//			1.2 获取cpu的历史负载load
//				1.2.1 如果this_cpu在group内,返回max(cpu->cpu_load[load_idx], rq->load.weight)
//				1.2.2 如果this_cpu不在group内,返回min(cpu->cpu_load[load_idx], rq->load.weight)
//			1.3 更新group的统计信息
//				1.3.1 group->group_load, group的历史负载
//				1.3.2 group->sum_nr_running, group中进程总数
//				1.3.3 group->sum_weighted_load, group的当前负载
//		2.更新group的cpu power
//			2.1 sched domain的cpu power保存在其sd->groups->cpu_power中(即domain包含的第一个group的cpu_power字段)
//			2.2 sd->groups->cpu_power等于子domain的cpu power总和
//		3.计算group的平均负载
//			3.1 公式 avg_load = group_load/group->cpu_power
//		4.计算group每进程负载
//			4.1 公式 avg_load_per_task = sgs->sum_weighted_load / sgs->sum_nr_running
//		5.如果group内cpu最大、最小负载悬殊,设置标识标识group内不均衡
//			5.1 公式 (max_cpu_load - min_cpu_load) > 2*avg_load_per_task
//		6.更新sched group的group_capacity,即能接纳的进程容量
//	注:
//		cpu power用于表示cpu group的能力,不同层次的cpu group具有不同的计算公式:
//			cpu domain: cpu_power = SCHED_LOAD_SCALE
//			physical domain:SCHED_LOAD_SCALE+SCHED_LOAD_SCALE*(cpus_weight(cpumask)-1)/10
//				其中cpus_weight计算物理cpu里的逻辑核个数(超线程)
//			node domain:在相同node domain下所有physical domain的cpu power的总和
1.3 static inline void update_sg_lb_stats(struct sched_domain *sd,
			struct sched_group *group, int this_cpu,
			enum cpu_idle_type idle, int load_idx, int *sd_idle,
			int local_group, const struct cpumask *cpus,
			int *balance, struct sg_lb_stats *sgs)
{
	unsigned long load, max_cpu_load, min_cpu_load;
	int i;
	unsigned int balance_cpu = -1, first_idle_cpu = 0;
	unsigned long avg_load_per_task = 0;
	//this_cpu在当前group
	if (local_group)
		balance_cpu = group_first_cpu(group);
	//最大、最小负载
	max_cpu_load = 0;
	min_cpu_load = ~0UL;
	//遍历group中的候选cpu
	for_each_cpu_and(i, sched_group_cpus(group), cpus) {
		struct rq *rq = cpu_rq(i);
		//非idle状态,并且有进程
		if (*sd_idle && rq->nr_running)
			*sd_idle = 0;
		//this_cpu在group内
		if (local_group) {
			//当前cpu为idle,并且为发现的第一个idle cpu
			if (idle_cpu(i) && !first_idle_cpu) {
				first_idle_cpu = 1;
				//balance_cpu记录this_cpu所在group内的第一个idle cpu
				balance_cpu = i;
			}
			//返回cpu i当前负载和load_idx历史负载记录两者最大的
			load = target_load(i, load_idx);
		} 
		else //this_cpu不在group内
		{
			//返回cpu i当前负载和load_idx历史负载记录两者最大的
			load = source_load(i, load_idx);
			//max_cpu_load,min_cpu_load记录最大、最小负载
			if (load > max_cpu_load)
				max_cpu_load = load;
			if (min_cpu_load > load)
				min_cpu_load = load;
		}
		//更新group的统计量
		//load_idx,历史负载统计
		sgs->group_load += load;
		//sched group中进程总数
		sgs->sum_nr_running += rq->nr_running;
		//cpu_rq(cpu)->load.weight,当前tick的负载统计
		sgs->sum_weighted_load += weighted_cpuload(i);

	}

	//只有当前domain的first idle cpu和first cpu(busiest)合适做load balance
	//CPU_NEWLY_IDLE类型的load balance将总是被允许
	if (idle != CPU_NEWLY_IDLE && local_group &&
	    balance_cpu != this_cpu) {
		//不需要做负载均衡
		*balance = 0;
		return;
	}
	//更新group的cpu power
	update_group_power(sd, this_cpu);

	//计算group的平均负载
	sgs->avg_load = (sgs->group_load * SCHED_LOAD_SCALE) / group->cpu_power;

	//更新group内进程平均负载
	//	sgs->sum_nr_running记录group内进程总数
	if (sgs->sum_nr_running)
		avg_load_per_task = sgs->sum_weighted_load / sgs->sum_nr_running;
	//如果group内最大负载、最小负载悬殊,表示组内不均衡
	if ((max_cpu_load - min_cpu_load) > 2*avg_load_per_task)
		sgs->group_imb = 1;
	//更新sched group的group_capacity,即能接纳的进程容量
	sgs->group_capacity =
		DIV_ROUND_CLOSEST(group->cpu_power, SCHED_LOAD_SCALE);
}

//	计算group在给定domain中的imbalance
//	调用路径:find_busiest_group->calculate_imbalance
//	函数参数:
//		sds:sched domain的统计信息
//		this_cpu:当前正在运行load balance的cpu
//		imbalance:保存imbalance值
//	函数任务:
//		1.计算最忙group内进程的平均负载
//			1.1 公式:最忙group当前的负载量/最忙group当前运行的进程数
//		2.如果最忙group失衡
//			2.1 最忙group内进程的平均负载 = min(最忙group的当前负载,sched doman的平均负载)
//		3.如果最忙group的负载小于domain平均负载,则说明已经平衡,返回
//		4.如果最忙group没有失衡
//			4.1 计算最忙group超过group容量的进程个数
//		5.计算load balance进行pull的load
//			5.1 pull的load量为 min(最忙group超过domain平均负载的量,最忙group的超过容量的负载量)
//		6.计算this_cpu所在group与最忙cpu所在group之间的imbalance量
//			6.1 imbalance量为 min(load balance进行pull的load,当前cpu所在group超过domain平均负载的量)
//		7.如果imbalance不足最忙group中进程的平均负载
//			7.1 进行微调整
static inline void calculate_imbalance(struct sd_lb_stats *sds, int this_cpu,
		unsigned long *imbalance)
{
	unsigned long max_pull, load_above_capacity = ~0UL;
	//计算最忙group内进程当前的平均负载
	sds->busiest_load_per_task /= sds->busiest_nr_running;
	//如果最忙group失衡
	if (sds->group_imb) {
		//则最忙group内进程的平均负载取 min(当前负载、历史负载)
		sds->busiest_load_per_task =
			min(sds->busiest_load_per_task, sds->avg_load);
	}
	//如果最忙group的负载小于平均负载,则说明已平衡
	if (sds->max_load < sds->avg_load) {
		//imbalance=0
		*imbalance = 0;
		return fix_small_imbalance(sds, this_cpu, imbalance);
	}
	//没有group失衡
	if (!sds->group_imb) {
	
		//最忙group超过容量的进程数
		load_above_capacity = (sds->busiest_nr_running -
						sds->busiest_group_capacity);
		load_above_capacity *= (SCHED_LOAD_SCALE * SCHED_LOAD_SCALE);
		load_above_capacity /= sds->busiest->cpu_power;
	}
	//计算load balance进行pull的load
	max_pull = min(sds->max_load - sds->avg_load, load_above_capacity);
	//计算imbalance
	*imbalance = min(max_pull * sds->busiest->cpu_power,
		(sds->avg_load - sds->this_load) * sds->this->cpu_power)
			/ SCHED_LOAD_SCALE;
	//imbalance不足最忙group中进程的平均负载
	if (*imbalance < sds->busiest_load_per_task)
		return fix_small_imbalance(sds, this_cpu, imbalance);
}
//	查找sched group中最忙的group
//	函数任务:
//		1.遍历group内所有在线的cpu
//			1.1 获取cpu对应的rq的当前负载rq->load.weight
//			1.2 如果rq当前只有一个进程,并且负载大于imbalance,继续1.1
//			1.3 通过cpu power计算cpu的负载
//				1.3.1 为方便在不同cpu见进行比较
//			1.4 记录负载最大的cpu
//		2.返回最忙的cpu
//	调用路径:load_balance->find_busiest_queue
2.1 static struct rq *find_busiest_queue(struct sched_group *group, enum cpu_idle_type idle,
		   unsigned long imbalance, const struct cpumask *cpus)
{
	struct rq *busiest = NULL, *rq;
	unsigned long max_load = 0;
	int i;
	//遍历group内的cpu
	for_each_cpu(i, sched_group_cpus(group)) {
		unsigned long power = power_of(i);
		unsigned long capacity = DIV_ROUND_CLOSEST(power, SCHED_LOAD_SCALE);
		unsigned long wl;
		//cpu在线
		if (!cpumask_test_cpu(i, cpus))
			continue;
		//cpu的rq
		rq = cpu_rq(i);
		//rq当前的负载
		wl = weighted_cpuload(i);
		//rq当前只有一个进程,则返回
		if (capacity && rq->nr_running == 1 && wl > imbalance)
			continue;
		//通过cpu power计算cpu的负载
		wl = (wl * SCHED_LOAD_SCALE) / power;
		//记录最大负载的cpu
		if (wl > max_load) {
			max_load = wl;
			busiest = rq;
		}
	}
	//返回最忙的cpu
	return busiest;
}
//	获取cpu power
//	函数任务:
//		1.如果cpu没有对应的group,返回SCHED_LOAD_SCALE
//		2.否则返回对应group的power
2.2 static unsigned long power_of(int cpu)
{
	//获取cpu对应的group
	struct sched_group *group = group_of(cpu);
	//没有对应的group,返回SCHED_LOAD_SCALE
	if (!group)
		return SCHED_LOAD_SCALE;
	//否则返回group的power
	return group->cpu_power;
}




                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值