实时调度类
目录
一、实时调度类概述
二、实时调度类策略
(一)先进先出(SCHED_FIFO)
(二)时间片轮转(SCHED_RR)
三、实时调度类源码数据结构
(1)task_struct的调度相关成员
(2)实时调度队列rt_rq
(3)调度实体(sched_rt_entity)
一、实时调度类概述
在Linux操作系统中,调度器是至关重要的一个环节,他决定了哪个进程可以获得CPU资源,以及获得多少资源。之前我们曾谈到了CFS完全公平调度器,它能提供良好的资源分配公平性,并通过红黑树优化查找时间复杂度,极大程度的提高了进程切换的效率。然而正是因为他的公平性,无法满足一些对时间延迟要求极低的进程,比如工业化的自动控制,音视频处理,实时监控等。
于是实时调度类应运而生,他和CFS类似,都有着自己的就绪队列。不同的是实时调度类严格遵循抢占式调度,优先级高的进程可以直接进入cpu运行,而优先级低的进程即使已经在cpu运行了也会被挤下来重新回到队列中等待。而CFS则是内核在每一个时钟中断检测正在运行的进程的vruntime和就绪队列的vruntime,如果有更小的则抢占。
实时调度类旨在确保实时任务能够在规定的时间内完成,具有比普通进程更高的优先级,能够抢占普通进程的 CPU 资源,从而保证系统的实时性和确定性。
Linux 支持两种主要的实时调度策略:先进先出(SCHED_FIFO)和时间片轮转(SCHED_RR),它们都属于实时调度类,通过特定的数据结构和算法来实现高效的调度。
二、实时调度类策略
(一)先进先出(SCHED_FIFO)
核心:一直运行,知道自己被阻塞或者更高优先级抢占
基本原理:当一个进程被设置为
SCHED_FIFO
调度策略时,它会根据实时优先级(rt_priority
,范围是 0 - 99,数值越大优先级越高)被放入实时调度队列中。只要该进程处于可运行状态,并且其优先级高于当前正在运行的进程(无论是实时进程还是普通进程),它就能立即抢占 CPU 资源开始运行。而且,一旦这个SCHED_FIFO
进程获得 CPU,它会一直运行下去,直到主动让出 CPU(例如,调用pthread_yield()
函数)、等待某个资源(如 I/O 操作)或者被更高优先级的实时进程抢占。示例:假设有两个
SCHED_FIFO
进程,进程 A 的实时优先级为 50,进程 B 的实时优先级为 30。进程 A 先被调度运行,此时如果进程 B 变为可运行状态,由于进程 A 的优先级更高,进程 B 会一直等待,直到进程 A 主动让出 CPU 或者被更高优先级的进程抢占。优点:实现简单,能够保证高优先级的实时任务得到快速执行,适用于对执行顺序有严格要求,且执行时间相对较短的实时任务。
缺点:如果一个高优先级的
SCHED_FIFO
进程长时间占用 CPU 且不主动让出,可能会导致其他实时进程甚至普通进程长时间得不到执行,出现 “饥饿” 现象。
(二)时间片轮转(SCHED_RR)
核心:基于时间片轮转,同一优先级的公平分配cpu,高优先级的抢占执行
基本原理:
SCHED_RR
与SCHED_FIFO
类似,也是根据实时优先级来决定进程的调度顺序。不同之处在于,SCHED_RR
为每个可运行的实时进程分配一个时间片(timeslice
)。当一个SCHED_RR
进程获得 CPU 后,它最多运行一个时间片的时长。当时间片耗尽时,如果还有其他相同优先级的SCHED_RR
进程在等待,系统会按照轮转的方式将 CPU 分配给下一个同优先级的SCHED_RR
进程。如果当前进程的优先级高于其他可运行进程,即使时间片耗尽,它依然可以继续运行。示例:假设有三个
SCHED_RR
进程,进程 C、D、E 的实时优先级均为 40,系统为它们分配的时间片为 10ms。进程 C 先获得 CPU 运行,10ms 后,即使它还未完成任务,只要进程 D 和 E 处于可运行状态,CPU 就会分配给进程 D,然后是进程 E,如此循环。如果在进程 C 运行过程中,出现了一个实时优先级为 60 的进程 F,那么进程 F 会立即抢占 CPU,当进程 F 运行结束或者主动让出 CPU 后,进程 C 会从上次中断的地方继续运行,且剩余的时间片依然有效。优点:避免了同优先级实时进程中某个进程长期占用 CPU 的情况,保证了同优先级实时进程之间的公平性,适用于多个实时任务需要轮流执行,且对响应时间有严格要求的场景。
缺点:相比于
SCHED_FIFO
,由于存在时间片的管理和切换,会带来一定的系统开销。
三、实时调度类源码数据结构
(1)task_struct的调度相关成员
在 Linux 内核中,task_struct
是描述进程的核心数据结构,对于实时调度类,其中包含了多个与之相关的关键成员:
policy
:用于表示进程的调度策略,当值为SCHED_FIFO
或SCHED_RR
时,表明该进程属于实时调度类。rt_priority
:记录实时进程的优先级,范围是 0 - 99,数值越大优先级越高,是实时调度决策的重要依据。sched_class
:指向该进程所属的调度类,对于实时进程,它指向实时调度类(rt_sched_class
),通过这个指针,调度器可以调用实时调度类的相关函数来处理该进程的调度操作。
(2)实时调度队列rt_rq
之前我们说每一个cpu都有一个运行队列,而一个运行队列分为三种调度就绪队列,上一篇文章中谈到了CFS完全公平调度队列(红黑树),而在实时调度队列中,他是一个优先级队列的数组。
struct rt_rq
{struct plist_head active; // 活跃进程列表,按照优先级从高到低排列unsigned long rt_nr_running; // 实时可运行进程的数量struct sched_rt_entity *curr; // 当前正在运行的实时进程实体// 其他与实时调度相关的成员,如调度统计信息等
};
active
:是一个优先级数组(基于plist
,一种高效的优先级队列实现),实时进程根据其优先级被插入到相应的位置。高优先级的进程会排在前面,调度器从这个队列中选择优先级最高的可运行进程来调度执行。rt_nr_running
:记录当前实时调度队列中可运行的实时进程数量,方便调度器快速了解实时进程的状态。curr
:指向当前正在该 CPU 上运行的实时进程实体,通过这个指针,调度器可以获取进程的相关信息,如优先级变化等,并进行相应的调度决策。
(3)调度实体(sched_rt_entity)
调度实体sched_rt_entity是task_struct中的一个成员,他用于描述实时进程在调度中的相关信息,包含了进程的优先级、运行时间等数据。
struct sched_rt_entity
{struct sched_entity se; // 继承自通用调度实体,包含一些通用的调度信息unsigned int rt_priority; // 实时优先级// 其他与实时调度相关的统计信息,如运行时间等
};
调度器将一个pcb插入调度就绪队列的 本质是插入这个调度实体,然后通过进程pcb成员的相对位置不变,利用强制类型转换才得到真正的pcb。