实时调度器,对于不同优先级任务,优先级更高的任务始终获得CPU资源,直到其退出可运行状态,才会运行更低优先级的任务。对于相同优先级的任务,分为SCHED_FIFO和SCHED_RR两种调度策略。SCHED_FIFO先入先出,先入队的任务始终获得CPU资源,直到其退出可运行状态,才会运行同优先级下一个任务。SCHED_RR调度策略,同优先级的任务轮流获得CPU资源。
1.总体框架
每个CPU都对应一个运行队列,用于管理运行在该CPU上的任务,内核将运行队列抽象为结构体struct rq,运行队列中用于管理实时任务的成员是struct rt_rq rt。结构体struct rt_rq保存运行队列的实时任务相关信息,可运行状态的任务保存在其成员struct rt_prio_array active中。
struct rq {
......................................
struct cfs_rq cfs; //CFS任务相关
struct rt_rq rt; //RT任务相关
struct dl_rq dl; //DEADLINE任务相关
......................................
}
struct rt_rq {
//active管理处于可运行状态的任务
struct rt_prio_array active;
unsigned int rt_nr_running;
unsigned int rr_nr_running;
......................................
};
结构体struct rt_prio_array有两个成员,struct list_head queue[MAX_RT_PRIO]是长度为MAX_RT_PRIO的内核链表数组,一个实时优先级对应数组中一个内核链表,用于保存该优先级的调度实体。bitmap成员是一个位图,当某一优先级对应内核链表中有调度实体时,该优先级在位图中对应比特位置位。
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1);
struct list_head queue[MAX_RT_PRIO];
};
RT调度器对应的调度实体抽象为结构体struct sched_rt_entity,调度器中的任务都有属于自己的调度实体,例如struct task_struct结构体中就包含成员struct sched_rt_entity rt。
struct task_struct {
...............................................
struct sched_entity se;
struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
struct task_group *sched_task_group;
#endif
struct sched_dl_entity dl;
................................................
}
RT调度器相关结构体的关系如下图所示:
2.任务入队
调度器入队接口enqueue_task,在RT调度器中对应的函数是enqueue_task_rt()。根据任务的优先级,在struct rt_prio_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);
if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
enqueue_pushable_task(rq, p);
}
static void enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
struct rq *rq = rq_of_rt_se(rt_se);
dequeue_rt_stack(rt_se, flags);
for_each_sched_rt_entity(rt_se)
__enqueue_rt_entity(rt_se, flags);
enqueue_top_rt_rq(&rq->rt);
}
static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
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);//优先级对应内核链表
..............................................................................
if (move_entity(flags)) {
WARN_ON_ONCE(rt_se->on_list);
// 加入优先级对应内核链表
if (flags & ENQUEUE_HEAD)
list_add(&rt_se->run_list, queue);
else
list_add_tail(&rt_se->run_list, queue);
//置位优先级在位图中对应比特位
__set_bit(rt_se_prio(rt_se), array->bitmap);
rt_se->on_list = 1;
}
rt_se->on_rq = 1;
inc_rt_tasks(rt_se, rt_rq);
}
3.选择下一个运行任务
当有任务退出可运行状态,或者有任务被唤醒时,会调用调度器pick_next_task接口,选择下一个运行的任务。RT调度器pick_next_task接口对应的函数是pick_next_task_rt(),首先通过位图找到有可运行状态任务的最高优先级,然后通过优先级找到对应的内核链表,最后选择内核链表第一个成员作为接下来运行的任务。
static struct task_struct *pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
struct task_struct *p;
WARN_ON_ONCE(prev || rf);
if (!sched_rt_runnable(rq))
return NULL;
p = _pick_next_task_rt(rq);
set_next_task_rt(rq, p, true);
return p;
}
static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
struct sched_rt_entity *rt_se;
struct rt_rq *rt_rq = &rq->rt;
do {
rt_se = pick_next_rt_entity(rt_rq);
if (unlikely(!rt_se))
return NULL;
rt_rq = group_rt_rq(rt_se);
} while (rt_rq);
return rt_task_of(rt_se);//由调度实体找到进程
}
static struct sched_rt_entity *pick_next_rt_entity(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;
if (SCHED_WARN_ON(list_empty(queue)))
return NULL;
next = list_entry(queue->next, struct sched_rt_entity, run_list);
return next;
}
4.周期调度
每次时钟节拍到来时,在函数scheduler_tick ()中会调用当前任务所属调度器的task_tick接口,以驱动调度器判断是继续运行当前任务,还是选择新任务运行。RT调度器中,只有RR调度策略的任务支持task_tick,FIFO调度策略不支持该接口(直接返回)。RR调度策略的任务都有时间片time_slice,task_tick接口中时间片减一,当时间片用完后重置时间片,选择同优先级中下一个任务运行,如此往复,已到达同优先级任务轮流执行的目的。
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
struct sched_rt_entity *rt_se = &p->rt;
update_curr_rt(rq);
update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);
watchdog(rq, p);
//不是RR调度策略,则直接返回
if (p->policy != SCHED_RR)
return;
//时间片减一,如果没用完,继续运行当前任务
if (--p->rt.time_slice)
return;
//如果用完,重置时间片
p->rt.time_slice = sched_rr_timeslice;
//如果用完,选择同优先级下一个任务运行
for_each_sched_rt_entity(rt_se) {
if (rt_se->run_list.prev != rt_se->run_list.next) {
requeue_task_rt(rq, p, 0);
resched_curr(rq);
return;
}
}
}
5.SCHED_FIFO和SCHED_RR对比
SCHED_FIFO不支持task_tick,故只有更高优先级的任务入队,或者当前任务主动让出CPU,才会选择同优先级内核链表中下一个任务运行。SCHED_RR支持task_tick,为任务分配时间片,同优先级的每个任务轮流运行time_slice长度。