03-实时进程调度概述
35.2 实时进程调度概述
在一个系统上一般会同时运行交互式进程和后台进程,标准的内核调度算法一般能够为这些进程提供足够的性能和响应度。但实时应用对调度器有更加严格的要求,如下所示。
- 实时应用必须要为外部输入提供担保最大响应时间。在很多情况下,这些担保最大响应时间必须非常短(如低于秒级)。如交通导航系统的慢速响应可能会使一个灾难。为了满足这种要求,内核必须要提供工具让高优先级进程能快速地取得CPU的控制权,抢占当前运行的所有进程。
一些时间关键的应用程序可能需要采取其他措施来避免不可接受的延迟。如为了避免由于页面错误而引起的延迟,应用程序可能会使用mlock()或mlockall()(50.2节中将予以介绍)将其所有虚拟内存锁在RAM中。
- 高优先级进程应该能够保持互斥地访问CPU直至它完成或自动释放CPU。
- 实时应用应该能够精确地控制其组件进程的调度顺序。
SUSv3规定的实时进程调度 API(原先在POSIX.1b中定义)部分满足了这些要求。这个API提供了两个实时调度策略:SCHED_RR和SCHED_FIFO。使用这两种策略中任意一种策略进行调度的进程的优先级要高于使用 35.1 中介绍的标准循环时间分享策略来调度的进程,实时调度API使用常量SCHED_OTHER来标识这种循环时间分享策略。
每个实时策略允许一个优先级范围。SUSv3要求实现至少要为实时策略提供32个离散的优先级。在每个调度策略中,拥有高优先级的可运行进程在尝试访问CPU时总是优先于优先级较低的进程。
对于多处理器Linux系统(包括超线程系统)来讲,高优先级的可运行进程总是优先于优先级较低的进程的规则并不适用。在多处理器系统中,各个CPU拥有独立的运行队列(这种方式比使用一个系统层面的运行队列的性能要好),并且每个CPU的运行队列中的进程的优先级都局限于该队列。如假设一个双处理器系统中运行着三个进程,进程A的实时优先级为20,并且它位于CPU 0的等待队列中,而该CPU当前正在运行优先级为30的进程B,即使CPU 1正在运行优先级为10的进程C,进程A还是需要等待CPU 0。 包含多个进程的实时应用可以使用35.4节中描述的CPU亲和力API来避免这种调度行为可能引起的问题。如在一个四处理器系统中,所有非关键的进程可以被分配到一个CPU中,让其他三个CPU处理实时应用。
Linux提供了 99个实时优先级,其数值从1(最低)~99(最高),并且这个取值范围同时适用于两个实时调度策略。每个策略中的优先级是等价的。这意味着如果两个进程拥有同样的优先级,一个进程采用了SCHED_RR的调度策略,另一个进程采用了SCHED_FIFO的调度策略,那么两个都符合运行的条件,至于到底运行哪个则取决于它们被调度的顺序了。实际上,每个优先级级别都维护着一个可运行的进程队列,下一个运行的进程是从优先级最高的非空队列的队头选取出来的。
POSIX实时与硬实时对比
满足本节开头处列出的所有要求的应用程序有时候被称为硬实时应用程序。但POSIX实时进程调度API无法满足这些要求。特别是它没有为应用程序提供一种机制来确保处理输入的响应时间,而这种机制需要操作系统的提供相应的特性,但Linux内核并没有提供这种特性(大多数其他标准的操作系统也没有提供这种特性)。POSIX API仅仅提供了所谓的软实时,允许控制调度哪个进程使用CPU。
在不给系统增加额外开销的情况下增加对硬实时应用程序的支持是非常困难的,这种新增的开销通常与时间分享应用程序的性能要求是存在冲突的,而典型的桌面和服务器系统上运行的应用程序大部分都是时间分享应用程序。这就是为何大多数UNIX内核——包括原来的Linux——并没有为实时应用程序提供原生支持的原因。但从版本2.6.18开始,各种特性都被添加到了Linux内核中,从而允许Linux为硬实时应用程序提供了完全的原生支持,同时不会给时间分享应用程序增加前面提及到的开销。