• 实时调度类


    按照POSIX标准的强制要求,除了“普通”进程之外, Linux还支持两种实时调度类。调度器结构使得实时进程可以平滑地集成到内核中,而无需修改核心调度器,这显然是调度类带来的好处。

    现在比较适合于回想一些很久以前讨论过的事实。实时进程的特点在于其优先级比普通进程高,对应地,其static_prio值总是比普通进程低,如图2-14所示。 rt_task宏通过检查其优先级来证实给定进程是否是实时进程,而task_has_rt_policy则检测进程是否关联到实时调度策略。

    1.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中。所有计算的单位都是实际时间,不需要虚拟时间。这样就简化了很多。

    1.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相同时,才能修改目标进程的优先级。此外,优先级只能降低,不能提升。
  • 相关阅读:
    SQL Server数据库读写分离提高并发性
    静态方法与实例化方法区别
    消息队列MQ对比
    【Python】socket模块应用
    【Matplotlib】利用Python进行绘图
    【Git】简单使用
    【HTTPS】自签CA证书 && nginx配置https服务
    【HTTP】 认证和单点登录 【瞎写的…】
    【Linux】防火墙与CentOS中的iptables
    【Ansible】的python api
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/9978386.html
Copyright © 2020-2023  润新知