Scheduler学习之四:deadline之Load balance

  1. Overview
    本文主要分析deadline的loadbalance相关,主要包含如下问题:
    a.load balance的方式
    b. load balance的时机
  2. Load balance的方式
    方式一:push task
    /*
     * See if the non running -deadline tasks on this rq
     * can be sent to some other CPU where they can preempt
     * and start executing.
     */
    static int push_dl_task(struct rq *rq)
    {
    	struct task_struct *next_task;
    	struct rq *later_rq;
    	int ret = 0;
    
    	if (!rq->dl.overloaded)
    		return 0;
    
    	next_task = pick_next_pushable_dl_task(rq);//从rq中选一个task出来,push
    	if (!next_task)
    		return 0;
    
    retry:
    	if (WARN_ON(next_task == rq->curr)) //不能直接选curr task
    		return 0;
    
    	/*
    	 * If next_task preempts rq->curr, and rq->curr
    	 * can move away, it makes sense to just reschedule
    	 * without going further in pushing next_task.
    	 */
    	if (dl_task(rq->curr) &&
    	    dl_time_before(next_task->dl.deadline, rq->curr->dl.deadline) &&
    	    rq->curr->nr_cpus_allowed > 1) {//如果next task可以抢占当前curr的,则不做Push
    		resched_curr(rq);
    		return 0;
    	}
    
    	/* We might release rq lock */
    	get_task_struct(next_task);
    
    	/* Will lock the rq it'll find */
    	later_rq = find_lock_later_rq(next_task, rq);//选择一个合适的rq出来
    	if (!later_rq) {//如果当前的task找不到合适的新的cpu,则尝试找下一个可以推送的task。
    		struct task_struct *task;
    
    		/*
    		 * We must check all this again, since
    		 * find_lock_later_rq releases rq->lock and it is
    		 * then possible that next_task has migrated.
    		 */
    		task = pick_next_pushable_dl_task(rq);
    		if (task == next_task) {
    			/*
    			 * The task is still there. We don't try
    			 * again, some other CPU will pull it when ready.
    			 */
    			goto out;
    		}
    
    		if (!task)
    			/* No more tasks */
    			goto out;
    
    		put_task_struct(next_task);
    		next_task = task;
    		goto retry;
    	}
    
    	deactivate_task(rq, next_task, 0);//将当前的task dequeue出来,
    	set_task_cpu(next_task, later_rq->cpu);//设置当前的task新的cpu
    
    	/*
    	 * Update the later_rq clock here, because the clock is used
    	 * by the cpufreq_update_util() inside __add_running_bw().
    	 */
    	update_rq_clock(later_rq);
    	activate_task(later_rq, next_task, ENQUEUE_NOCLOCK);//enqueue入新的rq当中
    	ret = 1;
    
    	resched_curr(later_rq);//被选中的rq重新调度
    
    	double_unlock_balance(rq, later_rq);
    
    out:
    	put_task_struct(next_task);
    
    	return ret;
    }
    上述代码比较简单,经历如下的步骤:
    a. 从当前的rq中选择一个可以Push出去的task
        如下的code所示,选择deadline靠前的task
    static struct task_struct *pick_next_pushable_dl_task(struct rq *rq)
    {
    	struct task_struct *p;
    
    	if (!has_pushable_dl_tasks(rq))
    		return NULL;
    
    	p = rb_entry(rq->dl.pushable_dl_tasks_root.rb_leftmost,
    		     struct task_struct, pushable_dl_tasks);//选择deadline排在前面的task
    
    	BUG_ON(rq->cpu != task_cpu(p));
    	BUG_ON(task_current(rq, p));
    	BUG_ON(p->nr_cpus_allowed <= 1);
    
    	BUG_ON(!task_on_rq_queued(p));
    	BUG_ON(!dl_task(p));
    
    	return p;
    }
    如果这个新选出来的task可能有机会抢占正在运行的task,则重新选择。
    b. 为这个task选择合适的cpu
    如下code所示,这里又走到之前选核策略上面了。因为选核策略已经兼顾了均衡。
    /* Locks the rq it finds */
    static struct rq *find_lock_later_rq(struct task_struct *task, struct rq *rq)
    {
    	struct rq *later_rq = NULL;
    	int tries;
    	int cpu;
    
    	for (tries = 0; tries < DL_MAX_TRIES; tries++) {
    		cpu = find_later_rq(task);
    
    		if ((cpu == -1) || (cpu == rq->cpu))
    			break;
    
    		later_rq = cpu_rq(cpu);
    
    		if (later_rq->dl.dl_nr_running &&
    		    !dl_time_before(task->dl.deadline,
    					later_rq->dl.earliest_dl.curr)) {
    			/*
    			 * Target rq has tasks of equal or earlier deadline,
    			 * retrying does not release any lock and is unlikely
    			 * to yield a different result.
    			 */
    			later_rq = NULL;
    			break;
    		}
    
    		/* Retry if something changed. */
    		if (double_lock_balance(rq, later_rq)) {
    			if (unlikely(task_rq(task) != rq ||
    				     !cpumask_test_cpu(later_rq->cpu, task->cpus_ptr) ||
    				     task_running(rq, task) ||
    				     !dl_task(task) ||
    				     !task_on_rq_queued(task))) {
    				double_unlock_balance(rq, later_rq);
    				later_rq = NULL;
    				break;
    			}
    		}
    
    		/*
    		 * If the rq we found has no -deadline task, or
    		 * its earliest one has a later deadline than our
    		 * task, the rq is a good one.
    		 */
    		if (!later_rq->dl.dl_nr_running ||
    		    dl_time_before(task->dl.deadline,
    				   later_rq->dl.earliest_dl.curr))
    			break;
    
    		/* Otherwise we try again. */
    		double_unlock_balance(rq, later_rq);
    		later_rq = NULL;
    	}
    
    	return later_rq;
    }
    c. 将要push的task 从当前的rq中dequeue出来,并enqueue入新的rq当中。
    方式二:pull task
    static void pull_dl_task(struct rq *this_rq)
    {
    	int this_cpu = this_rq->cpu, cpu;
    	struct task_struct *p;
    	bool resched = false;
    	struct rq *src_rq;
    	u64 dmin = LONG_MAX;
    
    	if (likely(!dl_overloaded(this_rq)))//此处是判断系统overloaded,并不是判断当前rq。所以,只有系统发生overloaded才会考虑将其他cpu 上面loading重的task pull过来,以减轻其他cpu loading
    		return;
    
    	/*
    	 * Match the barrier from dl_set_overloaded; this guarantees that if we
    	 * see overloaded we must also see the dlo_mask bit.
    	 */
    	smp_rmb();
    
    	for_each_cpu(cpu, this_rq->rd->dlo_mask) {//dlo_mask表示,发生overloaded的cpu。所以,此处从overloaded的cpu中选择做均衡。
    		if (this_cpu == cpu)
    			continue;
    
    		src_rq = cpu_rq(cpu);
    
    		/*
    		 * It looks racy, abd it is! However, as in sched_rt.c,
    		 * we are fine with this.
    		 */
    		if (this_rq->dl.dl_nr_running &&
    		    dl_time_before(this_rq->dl.earliest_dl.curr,
    				   src_rq->dl.earliest_dl.next))
    			continue; //如果当前cpu中当前task的deadline比src中的task先到,则不做pull。因为拉过来也要排队。所以能够被pull 的rq的条件为:当前rq没有dl task或者有rq的排队task dl比当前rq中正在执行dl靠前。即,主动被其他rq中的dl靠前的Task抢占。
    
    		/* Might drop this_rq->lock */
    		double_lock_balance(this_rq, src_rq);
    
    		/*
    		 * If there are no more pullable tasks on the
    		 * rq, we're done with it.
    		 */
    		if (src_rq->dl.dl_nr_running <= 1)
    			goto skip;
    
    		p = pick_earliest_pushable_dl_task(src_rq, this_cpu);//将src_rq中排队的靠前的task拉过来。
    
    		/*
    		 * We found a task to be pulled if:
    		 *  - it preempts our current (if there's one),
    		 *  - it will preempt the last one we pulled (if any).
    		 */
    		if (p && dl_time_before(p->dl.deadline, dmin) &&
    		    (!this_rq->dl.dl_nr_running ||
    		     dl_time_before(p->dl.deadline,
    				    this_rq->dl.earliest_dl.curr))) {
    			WARN_ON(p == src_rq->curr);
    			WARN_ON(!task_on_rq_queued(p));
    
    			/*
    			 * Then we pull iff p has actually an earlier
    			 * deadline than the current task of its runqueue.
    			 */
    			if (dl_time_before(p->dl.deadline,
    					   src_rq->curr->dl.deadline))
    				goto skip;
    
    			resched = true;
    
    			deactivate_task(src_rq, p, 0);
    			set_task_cpu(p, this_cpu);
    			activate_task(this_rq, p, 0);
    			dmin = p->dl.deadline; //后面只有比较当前dl更小才能被拉过来
    
    			/* Is there any other task even earlier? */
    		}
    skip:
    		double_unlock_balance(this_rq, src_rq);
    	}
    
    	if (resched)
    		resched_curr(this_rq);
    }
    综上所述:pull task会去将其他cpu中排队的deadline靠前的task拉到当前cpu中执行。分两种情况:
    1.当前rq中的dl task为空,则将所有overload cpu中dl 最靠前的task拉过来
    2.当前rq中的dl task不空,则将所有overload cpu中dl 最靠前且比当前rq中的正在running的task dl靠前的Task拉过来。
    cpu overload的判断标准:
    static void update_dl_migration(struct dl_rq *dl_rq)
    {
    	if (dl_rq->dl_nr_migratory && dl_rq->dl_nr_running > 1) {
    		if (!dl_rq->overloaded) {
    			dl_set_overload(rq_of_dl_rq(dl_rq));
    			dl_rq->overloaded = 1;
    		}
    	} else if (dl_rq->overloaded) {
    		dl_clear_overload(rq_of_dl_rq(dl_rq));
    		dl_rq->overloaded = 0;
    	}
    }
    所以overloaded判断的条件为:
    runable的task多于一个并且可以被迁移的Task也多于一个。可以被迁移指的是task 允许的cpu超过1个。因为running的task中有一些只能跑在当前cpu当中。
  3. Load均衡的时机
    a. pick next task时,注册回调函数,在执行:__schedule,rt_mutex_setprio,__sched_setscheduler中进行。
    b.pick next task时,请各调度器做一次balance,这时候dl class会尝试pull。所以,从这个地方可以看出,选task时,是从所有runqueue中选最快要到期的。
  4. Summary
    本文主要跟踪了dl的均衡机制,均衡机制主要是在选task的时候,要么进行push,要么进行pull进行,其目标是deadline最先到达的Task优先执行。

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值