这次主要从时钟tick中断响应到设置进程的调度标志位过程的分析。内核版本4.1.15。
时钟的中断函数响应后最终会调用scheduler_tick函数,scheduler_tick函数函数中有句话curr->sched_class->task_tick(rq, curr, 0); 这个钩子函数的注册在kernel/sched/fair.c中。task_tick_fair最终调用的是check_preempt_tick函数。这个函数的作用就是从二叉树中找出最需要执行的进程,然后把该进程的调度标志置位。__pick_first_entity就是找到最需要执行的进程,resched_curr函数就是设置调度标志位。这里不对虚拟运行时间的计算做分析。
__pick_first_entity函数:
struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq)
{
struct rb_node *left = cfs_rq->rb_leftmost;
if (!left)
return NULL;
return rb_entry(left, struct sched_entity, run_node);
}
可以看到这个函数里其实就是将二叉树最左侧的节点取出来,那说明二叉树里已经按照某种规则排好序了,排序的位置在哪里呢?入手点是cfs_rq,这个结构是在哪被排序的?根据什么排序的?查了下资料,最后找到__enqueue_entity、entity_before函数,作用就是就是根据vruntime把信息存入二叉树,最左侧的是最需要被执行的进程信息。
__enqueue_entity函数:
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:
*/
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;
}
}
/*
* Maintain a cache of leftmost tree entries (it is frequently
* used):
*/
if (leftmost)
cfs_rq->rb_leftmost = &se->run_node;
rb_link_node(&se->run_node, parent, link);
rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}
entity_before函数:
static inline int entity_before(struct sched_entity *a,
struct sched_entity *b)
{
return (s64)(a->vruntime - b->vruntime) < 0;
}
这样就可以确定排序是根据vruntime(虚拟运行时间)。
那又是在哪里调用的__enqueue_entity函数呢?
这是调用__enqueue_entity的一条路,还有其他的路,没有查找了就。
调用resched_curr函数设置调度标志位后,并没有马上就切换到需要运行的进程上下文中,目前还在时钟中断的上下文中,等中断结束之后才会调用schedule函数进行真正的进程上下文切换。
以上分析,若有错误,还望见谅。
与君共勉!