linux cfs调度器

CFS(completely fair scheduler)完全公平调度器,对应应用设置的调度策略为SCHED_NORMAL/SCHED_BATCH。
这种调度策略区别于实时调度,进程优先级低于实时调度进程,用nice值来表示进程的重要程度,nice值得范围-20~19,
转换成优先级为100~140。


应用程序有多种类型,cpu 消耗型,交互型,nice值一般也不同。cfs调度器如何保证完全公平呢?
cfs是将进程所有影响调度的因素都转换为对vruntime的处理,从而近似公平。


说下对CFS “完全公平” 的理解:
①不再区分进程类型,所有进程公平对待
②对I/O消耗型进程,仍然会提供快速响应(对睡眠进程做时间补偿)
③优先级高的进程,获得CPU时间更多(vruntime增长的更慢)


可见CFS的完全公平,并不是说所有进程绝对的平等,占用CPU时间完全相同,而是体现在vruntime数值上,所有进程都用虚拟时间来度量,总是让vruntime最小的进程抢占,这样看起来是完全公平的,但实际上vruntime的更新,增长速度,不同进程是不尽一样的。CFS利用这么个简单的vruntime机制,实现了以往需要相当复杂算法实现的进度调度需求。


vruntime在每个时钟中断,task创建,task加入运行队列,移出运行队列调用到,
接下来看下vruntime更新的相关代码:


每个tick中断时会调用tick_handle_periodic,tick_periodic里面只有在某个特定cpu上执行才会更新全局变量jiffy,

其他cpu上都只会更新自己rq上的进程信息。


//只有一个CPU是负责更新jffies的,其他的CPU只会更新当前自己的进程
static void tick_periodic(int cpu)
{
	if (tick_do_timer_cpu == cpu) {//更新jiffy
		write_seqlock(&jiffies_lock);

		/* Keep track of the next tick event */
		tick_next_period = ktime_add(tick_next_period, tick_period);

		do_timer(1);
		write_sequnlock(&jiffies_lock);
	}
	//更新进程时间信息
	update_process_times(user_mode(get_irq_regs()));
	profile_tick(CPU_PROFILING);
}

update_process_times中会继续调用scheduler_tick,

/*
 * This function gets called by the timer code, with HZ frequency.
 * We call it with interrupts disabled.
 */
 //更新CPU和当前进行的一些数据,然后根据当前进程的调度类,调用task_tick()函数
void scheduler_tick(void)
{
	int cpu = smp_processor_id();
	struct rq *rq = cpu_rq(cpu);
	struct task_struct *curr = rq->curr; //当前正在执行的进程
	u32 old_load;
	struct related_thread_group *grp;
    //更新调度时间
	sched_clock_tick();

	raw_spin_lock(&rq->lock);
	old_load = task_load(curr);
	grp = task_related_thread_group(curr);//task 所在group
	set_window_start(rq);
	update_rq_clock(rq);
	//更新cpu load信息
	update_cpu_load_active(rq);
	//调用cfs 子类task_tick_fair
	curr->sched_class->task_tick(rq, curr, 0);
	update_task_ravg(rq->curr, rq, TASK_UPDATE, sched_ktime_clock(), 0);
	raw_spin_unlock(&rq->lock);

	perf_event_task_tick();

#ifdef CONFIG_SMP
	rq->idle_balance = idle_cpu(cpu);
	trigger_load_balance(rq, cpu);
#endif
	rq_last_tick_reset(rq);

	if (update_preferred_cluster(grp, curr, old_load))
		set_preferred_cluster(grp);

	if (curr->sched_class == &fair_sched_class)
		check_for_migration(rq, curr);
}

static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &curr->se;
	//组调度,更新task group中每个实体的时间信息
	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		entity_tick(cfs_rq, se, queued);
	}

	if (sched_feat_numa(NUMA))
		task_tick_numa(rq, curr);

	update_rq_runnable_avg(rq, 1);
}

//每个时钟周期,更新vruntime
static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
	/*
	 * Update run-time statistics of the 'current'.
	 */
	//更新rq 时间信息
	update_curr(cfs_rq);

	/*
	 * Ensure that runnable average is periodically updated.
	 */
	update_entity_load_avg(curr, 1);
	update_cfs_rq_blocked_load(cfs_rq, 1);
	update_cfs_shares(cfs_rq);

#ifdef CONFIG_SCHED_HRTICK
	/*
	 * queued ticks are scheduled to match the slice, so don't bother
	 * validating it and just reschedule.
	 */
	if (queued) {
		resched_task(rq_of(cfs_rq)->curr);
		return;
	}
	/*
	 * don't let the period tick interfere with the hrtick preemption
	 */
	if (!sched_feat(DOUBLE_TICK) &&
			hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
		return;
#endif
	
	//运行状态task数量大于1的话,检查是否要发生进程调度抢占
	if (cfs_rq->nr_running > 1)
		check_preempt_tick(cfs_rq, curr);
}

//获取task实际运行的时间,加权后变为vruntime
static void update_curr(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	u64 now = rq_clock_task(rq_of(cfs_rq));
	unsigned long delta_exec;

	if (unlikely(!curr))
		return;

	/*
	 * Get the amount of time the current task was running
	 * since the last time we changed load (this cannot
	 * overflow on 32 bits):
	 */
	//当前系统时间减去本次任务开始执行时间, 差值为task 运行的时间
	delta_exec = (unsigned long)(now - curr->exec_start);
	if (!delta_exec)
		return;

	//task运行的实际时间加权后,加到当前task 的vruntime中
	__update_curr(cfs_rq, curr, delta_exec);
	//当前task执行开始时间
	curr->exec_start = now;

	if (entity_is_task(curr)) {
		struct task_struct *curtask = task_of(curr);

		trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
		cpuacct_charge(curtask, delta_exec);
		account_group_exec_runtime(curtask, delta_exec);
	}

	account_cfs_rq_runtime(cfs_rq, delta_exec);
}

static inline void
__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
	      unsigned long delta_exec)
{
	unsigned long delta_exec_weighted;

	schedstat_set(curr->statistics.exec_max,
		      max((u64)delta_exec, curr->statistics.exec_max));
	/* 总运行时间更新 */  
	curr->sum_exec_runtime += delta_exec; 
	schedstat_add(cfs_rq, exec_clock, delta_exec);
	/* 用优先级和delta_exec来计算weighted,以用于更新vruntime */ 
	delta_exec_weighted = calc_delta_fair(delta_exec, curr);//加权后的执行时间
	//vruntime 增长值不是实际运行的时间,而是加权后的值,这样保证运行的公平
	curr->vruntime += delta_exec_weighted;
	//cfs_rq 保存所有进程最小的vruntime,下次调度时直接使用
	update_min_vruntime(cfs_rq);
}

 //若当前进程nice为0,直接返回实际运行时间,其他所有nice值的加权都是以0 nice值为参考增加或减少的。
static inline unsigned long
calc_delta_fair(unsigned long delta, struct sched_entity *se)
{
	if (unlikely(se->load.weight != NICE_0_LOAD))//nice 不为0,就得计算delta加权值
		delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);

	return delta;
}

/*
 * delta *= weight / lw
 */
 /*
 NICE_0_LOAD is 1024 delta*=NICE_0_LOAD/lw  weight=NICE_0_LOAD,
 nice值越大的,自身的load越小,计算后delta相比实际时间就增长越快,这样下次被调度到的机会降低。
 另外也说明为了达到同样的vruntime,nice值高的进程可以分配更少的运行时间
 nice值对应的load 参考prio_to_weight。
 */
static unsigned long
calc_delta_mine(unsigned long delta_exec, unsigned long weight,
		struct load_weight *lw)
{
	u64 tmp;

	/*
	 * weight can be less than 2^SCHED_LOAD_RESOLUTION for task group sched
	 * entities since MIN_SHARES = 2. Treat weight as 1 if less than
	 * 2^SCHED_LOAD_RESOLUTION.
	 */
	if (likely(weight > (1UL << SCHED_LOAD_RESOLUTION)))
		tmp = (u64)delta_exec * scale_load_down(weight);//tmp=delta_exec*NICE_0_LOAD;
	else
		tmp = (u64)delta_exec;

	if (!lw->inv_weight) {
		unsigned long w = scale_load_down(lw->weight);//w=lw->weight;

		if (BITS_PER_LONG > 32 && unlikely(w >= WMULT_CONST))
			lw->inv_weight = 1;
		else if (unlikely(!w))
			lw->inv_weight = WMULT_CONST;
		else
			lw->inv_weight = WMULT_CONST / w; //1^32 /load
	}

	/*
	 * Check whether we'd overflow the 64-bit multiplication:
	 */
	if (unlikely(tmp > WMULT_CONST))
		tmp = SRR(SRR(tmp, WMULT_SHIFT/2) * lw->inv_weight,
			WMULT_SHIFT/2);
	else
		tmp = SRR(tmp * lw->inv_weight, WMULT_SHIFT);//数据按照1<<32位对齐,tmp等于WMULT_SHIFT的倍数

	return (unsigned long)min(tmp, (u64)(unsigned long)LONG_MAX);
}

/*
 * Preempt the current task with a newly woken task if needed:
 */
 //更新实体vruntime后,检查是否需要重新调度,当前进程执行时间超过调度器分配的时间,就重新调度
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
	unsigned long ideal_runtime, delta_exec;
	struct sched_entity *se;
	s64 delta;
	//ideal_runtime是理论上的处理器运行时间片,这个时间不固定,根据当前rq中的running task数量计算出来
	ideal_runtime = sched_slice(cfs_rq, curr);
	//该进程本轮调度累计运行时间
	delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; 
	// 假如实际运行超过调度器分配的时间,就标记重新调度标志
	if (delta_exec > ideal_runtime) {
		resched_task(rq_of(cfs_rq)->curr);
		/*
		 * The current task ran long enough, ensure it doesn't get
		 * re-elected due to buddy favours.
		 */
		clear_buddies(cfs_rq, curr);
		return;
	}

	/*
	 * Ensure that a task that missed wakeup preemption by a
	 * narrow margin doesn't have to wait for a full slice.
	 * This also mitigates buddy induced latencies under load.
	 */
	 //sysctl_sched_min_granularity 调度最小的间隔,不需要重新调度,直接返回
	if (delta_exec < sysctl_sched_min_granularity)
		return;
	//找到位于rq最左边的节点,这个entity就是vruntime最小的entity
	se = __pick_first_entity(cfs_rq);
	delta = curr->vruntime - se->vruntime;
	//当前任务跟rq中最小的vruntime节点对比
	if (delta < 0)
		return;
	//设置重新调度标记,需要切换到其他entity运行
	if (delta > ideal_runtime)
		resched_task(rq_of(cfs_rq)->curr);
}

继续看下调度实体出对,入队的操作,对cfs调度器,入队调用的enqueue_task_fair

/*
 * The enqueue_task method is called before nr_running is
 * increased. Here we update the fair scheduling stats and
 * then put the task into the rbtree:
 */
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &p->se;
	
	/* 这里是一个迭代,我们知道,进程有可能是处于一个进程组中的,所以当这个处于进程组中的进程加入到该进程组的队列中时,要对此队列向上迭代 */
	for_each_sched_entity(se) {
		if (se->on_rq)
			break;
		cfs_rq = cfs_rq_of(se);
		//se 插入rb tree
		enqueue_entity(cfs_rq, se, flags);

		/*
		 * end evaluation on encountering a throttled cfs_rq
		 *
		 * note: in the case of encountering a throttled cfs_rq we will
		 * post the final h_nr_running increment below.
		*/
		if (cfs_rq_throttled(cfs_rq))
			break;
		cfs_rq->h_nr_running++;
		inc_cfs_rq_hmp_stats(cfs_rq, p, 1);

		flags = ENQUEUE_WAKEUP;
	}
	//只有se不处于队列中或者cfs_rq_throttled(cfs_rq)返回假才会运行这个循环,用来处理task group余下的task
	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		cfs_rq->h_nr_running++;
		inc_cfs_rq_hmp_stats(cfs_rq, p, 1);

		if (cfs_rq_throttled(cfs_rq))
			break;

		update_cfs_shares(cfs_rq);
		update_entity_load_avg(se, 1);
	}

	if (!se) {
		update_rq_runnable_avg(rq, rq->nr_running);
		inc_nr_running(rq);
		inc_rq_hmp_stats(rq, p, 1);
	}
	hrtick_update(rq);
}


static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
	/*
	 * Update the normalized vruntime before updating min_vruntime
	 * through callig update_curr().
	 */
	if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))
		se->vruntime += cfs_rq->min_vruntime;

	/*
	 * Update run-time statistics of the 'current'.
	 */
	update_curr(cfs_rq);
	enqueue_entity_load_avg(cfs_rq, se, flags & ENQUEUE_WAKEUP);
	account_entity_enqueue(cfs_rq, se);
	update_cfs_shares(cfs_rq);

	//当前进程之前处于睡眠状态,刚被唤醒,新建进程flag为0
	if (flags & ENQUEUE_WAKEUP) {
		place_entity(cfs_rq, se, 0);
		enqueue_sleeper(cfs_rq, se);
	}

	update_stats_enqueue(cfs_rq, se, !!(flags & ENQUEUE_MIGRATING));
	check_spread(cfs_rq, se);
	if (se != cfs_rq->curr)//将se插入到运行队列cfs_rq的红黑树中
		__enqueue_entity(cfs_rq, se);
	se->on_rq = 1;

	if (cfs_rq->nr_running == 1) {
		list_add_leaf_cfs_rq(cfs_rq);
		check_enqueue_throttle(cfs_rq);
	}
}

/*
对"新"进程处理,initial为0表示新创建进程,为1代表睡眠唤醒的进程
子进程在创建时,vruntime初值首先被设置为min_vruntime;然后,如果sched_features中设置了START_DEBIT位,vruntime会在min_vruntime的基础上再增大一些。设置完子进程的vruntime之后,检查sched_child_runs_first参数,如果为1的话,就比较父进程和子进程的vruntime,若是父进程的vruntime更小,就对换父、子进程的vruntime,这样就保证了子进程会在父进程之前运行。

*/
static void
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
	//新建task以cfs的最小vruntime为初始值
	u64 vruntime = cfs_rq->min_vruntime;

	/*
	 * The 'current' period is already promised to the current tasks,
	 * however the extra weight of the new task will slow them down a
	 * little, place the new task so that it fits in the slot that
	 * stays open at the end.
	 */
	 /*
      sched_features是控制调度器特性的开关,每个bit表示调度器的一个特性。
      在sched_features.h文件中记录了全部的特性。START_DEBIT是其中之一,
      如果打开这个特性,表示给新进程的vruntime初始值要设置得比默认值更大一些,
      这样会推迟它的运行时间,以防进程通过不停的fork来获得cpu时间片。	 
	 */
	if (initial && sched_feat(START_DEBIT))//新进程并且设置了START_DEBIT
		vruntime += sched_vslice(cfs_rq, se);// 加上一个调度周期内的"时间片"

	/* sleeps up to a single latency don't count. */
	//对于休眠进程,vruntime会比rq中最小vruntime还要小,这样在下次调度切换时最先得到调度,
	//也就是交互性进程唤醒后最先得到调度
	if (!initial) {//休眠进程
		unsigned long thresh = sysctl_sched_latency;//一个调度周期

		/*
		 * Halve their sleep time's effect, to allow
		 * for a gentler effect of sleepers:
		 */
		if (sched_feat(GENTLE_FAIR_SLEEPERS))
			thresh >>= 1;

		vruntime -= thresh;
	}

	/* ensure we never gain time by being placed backwards. */
	//第三种情况,进程之前处于执行状态
	se->vruntime = max_vruntime(se->vruntime, vruntime);
}

//将调度实体加入rb tree中,这样才有机会被调度到
static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
	struct rb_node *parent = NULL;
	struct sched_entity *entry;
	int leftmost = 1;

	/*
	 * Find the right place in the rbtree:
	 */
	//在rb tree中找到合适插入点 
	while (*link) {
		parent = *link;
		entry = rb_entry(parent, struct sched_entity, run_node);
		/*
		 * We dont care about collisions. Nodes with
		 * the same key stay together.
		 */
		if (entity_before(se, entry)) {
			link = &parent->rb_left;
		} else {
			link = &parent->rb_right;
			leftmost = 0; //往右子树走过之后,新插入entity的vruntime不可能最小
		}
	}

	/*
	 * Maintain a cache of leftmost tree entries (it is frequently
	 * used):
	 */
	//加入一个新的task后,判断是否要更新rb_leftmost,
	//保存rb tree 最左子树,避免了rb tree的遍历查找
	if (leftmost)
		cfs_rq->rb_leftmost = &se->run_node;
	//插入 rb tree
	rb_link_node(&se->run_node, parent, link);
	//调整rb tree,达到平衡
	rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}

//sched entity 出队操作

static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &p->se;
	int task_sleep = flags & DEQUEUE_SLEEP;
	
	//对于task group,依次出队,group作为一个整体进行处理,出队、入队,以及选择group内的某个task
	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		dequeue_entity(cfs_rq, se, flags);

		/*
		 * end evaluation on encountering a throttled cfs_rq
		 *
		 * note: in the case of encountering a throttled cfs_rq we will
		 * post the final h_nr_running decrement below.
		*/
		if (cfs_rq_throttled(cfs_rq))
			break;
		cfs_rq->h_nr_running--;
		dec_cfs_rq_hmp_stats(cfs_rq, p, 1);

		/* Don't dequeue parent if it has other entities besides us */
		if (cfs_rq->load.weight) {
			/*
			 * Bias pick_next to pick a task from this cfs_rq, as
			 * p is sleeping when it is within its sched_slice.
			 */
			if (task_sleep && parent_entity(se))
				set_next_buddy(parent_entity(se));

			/* avoid re-evaluating load for this entity */
			se = parent_entity(se);
			break;
		}
		flags |= DEQUEUE_SLEEP;
	}

	for_each_sched_entity(se) {
		cfs_rq = cfs_rq_of(se);
		cfs_rq->h_nr_running--;
		dec_cfs_rq_hmp_stats(cfs_rq, p, 1);

		if (cfs_rq_throttled(cfs_rq))
			break;

		update_cfs_shares(cfs_rq);
		update_entity_load_avg(se, 1);
	}

	if (!se) {
		dec_nr_running(rq);
		update_rq_runnable_avg(rq, 1);
		dec_rq_hmp_stats(rq, p, 1);
	}
	hrtick_update(rq);
}

/*
当进程从一个CPU的运行队列中出来 (dequeue_entity) 的时候,它的vruntime要减去队列的min_vruntime值;
而当进程加入另一个CPU的运行队列 ( enqueue_entiry) 时,它的vruntime要加上该队列的min_vruntime值。
这样,进程从一个CPU迁移到另一个CPU之后,vruntime保持相对公平。
*/

static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
	/*
	 * Update run-time statistics of the 'current'.
	 */
	//更新当前task的vruntime
	update_curr(cfs_rq);
	dequeue_entity_load_avg(cfs_rq, se, flags & DEQUEUE_SLEEP);

	update_stats_dequeue(cfs_rq, se, !!(flags & DEQUEUE_MIGRATING));
	if (flags & DEQUEUE_SLEEP) {
#ifdef CONFIG_SCHEDSTATS
		if (entity_is_task(se)) {
			struct task_struct *tsk = task_of(se);

			if (tsk->state & TASK_INTERRUPTIBLE)
				se->statistics.sleep_start = rq_clock(rq_of(cfs_rq));
			if (tsk->state & TASK_UNINTERRUPTIBLE)
				se->statistics.block_start = rq_clock(rq_of(cfs_rq));
		}
#endif
	}

	clear_buddies(cfs_rq, se);

	if (se != cfs_rq->curr)
		__dequeue_entity(cfs_rq, se);
	se->on_rq = 0;
	account_entity_dequeue(cfs_rq, se);

	/*
	 * Normalize the entity after updating the min_vruntime because the
	 * update can refer to the ->curr item and we need to reflect this
	 * movement in our normalized position.
	 */
	//更新se vruntime 
	if (!(flags & DEQUEUE_SLEEP))
		se->vruntime -= cfs_rq->min_vruntime;

	/* return excess runtime on last dequeue */
	return_cfs_rq_runtime(cfs_rq);
	//更新cfs rq的min_vruntime
	update_min_vruntime(cfs_rq);
	update_cfs_shares(cfs_rq);
}
//从rb tree中移除entity
static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	if (cfs_rq->rb_leftmost == &se->run_node) {
		struct rb_node *next_node;

		next_node = rb_next(&se->run_node);
		cfs_rq->rb_leftmost = next_node;
	}

	rb_erase(&se->run_node, &cfs_rq->tasks_timeline);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值