想比于前面文章讲到的CFS的调度器,RT调度器就简单许多,RT的调度算法也相应的简单很多。
static const struct sched_class rt_sched_class = { .next = &fair_sched_class, .enqueue_task = enqueue_task_rt, .dequeue_task = dequeue_task_rt, .yield_task = yield_task_rt, .check_preempt_curr = check_preempt_curr_rt, .pick_next_task = pick_next_task_rt, .put_prev_task = put_prev_task_rt, #ifdef CONFIG_SMP .select_task_rq = select_task_rq_rt, .set_cpus_allowed = set_cpus_allowed_rt, .rq_online = rq_online_rt, .rq_offline = rq_offline_rt, .pre_schedule = pre_schedule_rt, .post_schedule = post_schedule_rt, .task_woken = task_woken_rt, .switched_from = switched_from_rt, #endif .set_curr_task = set_curr_task_rt, .task_tick = task_tick_rt, .get_rr_interval = get_rr_interval_rt, .prio_changed = prio_changed_rt, .switched_to = switched_to_rt, };
首先来看enqueu_task_rt:
/* * Adding/removing a task to/from a priority array: */ static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags) { struct sched_rt_entity *rt_se = &p->rt; if (flags & ENQUEUE_WAKEUP) rt_se->timeout = 0; enqueue_rt_entity(rt_se, flags & ENQUEUE_HEAD); //核心操作 if (!task_current(rq, p) && p->rt.nr_cpus_allowed > 1) enqueue_pushable_task(rq, p); }该方法的核心操作即enqueue_rt_entity:
static void enqueue_rt_entity(struct sched_rt_entity *rt_se, bool head) { dequeue_rt_stack(rt_se); //对于这个操作完成的功能,我搞不清楚,即使看了其注释还是一头雾水。貌似是要将rt_se从其所在的队列中删除?但是注释中给出的解释有不是这样的。。。 for_each_sched_rt_entity(rt_se) __enqueue_rt_entity(rt_se, head); }
把第一个操作的实现列出来,虽然我也不知道它具体完成什么功能。
/* * Because the prio of an upper entry depends on the lower * entries, we must remove entries top - down. */ static void dequeue_rt_stack(struct sched_rt_entity *rt_se) { struct sched_rt_entity *back = NULL; for_each_sched_rt_entity(rt_se) { rt_se->back = back; back = rt_se; } for (rt_se = back; rt_se; rt_se = rt_se->back) { if (on_rt_rq(rt_se)) __dequeue_rt_entity(rt_se); } }第二个操作的功能是很明显的,就是将rt_se入列:
static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, bool head) { struct rt_rq *rt_rq = rt_rq_of_se(rt_se); struct rt_prio_array *array = &rt_rq->active; struct rt_rq *group_rq = group_rt_rq(rt_se); struct list_head *queue = array->queue + rt_se_prio(rt_se); //找到rt_se对应优先级的链表。 /* * Don't enqueue the group if its throttled, or when empty. * The latter is a consequence of the former when a child group * get throttled and the current group doesn't have any other * active members. */ if (group_rq && (rt_rq_throttled(group_rq) || !group_rq->rt_nr_running)) return; if (!rt_rq->rt_nr_running) list_add_leaf_rt_rq(rt_rq); if (head) //如果head不为空,则将该rt任务插入到run_list的队列头。说明该rt任务在与其同一优先级的rt任务中,最先被运行。 list_add(&rt_se->run_list, queue); else list_add_tail(&rt_se->run_list, queue); //如果head为空,则将rt任务插入到queue的队列尾。说明该rt任务在与其同一优先级的rt任务中,最后被运行。 __set_bit(rt_se_prio(rt_se), array->bitmap); inc_rt_tasks(rt_se, rt_rq); }回到调度器中,下一个是dequeue_task_rt方法,它完成的功能则正好与enqueue_task_rt方法相反:
static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags) { struct sched_rt_entity *rt_se = &p->rt; update_curr_rt(rq); //更新相关时间 dequeue_rt_entity(rt_se); //只是将p移至链表的尾部。 dequeue_pushable_task(rq, p); }/* * Update the current task's runtime statistics. Skip current tasks that * are not in our scheduling class. */ static void update_curr_rt(struct rq *rq) { struct task_struct *curr = rq->curr; struct sched_rt_entity *rt_se = &curr->rt; struct rt_rq *rt_rq = rt_rq_of_se(rt_se); u64 delta_exec; if (curr->sched_class != &rt_sched_class) return; delta_exec = rq->clock_task - curr->se.exec_start; //计算此次调度运行的时间 if (unlikely((s64)delta_exec < 0)) delta_exec = 0; schedstat_set(curr->se.statistics.exec_max, max(curr->se.statistics.exec_max, delta_exec)); curr->se.sum_exec_runtime += delta_exec; //更新运行的总时间 account_group_exec_runtime(curr, delta_exec); curr->se.exec_start = rq->clock_task; //更新运行的起始时间 cpuacct_charge(curr, delta_exec); //与组调度相关 sched_rt_avg_update(rq, delta_exec); //与SMP相关 if (!rt_bandwidth_enabled()) return; for_each_sched_rt_entity(rt_se) { rt_rq = rt_rq_of_se(rt_se); if (sched_rt_runtime(rt_rq) != RUNTIME_INF) { raw_spin_lock(&rt_rq->rt_runtime_lock); rt_rq->rt_time += delta_exec; if (sched_rt_runtime_exceeded(rt_rq)) //rt_rq的运行时间是否超过了分配给它的时间片,如果超过了则将其设置为可调度的。 resched_task(curr); raw_spin_unlock(&rt_rq->rt_runtime_lock); } } }
static void dequeue_rt_entity(struct sched_rt_entity *rt_se) { dequeue_rt_stack(rt_se); for_each_sched_rt_entity(rt_se) { struct rt_rq *rt_rq = group_rt_rq(rt_se); if (rt_rq && rt_rq->rt_nr_running) __enqueue_rt_entity(rt_se, false); //可以看到这里的出队列,只是将其移至链表的最后尾部。 } }继续看调度器的下一个方法yield_task_rt,暂时放弃执行:
static void yield_task_rt(struct rq *rq) { requeue_task_rt(rq, rq->curr, 0); //只将curr移动到链表的末尾即可。 }static void requeue_task_rt(struct rq *rq, struct task_struct *p, int head) { struct sched_rt_entity *rt_se = &p->rt; struct rt_rq *rt_rq; for_each_sched_rt_entity(rt_se) { rt_rq = rt_rq_of_se(rt_se); requeue_rt_entity(rt_rq, rt_se, head); //只是将其移动到链表的末尾位置即可。 } }调度器的下一个方法check_preempt_curr_rt,用来检查是否满足可抢占的条件:
/* * Preempt the current task with a newly woken task if needed: */ static void check_preempt_curr_rt(struct rq *rq, struct task_struct *p, int flags) { if (p->prio < rq->curr->prio) { //如果给定的p的优先级的数值小于curr,也就是说其优先级大于curr resched_task(rq->curr); //就将curr设置为可抢占的。 return; } #ifdef CONFIG_SMP /* * If: * * - the newly woken task is of equal priority to the current task * - the newly woken task is non-migratable while current is migratable * - current will be preempted on the next reschedule * * we should check to see if current can readily move to a different * cpu. If so, we will reschedule to allow the push logic to try * to move current somewhere else, making room for our non-migratable * task. */ if (p->prio == rq->curr->prio && !test_tsk_need_resched(rq->curr)) check_preempt_equal_prio(rq, p); #endif }接着来看下面一个重要的方法,pick_next_task_rt,挑选下一个要被调度的rt任务:
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; }
来看这里的核心函数_pice_next_task_rt():
static struct task_struct *_pick_next_task_rt(struct rq *rq) { struct sched_rt_entity *rt_se; struct task_struct *p; struct rt_rq *rt_rq; rt_rq = &rq->rt; if (unlikely(!rt_rq->rt_nr_running)) return NULL; if (rt_rq_throttled(rt_rq)) return NULL; do { rt_se = pick_next_rt_entity(rq, rt_rq); BUG_ON(!rt_se); rt_rq = group_rt_rq(rt_se); } while (rt_rq); p = rt_task_of(rt_se); p->se.exec_start = rq->clock_task; return p; }
我们忽略掉组相关的调度,那么这里的核心函数就是pick_next_rt_entity: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); BUG_ON(idx >= MAX_RT_PRIO); queue = array->queue + idx; next = list_entry(queue->next, struct sched_rt_entity, run_list); return next; }想比于CFS,这个函数很简单,查找array位图第一个不为0的位置,然后取链接在这个位置处的rt任务链表的第一个rt任务作为下一个被调度执行的rt任务。
调度器中下面一个方法是put_prev_task_rt(),关于这个函数的作用可以参考CFS的相对应的方法的作用:
static void put_prev_task_rt(struct rq *rq, struct task_struct *p) { update_curr_rt(rq); p->se.exec_start = 0; /* * The previous task needs to be made eligible for pushing * if it is still active */ if (on_rt_rq(&p->rt) && p->rt.nr_cpus_allowed > 1) enqueue_pushable_task(rq, p); }
同样,我们先忽略SMP相关的方法:#ifdef CONFIG_SMP .select_task_rq = select_task_rq_rt, .set_cpus_allowed = set_cpus_allowed_rt, .rq_online = rq_online_rt, .rq_offline = rq_offline_rt, .pre_schedule = pre_schedule_rt, .post_schedule = post_schedule_rt, .task_woken = task_woken_rt, .switched_from = switched_from_rt, #endif
接下来,调度器实现的方法是set_curr_task_rt(),我也不知道这个方法是干什么用的。。。static void set_curr_task_rt(struct rq *rq) { struct task_struct *p = rq->curr; p->se.exec_start = rq->clock_task; /* The running task is never eligible for pushing */ dequeue_pushable_task(rq, p); }
相对于CFS,RT中的RR算法更为重要一些,实现这个算法的就是方法task_tick_rt:
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued) { update_curr_rt(rq); watchdog(rq, p); /* * RR tasks need a special form of timeslice management. * FIFO tasks have no timeslices. */ if (p->policy != SCHED_RR) //RT任务只有两种算法,RR和FIFO;FIFO直到进程运行完毕都不会被调度,因此,直接返回。 return; if (--p->rt.time_slice) //如果时间片尚未用完,则返回。 return; p->rt.time_slice = DEF_TIMESLICE; //重新将p的时间片设为DEF_TIMESLICE,一般为100ms。 /* * Requeue to the end of queue if we are not the only element * on the queue: */ if (p->rt.run_list.prev != p->rt.run_list.next) { requeue_task_rt(rq, p, 0); //将p调整到队列的尾部,在下一次调度时不可能被调度到。 set_tsk_need_resched(p); //将p设为可调度的。 } }调度器中下面一个算法get_rr_interval_rt只是用来获得时间片的大小,可以看到,如果采用RR算法,那么返回的都是默认时间片的大小100ms:static unsigned int get_rr_interval_rt(struct rq *rq, struct task_struct *task) { /* * Time slice is 0 for SCHED_FIFO tasks */ if (task->policy == SCHED_RR) return DEF_TIMESLICE; else return 0; }调度器的下面一个方法prio_changed_rt():/* * Priority of the task has changed. This may cause * us to initiate a push or pull. */ static void prio_changed_rt(struct rq *rq, struct task_struct *p, int oldprio) { if (!p->on_rq) return; if (rq->curr == p) { #ifdef CONFIG_SMP /* * If our priority decreases while running, we * may need to pull tasks to this runqueue. */ if (oldprio < p->prio) pull_rt_task(rq); /* * If there's a higher priority task waiting to run * then reschedule. Note, the above pull_rt_task * can release the rq lock and p could migrate. * Only reschedule if p is still on the same runqueue. */ if (p->prio > rq->rt.highest_prio.curr && rq->curr == p) resched_task(p); #else /* For UP simply resched on drop of prio */ if (oldprio < p->prio) //如果其优先级减小了,那么就要重新调度 resched_task(p); #endif /* CONFIG_SMP */ } else { /* * This task is not running, but if it is * greater than the current running task * then reschedule. */ if (p->prio < rq->curr->prio) //如果p的优先级比curr的优先级大的话,就重新调度。 resched_task(rq->curr); } }
来看RT调度器中的最后一个算法,switch_to_rt(),注释中解释的相当清楚了,我们就不用再详述了:/* * When switching a task to RT, we may overload the runqueue * with RT tasks. In this case we try to push them off to * other runqueues. */ static void switched_to_rt(struct rq *rq, struct task_struct *p) { int check_resched = 1; /* * If we are already running, then there's nothing * that needs to be done. But if we are not running * we may need to preempt the current running task. * If that current running task is also an RT task * then see if we can move to another run queue. */ if (p->on_rq && rq->curr != p) { #ifdef CONFIG_SMP if (rq->rt.overloaded && push_rt_task(rq) && /* Don't resched if we changed runqueues */ rq != task_rq(p)) check_resched = 0; #endif /* CONFIG_SMP */ if (check_resched && p->prio < rq->curr->prio) resched_task(rq->curr); } }