码迷,mamicode.com
首页 > 其他好文 > 详细

实时调度类

时间:2018-11-18 17:04:10      阅读:202      评论:0      收藏:0      [点我收藏+]

标签:ret   activate   osi   数值   码流   数据结构   优先   保存   程序   

按照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相同时,才能修改目标进程的优先级。此外,优先级只能降低,不能提升。

实时调度类

标签:ret   activate   osi   数值   码流   数据结构   优先   保存   程序   

原文地址:https://www.cnblogs.com/linhaostudy/p/9978386.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!