按照POSIX标准的强制要求,除了“普通”进程之外, Linux还支持两种实时调度类。调度器结构使得实时进程可以平滑地集成到内核中,而无需修改核心调度器,这显然是调度类带来的好处。
现在比较适合于回想一些很久以前讨论过的事实。实时进程的特点在于其优先级比普通进程高,对应地,其static_prio值总是比普通进程低,如图2-14所示。 rt_task宏通过检查其优先级来证实给定进程是否是实时进程,而task_has_rt_policy
则检测进程是否关联到实时调度策略。
1. 性质
实时进程与普通进程有一个根本的不同之处:如果系统中有一个实时进程且可运行,那么调度器总是会选中它运行,除非有另一个优先级更高的实时进程。
现有的两种实时类,不同之处如下所示。
- 循环进程(SCHED_RR)有时间片,其值在进程运行时会减少,就像是普通进程。在所有的时间段都到期后,则该值重置为初始值,而进程则置于队列的末尾。这确保了在有几个优先级相同的SCHED_RR进程的情况下,它们总是依次执行。
- 先进先出进程(SCHED_FIFO)没有时间片,在被调度器选择执行后,可以运行任意长时间。
很明显,如果实时进程编写得比较差,系统可能变得无法使用。只要写一个无限循环,循环体内不进入睡眠即可。在编写实时应用程序时,应该多加小心。
1.2 数据结构
实时进程的调度类定义如下:
kernel/sched-rt.c
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,
.set_curr_task = set_curr_task_rt,
.task_tick = task_tick_rt,
};
实时调度器类的实现比完全公平调度器简单。大约只需要250行代码,而CFS则需要1100行!
kernel/sched.c
struct rq {
...
t_rq rt;
...
}
就绪队列非常简单,链表就足够了:
kernel/sched.c
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* 包含1比特用于间隔符 */
struct list_head queue[MAX_RT_PRIO];
};
struct rt_rq {
struct rt_prio_array active;
};
具有相同优先级的所有实时进程都保存在一个链表中,表头为active.queue[prio],而active.bitmap位图中的每个比特位对应于一个链表,凡包含了进程的链表,对应的比特位则置位。如果链表中没有进程,则对应的比特位不置位。图2-23说明了具体情形。
实时调度器类中对应于update_cur的是update_curr_rt,该函数将当前进程在CPU上执行花费的时间记录在sum_exec_runtime中。所有计算的单位都是实际时间,不需要虚拟时间。这样就简化了很多。
3. 调度器操作
进程的入队和离队都比较简单。只需以p->prio为索引访问queue数组queue[p->prio],即可获得正确的链表,将进程加入链表或从链表删除即可。如果队列中至少有一个进程,则将位图中对应的比特位置位;如果队列中没有进程,则清除位图中对应的比特位。请注意,新进程总是排列在每个链表的末尾。
两个比较有趣的操作分别是,如何选择下一个将要执行的进程,以及如何处理抢占。首先考虑pick_next_task_rt,该函数放置选择下一个将执行的进程。其代码流程图在图2-24给出。
sched_find_first_bit是一个标准函数,可以找到active.bitmap中第一个置位的比特位,这意味着高的实时优先级(对应于较低的内核优先级值),因此在较低的实时优先级之前处理。取出所选链表的第一个进程,并将se.exec_start设置为就绪队列的当前实际时钟值,即可。
周期调度的实现同样简单。SCHED_FIFO进程最容易处理。它们可以运行任意长的时间,而且必须使用yield系统调用将控制权显式传递给另一个进程:
kernel/sched.c
static void task_tick_rt(struct rq *rq, struct task_struct *p)
{
update_curr_rt(rq);
/*
* 循环进程需要一种特殊形式的时间片管理。
* 先进先出进程没有时间片。
*/
if (p->policy != SCHED_RR)
return;
...
如果当前进程是循环进程,则减少其时间片。在尚未超出时间段时,没什么可作的,进程可以继续执行。计数器归0后,其值重置为DEF_TIMESLICE,即100 * HZ / 1000,亦即100毫秒。如果该进程不是链表中唯一的进程,则重新排队到末尾。通过用set_tsk_need_resched设置TIF_NEED_RESCHED标志,照常请求重调度:
为将进程转换为实时进程,必须使用sched_setscheduler系统调用。这里不详细讨论该函数了,因为它只执行了下列简单任务。
- 使用deactivate_task将进程从当前队列移除。
- 在task_struct中设置实时优先级和调度类。
- 重新激活进程
如果进程此前不在任何就绪队列上,那么只需要设置调度类和新的优先级数值。停止进程活动和重激活则是不必要的。
要注意,只有具有root权限(或等价于CAP_SYS_NICE)的进程执行了sched_setscheduler系统调用,才能修改调度器类或优先级。否则,下列规则适用。
- 调度类只能从SCHED_NORMAL改为SCHED_BATCH,或反过来。改为SCHED_FIFO是不可能的。
- 只有目标进程的UID或EUID与调用者进程的EUID相同时,才能修改目标进程的优先级。此外,优先级只能降低,不能提升。
原文地址:https://www.cnblogs.com/linhaostudy/p/9978386.html