Overview
按照POSIX标准的强制要求,除了"普通进程",Linux还支持两种实时调度类,调度器结构使得实时进程可以平滑地集成到内核中。
实时进程的特点是其优先级比普通进程高,rt_task通过检查其优先级来证实给定进程是否是实时进程,而task_has_rt_policy则检测进程是否关联到实时调度策略。
性质
- 实时进程和普通进程有一个根本的不同之处:如果系统中有一个实时进程且可运行,那么调度器总是会选中它运行,除非有另一个优先级更高的实时进程
- 循环进程(SCHED_RR):基于时间片,在进程运行时时间片会减少,进程时间用完后(100ms),则重置为初始值,并将进程放置到队列的末尾,这确保了在有几个优先级相同的SCHED_RR进程时,它们总是依次执行
- 先进先出进程(SCHED_FIFO):没有时间片,在被调度器选择后,可以运行任意长时间。
数据结构
-
实时进程调度类
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 .load_balance = load_balance_rt, .move_one_task = move_one_task_rt, #endif .set_curr_task = set_curr_task_rt, .task_tick = task_tick_rt, };
-
就绪队列
struct rq { struct rt_rq rt; } struct rt_rq { struct rt_prio_array active; int rt_load_balance_idx; struct list_head *rt_load_balance_head, *rt_load_balance_curr; }; struct rt_prio_array { DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */ struct list_head queue[MAX_RT_PRIO]; };
- 具有相同优先级的所有实时进程都保存在一个链表中,表头为active.queue[prio],而active.bitmap位图中的每个比特位对应一个链表,凡包含了进程的链表,对应的比特位置位
-
实时调度器类中对应于update_curr的是update_curr_rt,该函数将当前进程在CPU上执行花费的时间记录在sum_exec_runtime中,记录的是实际时间。
调度器操作
-
进程的入队和离队都比较简单,只需以p->prio为索引访问queue数组,即可获得正确的链表,将进程加入或者从链表删除即可,当然要同步设置bitmap位图。
-
选择下一个进程
static struct task_struct *pick_next_task_rt(struct rq *rq) { struct rt_prio_array *array = &rq->rt.active; struct task_struct *next; struct list_head *queue; int idx; idx = sched_find_first_bit(array->bitmap); if (idx >= MAX_RT_PRIO) return NULL; queue = array->queue + idx; next = list_entry(queue->next, struct task_struct, run_list); next->se.exec_start = rq->clock; return next; }
-
周期调度
static void task_tick_rt(struct rq *rq, struct task_struct *p) { update_curr_rt(rq); /* * RR tasks need a special form of timeslice management. * FIFO tasks have no timeslices. */ if (p->policy != SCHED_RR) return; if (--p->time_slice) return; p->time_slice = DEF_TIMESLICE; /* * Requeue to the end of queue if we are not the only element * on the queue: */ if (p->run_list.prev != p->run_list.next) { requeue_task_rt(rq, p); set_tsk_need_resched(p); } }
- SCHED_FIFO进程可以运行任意长时间,因此直接返回即可
- SCHED_RR进程减少其时间片,在尚未超出时间段时,直接返回,否则重置时间片为DEF_TIMESLICE(100ms),另外如果该进程不是链表中的唯一的进程,则重新排队到末尾
-
实时进程转换
- 使用sched_setscheduler系统调用,必须具有root权限或者CAP_SYS_NICE能力:使用deactive_task将进程从当前队列删除;在task_struct中设置实时优先级和调度类;重新激活进程