进程管理之schedule --> pick_next_task()

/*
 * Pick up the highest-prio task:
 */
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
	const struct sched_class *class;
	struct task_struct *p;

	/*
	 * Optimization: we know that if all tasks are in
	 * the fair class we can call that function directly:
	 */
	if (likely(rq->nr_running == rq->cfs.nr_running)) { //如果nr_running==cfs.nr_running,则说明当前rq队列中是没有rt任务的,
                                                            //rt任务不在cfs队列中,它的优先级的设置和cfs不一样。
		p = fair_sched_class.pick_next_task(rq); //在cfs队列中挑选即将被切换进去的进程,核心函数,我们下文会详细讲解。
		if (likely(p))
			return p;
	}

	for_each_class(class) { 
		p = class->pick_next_task(rq);
		if (p)
			return p;
	}

	BUG(); /* the idle class will always have a runnable task */
}

当CPU中没有rt任务时,那么从cfs队列中挑选合适的进程以待切换进CPU,因为是cfs调度类,所以最终调度的函数是pick_next_task_fair:

static struct task_struct *pick_next_task_fair(struct rq *rq)
{
	struct task_struct *p;
	struct cfs_rq *cfs_rq = &rq->cfs;
	struct sched_entity *se;

	if (!cfs_rq->nr_running) 
		return NULL;

	do {
		se = pick_next_entity(cfs_rq); //挑选下一个调度实体,详解见下文
		set_next_entity(cfs_rq, se); //
		cfs_rq = group_cfs_rq(se); 
	} while (cfs_rq);  //如果被调度的进程仍属于当前组,那么选取下一个可能被调度的任务,以保证组间调度的公平性???

	p = task_of(se);
	hrtick_start_fair(rq, p);

	return p;
}
来看一下pick_next_entity:

/*
 * Pick the next process, keeping these things in mind, in this order:
 * 1) keep things fair between processes/task groups                    1. 首先要确保任务组之间的公平,这也是设置组的原因之一;
 * 2) pick the "next" process, since someone really wants that to run   2. 其次,挑选下一个合适的(优先级比较高的)进程,因为它确实需要马上运行;
 * 3) pick the "last" process, for cache locality                       3. 如果没有找到条件2中的进程,那么为了保持良好的局部性,则选中上一次执行的进程;
 * 4) do not run the "skip" process, if something else is available     4. 只要有任务存在,就不要让CPU空转,也就是让CPU运行idle进程。
 */
static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)
{
	struct sched_entity *se = __pick_first_entity(cfs_rq); //摘取红黑树最左边的进程
	struct sched_entity *left = se;

	/*
	 * Avoid running the skip buddy, if running something else can
	 * be done without getting too unfair.
	 */
	if (cfs_rq->skip == se) { //如果是被取到的进程自动放弃运行权利
        /*
         *cfs_rq队列中的*skip指向暂时不需要被调度执行的进程,这样的进程一般通过sched_yield()
         *(在CFS中是通过yield_task_fair)放弃执行权的,同时在sched_yield设置skip之前,总是将上一个被设置为skip的进程清除掉,以防止放弃
         *运行权利的进程永远得不到调度。
         */
                struct sched_entity *second = __pick_next_entity(se); //摘取红黑树上第二左的进程节点
		if (second && wakeup_preempt_entity(second, left) < 1) //left应该抢占second么?这个函数的具体实现会在下文详述。
			se = second; //left不会抢占second,则se = second.
	}

	/*
	 * Prefer last buddy, try to return the CPU to a preempted task.
	 */
	if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
        //如果上一次执行的进程尚在cfs_rq队列中,并且left不能抢占它,这里我们应该能够想到为什么内核线程的active_mm要借用
        //上一个进程的active_mm。这样的话上一个activ_mm就不用从tlb中清洗掉,而下一次调度的时候有可能重新调度到该进程,增加了tlb的命中率。
                se = cfs_rq->last; 

	/*
	 * Someone really wants this to run. If it's not unfair, run it.
	 */
	if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)//如果cfs_rq中指向下一个即将被调度进来的进程的指针不为空,并且left不能抢占它 
		se = cfs_rq->next;

	clear_buddies(cfs_rq, se); //清空last, next, skip指针

	return se;
}

我们首先要关注的是 pick_next_entity中调用的第一个函数__pick_first_entity:

static struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)
{
	struct rb_node *left = cfs_rq->rb_leftmost;  //挑选cfs队列红黑树中最左的元素

	if (!left)
		return NULL;

	return rb_entry(left, struct sched_entity, run_node);
}

                                   现在,有必要对CFS调度机制有一个深入的了解了。推荐参考网站:http://blog.csdn.net/peimichael/article/details/5218335或者《独辟蹊径》一书。

接着,来看一下在pick_next_entity中被反复用到的一个函数:

/*
 * Should 'se' preempt 'curr'.
 *
 *             |s1
 *        |s2
 *   |s3
 *         g
 *      |<--->|c
 *
 *  w(c, s1) = -1
 *  w(c, s2) =  0
 *  w(c, s3) =  1
 *
 */
static int
wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
	s64 gran, vdiff = curr->vruntime - se->vruntime; //curr和se之间虚拟时钟的差别

	if (vdiff <= 0)     //如果curr的虚拟时钟小于se的虚拟时钟
		return -1;  //则se不能抢占curr

	gran = wakeup_gran(curr, se); //计算粒度,详述见下文
	if (vdiff > gran)  //如果vdiff大于粒度,则允许se抢占curr
		return 1;

	return 0;
}

这个函数返回-1表示新进程vruntime大于当前进程,当然不能抢占,返回0表示虽然新进程vruntime比当前进程小,但是没有小到调度粒度,一般也不能抢占。返回1表示新进程vruntime比当前进程小的超过了调度粒度,可以抢占。
调度粒度是什么概念呢?进程调度的时候每次都简单选择vruntime最小的进程调度,其实也不完全是这样。
假设进程A和B的vruntime很接近,那么A先运行了一个tick,vruntime比B大了,B又运行一个tick,vruntime又比A大了,又切换到A,这样就会在AB间频繁切换,对性能影响很大,因此如果当前进程的时间没有用完,就只有当有进程的vruntime比当前进程小超过调度粒度时,才能进行进程切换。
函数上面注释中那个图就是这个意思,我们看下:
横坐标表示vruntime,s1 s2 s3分别表示新进程,c表示当前进程,g表示调度粒度。
s3肯定能抢占c;而s1不可能抢占c。
s2虽然vruntime比c小,但是在调度粒度之内,能否抢占要看情况,像现在这种状况就不能抢占。

(摘自:http://blog.csdn.net/peimichael/article/details/5218335)

在选中了下一个将被调度执行的进程之后,回到pick_next_task_fair中,执行set_next_entity():


static void
set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	/* 'current' is not kept within the tree. */
	if (se->on_rq) { //如果se尚在rq队列上
		/*
		 * Any task has to be enqueued before it get to execute on
		 * a CPU. So account for the time it spent waiting on the
		 * runqueue.
		 */
		update_stats_wait_end(cfs_rq, se);
		__dequeue_entity(cfs_rq, se); //将se从rq队列中删除
                /*
                 *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; //挑选下一个leftmost
                 *    }
                 

                 *     rb_erase(&se->run_node, &cfs_rq->tasks_timeline); //调整rb树
                 *}
                 */
	}

	update_stats_curr_start(cfs_rq, se); //We are picking a new current task.更新sched_entity中的exec_start字段为当前clock_task.
	cfs_rq->curr = se; //将se设置为curr进程
#ifdef CONFIG_SCHEDSTATS
	/*
	 * Track our maximum slice length, if the CPU's load is at
	 * least twice that of our own weight (i.e. dont track it
	 * when there are only lesser-weight tasks around):
	 */
	if (rq_of(cfs_rq)->load.weight >= 2*se->load.weight) {
		se->statistics.slice_max = max(se->statistics.slice_max,
			se->sum_exec_runtime - se->prev_sum_exec_runtime);
	}
#endif
	se->prev_sum_exec_runtime = se->sum_exec_runtime; //更新task上一次投入运行的从时间。
}

以上,我们讨论了公平调度的情况,回到pick_next_task中,如果rq队列中进程的数量不等于CFS中进程的数量,那么说明有其它类型的任务需要被优先调度。

	for_each_class(class) { 
		p = class->pick_next_task(rq);
		if (p)
			return p;
	}

对于for_each_class:

#define sched_class_highest (&stop_sched_class)
#define for_each_class(class) \
   for (class = sched_class_highest; class; class = class->next)
我们可以得到这样的结论,不同类型的任务被调度的顺序是:

stop_sched_class     ---->     rt_sched_class    ---->    fair_sched_class    ---->    idle_sched_class    ---->    NULL

首先看一下stop_sched_class选取下一个进程的过程:

static struct task_struct *pick_next_task_stop(struct rq *rq)
{
	struct task_struct *stop = rq->stop;  //直接将rq队列中的stop指向的进程作为即将被调度进来的进程。

	if (stop && stop->on_rq)
		return stop;

	return NULL;
}

然后,看rt_sched_class挑选下一个进程的过程:

static struct task_struct *pick_next_task_rt(struct rq *rq)
{
	struct task_struct *p = _pick_next_task_rt(rq);

	/* The running task is never eligible for pushing */
	if (p)
		dequeue_pushable_task(rq, p);

#ifdef CONFIG_SMP
	/*
	 * We detect this state here so that we can avoid taking the RQ
	 * lock again later if there is no need to push
	 */
	rq->post_schedule = has_pushable_tasks(rq);
#endif

	return p;
}
其中的关键操作自然是_pick_next_task_rt:

static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,
						   struct rt_rq *rt_rq)
{
	struct rt_prio_array *array = &rt_rq->active;
	struct sched_rt_entity *next = NULL;
	struct list_head *queue;
	int idx;

	idx = sched_find_first_bit(array->bitmap); //找到active->bitmap中第一个置位的比特位,即高优先级。
	BUG_ON(idx >= MAX_RT_PRIO);

	queue = array->queue + idx;
	next = list_entry(queue->next, struct sched_rt_entity, run_list);

	return next;
}

实时进程的调度用的是和CFS不同的调度方法,不过想比于CFS,它更简单。详细的可以参看《深入Linux内核架构》P117.

到目前为止,我们终于将下一个要执行的进程挑选出来了!



你可能感兴趣的:(struct,Class,each,任务,optimization,linux内核)