cfs的负载均衡(1)触发时机

简介

负载均衡的主要问题为:内核如何为一个任务选择一个合适的cpu运行,让系统既不会出现一核有难,八核围观的情况,也让系统效率更高,节能省电。

目前内核在迁移进程的时候,主要在进程非running时进行,有两种情况,一种是进程刚刚创建或者睡眠唤醒,也就是还没有成为就绪态时,内核会为其挑选一个合适的cpu运行,被称为任务放置。一种是已经是就绪态了,但是没有cpu资源,处在runnable状态还没有running时,内核会根据负载情况,将runnable进程迁移到其他cpu运行,本文主要介绍第二种情况的负载均衡,因为这种负载均衡最终都会调用到load_balance, 所以就用load_balance来代表。

本文只粗略介绍load_balance的触发时机。

周期性负载均衡

之前一直有一个疑问,内核到底是在什么时候进行调度的,内核仿佛一直在运行一样,默默看着其他进程运行,记录它们的运行时间,某个时机再去进行调度。但事实上,内核调度更多的,靠的是时钟中断,称为tick, 每过一段时间(一般1/1024 s),时钟都会发送一个中断,触发内核的tick,然后开始进行调度,这里只看负载均衡相关的代码。

scheduler_tick
    trigger_load_balance


void trigger_load_balance(struct rq *rq)
{
	/* Don't need to rebalance while attached to NULL domain */
	if (unlikely(on_null_domain(rq)))
		return;

	if (time_after_eq(jiffies, rq->next_balance))
		raise_softirq(SCHED_SOFTIRQ);

	nohz_balancer_kick(rq);
}

由此可见,cpu每过一段时间,会触发软中断,还会有nohz kick一下。

软中断中的负载均衡

先看软中断部分,会运行到run_rebalance_domains

run_rebalance_domains
    rebalance_domains
        load_balance

注意到,load_balance的第一个参数,this_cpu, 也正是触发这个软中断的cpu,而这个cpu,是作为dst_cpu, 也就是任务会被拉取到这个cpu上。换句话说,触发软中断的cpu会把任务拉取到自己身上。

static int load_balance(int this_cpu, struct rq *this_rq,
			struct sched_domain *sd, enum cpu_idle_type idle,
			int *continue_balancing)
{
	int ld_moved, cur_ld_moved, active_balance = 0;
	struct sched_domain *sd_parent = sd->parent;
	struct sched_group *group;
	struct rq *busiest;
	struct rq_flags rf;
	struct cpumask *cpus = this_cpu_cpumask_var_ptr(load_balance_mask);

	struct lb_env env = {
		.sd		= sd,
		.dst_cpu	= this_cpu,
		.dst_rq		= this_rq,
		.dst_grpmask    = sched_group_span(sd->groups),
		.idle		= idle,
		.loop_break	= sched_nr_migrate_break,
		.cpus		= cpus,
		.fbq_type	= all,
		.tasks		= LIST_HEAD_INIT(env.tasks),
	};

所以这是负载均衡的第一个触发时机:当tick时,cpu发现自己负载不重,而且满足负载均衡的条件(时间满足,不平衡性满足),就会触发软中断将任务拉到自己身上。

为什么是软中断时执行,因为负载均衡也是需要时间的,如果在中断处理函数中执行,cpu会有很长的时间收不到中断,是不好的。

nohz 负载均衡

内核是靠着频繁的tick进行调度的,但是tick会占用cpu时间,如果cpu上只有一个任务,不需要调度,那就不需要tick,同时,如果cpu上一个任务都没有,进入idle状态,那么也不需要tick。

那么这些关掉了tick的cpu什么时候醒来呢,涉及到负载均衡,我们只关注因为idle状态关闭tick的cpu,因为它们闲着,可以多干活,可以发现,代码逻辑核心就是通过cpu之间的中断 ipi 对idle的cpu进行通知。

nohz_balancer_kick
    判断一下是否kick idle cpu,要是自己任务多,时间满足,就kick一下
    kick_ilb
        smp_send_reschedule

idle的cpu收到这个中断后,就要起来干活了:

smp_reschedule_interrupt
    scheduler_ipi
    	if (unlikely(got_nohz_idle_kick())) {
		    this_rq()->idle_balance = 1;
		    raise_softirq_irqoff(SCHED_SOFTIRQ);
	    }

发现和上面是触发相同的软中断, 同样是运行到run_rebalance_domains,但是首先执行到nohz_idle_balance。

run_rebalance_domains
    nohz_idle_balance
        _nohz_idle_balance
            for_each_cpu(balance_cpu, nohz.idle_cpus_mask)
                rebalance_domains
                    load_balance

一个有正常tick的cpu,当周期性触发tick,并发现自己挺忙的时,就会找一个idle cpu kick它一下,这个idle cpu就会自己触发自己的软中断,然后开始进行nohz_idle_balance,和上面单纯的将任务拉取到自己身上不同,这个idle cpu还会遍历其他所有的idle cpu,让他们开始干活了。

为什么要让idle cpu做这件事?因为既然有tick 的cpu都忙不过来,才会kick,就不能让它来做这件事,而是找个idle cpu来负责把其他idle cpu唤醒干活。

newidle balance

上述都算是周期性触发负载均衡,也就是隔一段时间检查一下cpu之间任务平不平衡,不平衡均衡一下。还有一种情况,内核知道自己很可能不平衡了,那就是newidle balance。

在调度时,内核会选择下一个运行的任务,在cfs调度中是pick_next_task_fair。但是如果发现已经没有任务可选了,那么就很可能要进入idle状态了,即没事可做。

可是这个cpu上没事可做,想idle一会,其他cpu可能正热火朝天着,所以在进入idle之前,还要再进行一次负载均衡,即看看有没有任务能拉过来做, 整体逻辑相当于是被kick了。

pick_next_task_fair
    没有任务要做了
    newidle_balance
        nohz_newidle_balance
        load_balance

这里同样也会尝试nohz balance, 唯一的不同可能干一会发现自己有任务得干,就再kick一下一个idle cpu,把任务交给它。整体来说,和被kick了没有什么太大的差别。

结语

本文粗略介绍了load balance的时机,下一篇文章会介绍load balance的工作流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值