efficient lock-free solutions for multi-core Linux scheduler第一章 研究背景

The Linux scheduler

        处理器时间是一个有限的资源,进程调度器是内核的一个子系统,它将处理器时间分配给可运行的进程。

模块化调度框架

        Linux调度器是一个可以轻松扩展的模块化框架。每个调度器模块都是一个调度类,封装了特定的调度策略细节。

        调度类是通过sched class结构实现的,该结构包含挂载在相应事件发生时必须调用的函数上的钩子。调度器钩子的(部分)是:

                • enqueue task(...):当任务进入可运行状态时,它被调用。它将任务排队到运行队列中。

                •enqueue task(...):当任务不再可运行时,它被调用。它从运行队列中删除一个任务。

                •yield task(...):当前任务停止执行,并将处理器交还给操作系统的任务调度器。

                •check preempt curr(...):它检查进入可运行状态的任务是否应该抢占当前运行的任务。

                •pick next task(...):它选择最合适的任务。

                •put prev task(...):它抢占一个运行任务。

                •select task rq(...):它需要决定将这个即将唤醒的任务放置在哪个运行队列(CPU核心)中。任务通常在等待某些事件完成后被唤醒,这时需要决定将其分配到哪个CPU核心上继续执行。

                •task tick(...):它的任务是执行与当前正在运行的任务相关的周期性工作。这可能包括一些需要定期执行的任务,如任务的状态更新、性能统计、资源回收等。

        目前在Linux调度器中实现了三种“公平”调度策略(SCHED NORMAL、SCHED BATCH、SCHED IDLE)和两个实时调度策略(SCED RR、SCHED FIFO)。

调度实体、任务和运行队列

        调度器用于实现任何调度策略的所有数据都包含struct sched entity中(每个调度器模块都有一个调度实体)。调度实体是操作系统中的一个通用概念,可以代表各种可以被调度的实体,不仅仅局限于进程。这可以包括任务组、线程、作业或其他类型的执行单元,取决于操作系统的设计和调度策略。这种灵活性允许操作系统根据不同的需求和场景来管理和调度不同类型的实体,以实现多任务和资源管理。

        在 struct task-struct 的开头,有一些字段可以识别任务:

        • volatile long state:它描述了任务的状态。它可以假设三个值(-1, 0, >0),分别取决于任务不可运行、可运行或停止。

        •const struct-sched class *sched-class:它将任务绑定到它的调度类。

        •struct sched-entity se, struct sched-rt-entity rt:它包含调度实体相关信息。

        •cpumask-t cpus-allowed:任务可以运行的cpu的掩码。

        •pid-t pid:唯一标识任务的进程标识符。

        Linux内核中有一个主要的运行队列数据结构,称为 struct rq。在多核系统中,通常会有一个 struct rq对应于每个CPU核心,这是为了支持多核心并行执行任务。运行队列的实现是以模块化的方式进行的。模块化意味着不同的调度类别(例如,CFS、实时调度等)可以根据其需要实现自己的运行队列,以适应不同的调度算法和策略。这种灵活性有助于Linux适应各种应用场景和硬件架构。

        在操作系统中,调度器负责管理任务的执行,以确保任务在CPU上合理分配和执行。因此,深入了解调度器的内部实现对于理解系统的行为和性能至关重要。以CFS为例,CFS调度器使用时间有序的红黑树来排队任务,并构建未来任务执行的“时间轴”。红黑树中的每个运行中的进程都对应一个节点,而位于最左边位置的进程是下一个要被调度执行的进程。这种结构确保了公平性和按照任务的优先级进行调度。尽管红黑树的结构相对复杂,但它在执行查找、插入和删除等操作时具有较好的最坏情况运行时间,并且在实际中表现出高效性。这意味着这个数据结构能够以对数时间复杂度(O(log n))处理树中的元素,这对于高效的任务调度非常重要。

The Linux real-time scheduler

        由于Linux是一个符合POSIX的操作系统,Linux调度器还必须提供SCHED FIFO和SCHED RR调度算法。这些算法实际上是在SCHED RT调度类中实现的,因此它们代表了专门用于实时任务管理的Linux内核代码的一部分。在本节中参照多处理器系统支持简要介绍这些类。

 SCHED FIFO and SCHED RR

        FIFO和RR是简单的固定优先级策略。根据POSIX标准,FIFO是一个严格的先进先出调度策略。该策略包含至少 32 个优先级(实际上在Linux 中是 100 个)。SCHED RR是一种基于循环调度的策略,使用时间片来调度任务。每个任务都有一个固定的时间段来执行,当任务达到时间片限制时,它会成为队列中的尾部,并且队列中的下一个任务将开始执行。这种策略有助于实现任务之间的公平分配,确保每个任务都有机会执行,并避免了某个任务长时间占用CPU的情况。这也是一种常见的实时任务调度策略。

        固定优先级调度器的显著缺点是进程之间的公平性和安全性。如果一个普通的非特权用户被允许访问实时调度功能,那么该用户也可以提高自己的进程优先级,从而可能导致系统中的其他任务无法得到足够的CPU时间,陷入饥饿状态。

Multiprocessor support

        多核处理器在前面提到的数据结构struct sched-class中有更多的字段:

        • select task rq(...):函数通常在几个情况下被调用。这些情况包括进程的创建(fork)、程序的执行(exec)和任务的唤醒(wake-up)。这些都是涉及到新任务进入系统或现有任务从休眠状态唤醒时的情况。当新任务进入系统或现有任务从休眠状态唤醒时,调度器需要决定将该任务分配到哪个运行队列(CPU核心)上。这个决策非常重要,因为它会影响到任务的执行效率和系统的性能。

        load balance(...):函数的作用是检查系统中的CPU核心负载,如果不平衡,则尝试重新分配任务以实现负载均衡。然而,这个函数并非所有调度类别都会实现,因为某些调度策略可能不需要或不支持负载平衡操作。

        pre schedule(...): 函数的任务是执行与调度类别相关的工作,这些工作需要在实际的调度操作之前完成。这可以包括一些与特定调度类别相关的初始化、准备工作或其他必要的操作。

        post schedule(...):同理,在调度操作完成后完成。

        task woken(...):函数用于处理任务被唤醒时的情况,通常用于执行一些与任务唤醒相关的操作,以确保任务可以继续执行或准备好被重新调度。这有助于操作系统在任务唤醒时维护任务的一致性和正确性。

        set cpus allowed(...):函数的主要任务是改变任务的CPU亲和性,具体行为可能因调度类别而异。它可以被用于控制任务的运行位置,也可以在某些情况下触发任务迁移操作,以优化系统性能和资源利用。这对于操作系统的资源管理和性能调优非常重要。

        现代大型多处理器系统可以具有复杂的结构,并且大多数处理器彼此的关系不相等。多处理器(非实时)调度器的关键目标之一是平衡cpu上的负载。让调度程序在许多不同的负载类型下智能迁移任务并不容易。为了解决这个问题,引调度域入 Linux 内核中。

        多核系统通常将核心组织成多个调度域。每个调度域是一个逻辑组,包含一组CPU核心,这些核心可以一起协调任务调度。调度域的概念允许任务调度器在各个调度域之间进行负载均衡、任务迁移和性能优化,以实现更好的系统性能和资源利用。结构指针struct sched-domain *sd被 添加到 struct rq 中,将运行队列 (CPU) 与其调度域绑定。

Linux scheduler multiprocessor support in realtime scheduling class

        实时系统的调度器需要进行全局调度决策,考虑所有任务的状态,并且需要频繁地进行实时任务的平衡,以满足严格的时间要求。这些特性使得实时系统的调度器更复杂,但也更适用于需要高度可预测性和实时性能的应用程序,如嵌入式系统和实时控制系统。

        实时任务调度器将权衡吞吐量以支持正确性,但同时,它必须确保尽量少的任务迁移。

Real-time load balancing algorithm

        在本节中将详细介绍 Linux 使用的策略来平衡 CPU 之间的实时任务。这些策略已被引入为全局理论调度策略依从性和性能可扩展性之间的权衡。

        push-pull strategy要解决下面这些问题:

  1. 在唤醒时如何最优地安排任务: 当一个任务从休眠状态唤醒时,调度器需要决定将其安排在哪个运行队列(CPU核心)上,以实现最优的执行条件。这是任务在唤醒时的首要问题。

  2. 低优先级任务在高优先级任务运行时的处理: 如果一个低优先级任务在某个运行队列上被唤醒,但该队列上正在运行一个高优先级任务。

  3. 低优先级任务被高优先级任务抢占时的处理: 如果在同一运行队列上有一个低优先级任务正在运行,但被一个高优先级任务抢占了。

  4. 任务降低优先级时的处理: 如果一个任务降低了其优先级,这可能导致此前的低优先级任务变得更高优先级。

         Push操作是在上述情况1、2和3中执行的操作。它涉及将任务从一个运行队列(通常是优先级较低的队列)推送到另一个运行队列,以便更好地满足任务的优先级要求。这可能涉及将任务从一个CPU核心迁移到另一个核心。 Pull操作是在情况4中执行的操作。它涉及从其他运行队列中拉取更高优先级的任务,以满足任务降低优先级后的新要求。

Real-time scheduler data structures and concepts

        

        实时任务的优先级的范围在 0-99 内。这些任务被组织在优先级索引数组的运行队列active中。每个优先级级别都有一个子队列,其中存储了该级别上的实时任务。通过位掩码,调度器可以有效地确定最高优先级的任务,从而在任务调度时选择正确的任务来运行。

        rt-nr-running指运行中的实时任务数。rt-nr-uninterruptible 这个统计数据表示当前系统中处于 "TASK-UNINTERRUPTIBLE"(不可中断)状态的实时任务的数量。不可中断状态通常表示任务正在等待某些事件的发生,例如等待磁盘I/O完成。在这种状态下,任务无法被外部中断,因为它们正在等待某些关键资源,而中断可能导致数据不一致性或其他问题。

        rt-nr-migratory表示可以迁移到其他运行队列的运行队列上的任务数量,一些实时任务绑定到特定的CPU,即使运行队列过载这些任务也不能推开或拉到另一个 CPU。当运行队列包含多个实时任务时且其中至少一个可以迁移到另一个运行队列,设置overloaded字段。每当新的实时任务进入运行队列,它就会更新。

        highest-prio字段是缓存在运行队列上排队的两个最高优先级任务的优先级的结构。

Root domains

        Cpusets 提供了一种将 CPU 划分为进程或一组进程使用的子集的机制。不具有排他性的cpusets一般可以重叠。根域在这里充当一个管理桥梁,它将cpuset的配置和信息集成在一起,以便更容易地管理和控制cpuset。这有助于确保cpuset的资源隔离和管理能够按照预期方式工作,并为系统管理员提供了一种组织和配置cpuset的方式。

        struct root-domain结构相关字段如下:

        

        根域在多核系统中用于控制和管理任务的范围以及全局变量的作用域。它允许系统管理员将任务和资源隔离到特定的域内,以满足不同任务的需求,并确保实时任务的调度和管理受到严格的限制。这有助于提高系统的性能和可靠性。在默认情况下,系统会创建一个高级根域,该根域包括了所有的CPU核心作为成员。这个高级根域覆盖了整个系统,包含了全局范围内的任务调度和管理。

CPU priority management

        每个CPU核心可以处于以下状态之一:INVALID、IDLE、NORMAL、RT1、...RT99。这些状态表示了CPU核心当前的任务优先级情况。为了维护每个CPU核心的状态信息,系统使用了一个二维位图(bitmap)。这个位图的一维表示不同的优先级级别,而二维表示在每个优先级级别上的CPU核心。通过这个位图,可以有效地跟踪每个CPU核心的当前状态和优先级。CPU优先级是指在运行队列(runqueue)上排队的最高优先级任务的优先级。这个信息通常存储在rq->rt.highest_prio.curr中。这个信息对于任务迁移决策非常重要,因为它表示了每个CPU核心上正在运行或等待运行的任务的优先级。

         "pri-to-cpu"字段提供了关于特定优先级级别的所有CPU核心的信息。这个信息被封装在结构体"struct cpupri-vec"中。这意味着对于每个优先级级别,可以查看哪些CPU核心属于该级别。这对于任务分配和调度非常重要,因为某些任务可能需要在特定优先级级别上运行。

        cpu-to-pri"字段用于指示每个CPU核心的优先级。这个字段告诉系统每个CPU核心的任务优先级。

        "cpupri"数据结构的范围限定在根域(root domain)级别。这意味着每个独占CPU集合(exclusive cpuset)都有自己的"cpupri"数据值。这有助于确保不同的cpuset能够独立管理其CPU核心的优先级和任务分配。

Details of the Push scheduling algorithm

        当低优先级的实时任务被更高的任务抢占或当任务被唤醒在一个已经运行在其上的更高优先级任务的运行队列上时,调度器需要搜索适合任务的运行队列。这种搜索运行队列并将其中一个任务转移到另一个运行队列的操作称为push任务。

        推送算法在调用此操作的CPU运行队列上查看最高优先级的非运行实时任务,并考虑所有其他运行队列以找到可以运行的CPU。它搜索优先级较低的运行队列,即当前运行的任务可以被推送任务抢占的队列被推送。该算法优先考虑任务最后一次执行的 CPU,因为它很可能在该位置有热缓存。如果不可能,则找到一个逻辑上最接近最后一个 CPU 的 CPU。如果这也失败,则从掩码中随机选择 CPU。

Details of the Pull scheduling algorithm

        pull 算法查看根域中的所有过载运行队列,并检查它们是否具有可以在调用函数的 CPU 运行队列上运行的不可运行的实时任务.此搜索仅在扫描根域中的所有过载运行队列后中止。因此,拉动操作可能会将多个任务拉入目标队列。

        与"push"操作类似,"pull"操作也分为两个阶段。在第一阶段,它选择一个候选任务,该任务将被尝试拉取到目标运行队列。然后,在第二阶段,实际执行拉取任务的操作。由于并行的调度操作可能在"pull"操作的两个阶段之间执行,因此存在一种情况,即在第一阶段选择的任务在第二阶段执行拉取操作之前可能已经被其他操作更改或不再适合拉取。为了应对这种可能性,"pull"操作不会在第一次操作失败后立即终止。相反,它会继续尝试拉取任务,即使在第一次选择任务后出现了竞态条件。这样,即使出现竞态条件,也有机会成功拉取任务。最后,如果不断尝试拉取任务,并且其他操作也在不断影响任务的分配和位置,可能会导致一些任务被拉取到目标运行队列,但在稍后被"push"操作移到其他CPU核心,这可能引发所谓的任务反弹(task bouncing)现象。任务反弹指的是任务在不同CPU核心之间频繁地移动,可能会导致性能下降和系统不稳定性。

State of the art of Real-Time scheduling on Linux

RTLinux, RTAI and Xenomai

        RTLinux是一个针对标准Linux内核的扩展,旨在为实时应用提供支持。它的开发目的是使Linux内核能够处理实时任务,而不仅仅是传统的非实时任务。RTLinux采用了"中断抽象"的方法。这个方法的核心思想是在标准Linux内核和计算机硬件之间创建一个虚拟硬件层,称为"实时硬件抽象层"(Real-Time Hardware Abstraction Layer,RTHAL)。这个层次实际上只虚拟化了中断。每个来自真实硬件的中断源都被标记为实时或非实时。实时中断由实时子系统处理,而非实时中断由Linux内核管理。这意味着RTLinux能够识别并处理需要实时响应的中断,而将不需要实时响应的中断交给Linux内核处理。

        最初,RTAI是RTLinux的一个变种,旨在为Linux内核添加实时特性。尽管RTAI最初是基于RTLinux代码启动的,但两个项目的API(应用程序编程接口)在不同的方向上进行了演化。主要开发者(教授Paolo Mantegazza)对RTAI代码进行了重写,添加了新功能,并创建了一个更完整和强大的系统。因此,RTAI和RTLinux之间存在差异。RTAI社区还开发了Adaptive Domain Environment for Operating Systems(ADEOS)纳米内核,作为RTAI核心的替代方案。ADEOS纳米内核采用了管道方案,其中每个域(操作系统)都有一个预定义的优先级入口。RTAI是最高优先级的域,始终在Linux域之前处理中断,因此能够为任何硬实时活动提供服务,无论是在之前还是在完全抢占非硬实时任务之前。

        文中提到,之前提到的各种替代方案都是有效的解决方案,因为它们允许实现非常低的延迟。这对于需要高实时性能的应用非常重要,如工业自动化和嵌入式系统。尽管这些解决方案有效,但它们通常对Linux内核进行了相当深入的修改,因此可以被视为相对侵入性的解决方案。这些修改可能会限制实时任务的访问标准Linux设施,如设备驱动程序、网络协议栈等。文中提到,RTLinux和RTAI等实时子系统在与Linux内核相同的内存空间中以相同的特权级别执行。这意味着实时任务与Linux内核之间没有内存保护,如果实时任务出现错误,可能会导致整个系统崩溃。

PREEMPT RT

        CONFIG PREEMPT RT补丁集的主要目标是允许几乎整个内核都可以被抢占,只有极少数的代码区域不可抢占。为了实现这一目标,补丁集采取了一系列措施。补丁集将大多数内核自旋锁替换为支持优先级继承的互斥锁。优先级继承是一种解决非有界优先级反转问题的协议。优先级反转是指高优先级任务必须等待低优先级任务完成关键代码段并释放锁。如果低优先级任务在持有锁的同时被中优先级任务抢占,那么高优先级任务将不得不等待中优先级任务完成,这可能需要很长时间(非有界的等待时间)。优先级继承协议规定,在这种情况下,低优先级任务在持有锁时继承高优先级任务的优先级,防止了被中优先级任务抢占。CONFIG PREEMPT RT补丁集的主要关注点是提高Linux内核的可确定性,通过改进一些不允许可预测行为的部分。尽管优先级继承机制是一个复杂的算法,但它可以帮助降低Linux活动的延迟,达到与中断抽象方法(Interrupt Abstraction)相当的水平。

OCERA

        OCERA是一个欧洲项目,专注于嵌入式实时应用程序。它采用开源方法,旨在为嵌入式系统提供一种综合的执行环境。在OCERA项目中,开发了一个针对Linux 2.4内核的实时调度器。这个调度器旨在允许嵌入式系统实时执行任务,以满足实时性要求。为了尽量减少对内核代码的修改,实时调度器被开发为一个小型补丁(patch)和一个外部可加载的内核模块。补丁的作用是通过一些钩子(hooks)将相关的调度事件导出给外部模块。这种方法的好处是简单灵活,但需要确保在内核中放置这些钩子的位置,这可能会成为挑战。由于钩子的位置可能会随内核版本的变化而发生变化,因此将实时调度器的代码移植到内核的新版本可能会很困难。这是因为内核的不断演进可能会导致钩子的位置和接口发生变化。

 FRESCOR

        FRESCOR是一个研究项目,由欧洲联盟的第六框架计划部分资助。项目旨在开发技术和基础设施,以支持实时应用程序的设计和嵌入式系统中的应用,这些应用具有灵活的调度需求。个框架基于AQuoSA(Adaptive Quality of Service-driven Scheduling Architecture)并进一步添加了基于合同的API和一个复杂的中间件,用于指定和管理系统的性能,尽管FRESCOR项目提出了一种复杂的实时框架,但它也面临着上述提到的一些问题和挑战,这包括与内核版本变化相关的问题以及其他可能的挑战。

LITMUSRT

        LITMUSRT项目是Linux内核的软实时扩展,专注于多处理器实时调度和同步。它旨在扩展Linux内核,使其能够满足实时系统的需求。LITMUSRT修改了Linux内核以支持间歇性任务模型,这意味着它可以处理按间隔执行的实时任务。此外,项目还支持模块化调度器插件,允许用户选择不同的调度算法。它同时支持分区调度(Partitioned Scheduling)和全局调度(Global Scheduling)。LITMUSRT的主要目的是为应用实时系统研究提供一个实验平台。它提供了内核中的抽象和接口,简化了多处理器实时调度和同步算法的原型设计。需要注意的是,LITMUSRT不是用于生产环境的系统,不追求“稳定性”,也不以POSIX兼容性为目标,并且不打算合并到主线Linux内核中。此外,它仅支持Intel(x86-32)和Sparc64架构,不支持嵌入式平台,而后者通常用于工业实时控制系统。

EDF and CBS theory

        首先介绍相关的符号表示

Earliest Deadline First

        在动态优先级调度算法中,任务的优先级在其执行过程中可以发生变化。这意味着任务可能会在不同的时间点具有不同的优先级。在固定优先级调度算法中,任务的优先级在其执行过程中保持不变。任务一旦被分配了一个优先级,就会在整个执行过程中保持相同的优先级。EDF是一种动态优先级调度算法,其特点是根据任务的绝对截止期限来调度任务。在任何时刻,从运行队列中选择的任务是具有最早绝对截止期限的任务。周期性任务的绝对截止期限取决于当前任务的第k个实例(job)。这意味着任务的每个实例都有自己的绝对截止期限,通常可以根据任务的相对截止期限和周期来计算。

       EDF算法中,每个任务的每个实例(job)的优先级是固定的,但是相对于其他任务,任务的优先级会随着时间而变化。这意味着任务的相对优先级会根据其绝对截止期限的不同而变化。EDF通常与抢占式调度器一起使用。当一个拥有更早截止期限的任务准备好运行时,如果当前正在运行的任务的截止期限较晚,那么调度器会暂停当前任务并将CPU分配给刚刚到达的具有最早截止期限的任务。这确保了最早截止期限的任务得到及时执行。EDF算法不仅适用于周期性任务,还适用于非周期性任务(aperiodic tasks)。因为任务的选择仅基于绝对截止期限,所以可以有效地处理各种任务类型。

        给定一个周期或零星任务的任务集,EDF能够处理当且仅当

      它是一种最优的调度算法,因为它可以确保在特定条件下,可调度性的任务集合总是能够被正确调度,不会导致任务错过它们的截止期限。这种性质使得EDF算法在实时系统中具有重要的应用,特别是在抢占式单处理器系统中。

Constant Bandwidth Server

        

        周期性任务通常被认为是硬实时任务,而非周期性任务可能是硬实时任务、软实时任务,甚至是非实时任务,这取决于应用程序的要求。

        在处理这种情况时,可以引入一个专门用于处理非周期性请求的周期性任务,通常被称为“服务器”(server)。服务器的作用是提供对非周期性请求的服务,以实现非周期性任务的平均响应时间较好。服务器任务按照与周期性任务类似的方式进行调度,并用于服务非周期性请求。这种方法可以在实时系统中提高非周期性任务的性能。

        CBS是一种用于处理非周期性请求的服务机制,在动态上下文中使用(周期性任务使用EDF进行调度)。以下是CBS的主要特征和工作原理:

CBS特征:

  •        CBS由有序对(Qs,Ts)来定义,其中Qs是最大预算(budget),Ts是服务器的周期。服务器带宽的比率Us = Qs / Ts称为服务器带宽。
  •         服务器管理两个内部变量来定义其状态:cs是在时间t时的当前预算(初始化为零),ds是服务器分配给请求的当前截止期限(初始化为零)。

请求管理:

  • 如果在当前请求仍然活动时新请求到达,新请求将排队在服务器队列中(可以使用任意规则进行管理,例如FIFO)。
  • 如果在服务器处于空闲状态时新请求到达(在时刻t),系统将检查是否可以重用当前预算和截止期限。如果cs ≤ (t - ds)Us,则可以使用当前服务器值进行请求调度,否则必须将预算恢复到最大值(cs = Qs),并计算新的截止期限(ds = t + Ts)。
  • Qs(最大预算)表示了服务器在一个周期内可以分配给非周期性任务的最大资源数量。
  • cs(当前预算)表示在某一时刻(t)内仍然可用的资源数量。cs的值在处理请求时逐渐减少,直到耗尽为止

请求调度:

  • 当一个请求完成时,服务器将从内部队列中选择下一个请求(如果存在)并使用当前预算和截止期限进行调度。
  • 当预算耗尽(cs = 0)时,它将被重新充值到最大值(cs = Qs),并且当前截止期限将被推迟一个周期(ds = ds + Ts)

CBS算法的基本思想:

  • CBS算法的基本思想是,当新请求到达时,会分配一个截止期限,该期限是使用服务器带宽计算的,并将请求插入EDF准备队列中。
  • 如果一个非周期性任务尝试执行超出其分配的服务器带宽,其截止期限将被推迟,从而降低其EDF优先级,以便其他任务可以抢占它。

EDF scheduling on SMP systems 

        SMP平台由M个相同速度的处理器(或核心)组成。在多核平台上,有三种不同的方法来调度任务集合:

  1. 分区式(Partitioned):在这种方法中,EDF任务被静态地分配到处理器上,并且每个处理器上的任务都按照EDF的方式进行调度。任务被固定到特定的运行队列,不能在不同队列之间迁移。因此,在M处理器系统中,有M个独立调度的任务集合。这种方法的主要优点是简单性,因为多处理器调度问题被简化为M个单处理器问题。此外,任务不会产生迁移开销,因为没有任务迁移。然而,P-EDF的缺点包括寻找任务到处理器的最佳分配(这是NP难问题)的复杂性,以及无法调度某些特定任务集合,这些集合只有在任务集不分区时才能被调度。

  2. 全局式(Global):在这种方法中,任务的作业被插入到全局截止时间排序的就绪队列中,然后根据每个时刻可用的处理器将其分配给就绪队列中最接近截止时间的作业。

  3. 混合式(Hybrid):在这种方法中,任务被静态地分配到固定大小的集群中,类似于在P-EDF中将任务分配给处理器的方式。然后,使用G-EDF算法来调度每个集群中的任务,就好像每个集群都由一个独立的调度系统构成。

        没有EDF的任何变体是最优的,因此在每个EDF变体下都可能发生截止时间的错误。然而,已经证明,在G-EDF下,截止时间的延迟在可行的系统中是有界的,这对于许多软实时应用来说是足够的。在H-GDF方法下,只要分配给每个集群的任务的总利用率不超过每个集群的核心数,每个集群的截止时间延迟就是有界的。

The SCHED DEADLINE scheduling class

        开发者选择了"SCHED DEADLINE"而不是"SCHED EDF"的名称,因为EDF并不是唯一的最后期限算法,未来可能希望切换到不同的算法,而不会强制应用程序更改所请求的调度类。这个项目的合作伙伴(包括Ericsson Research、Evidence S.r.l.、AKAtech)强烈认为,像Linux这样的通用操作系统应该提供一个标准的实时调度策略,同时允许以通常的方式调度非实时任务。

        文本还指出,现有的调度类(例如SCHED FAIR和SCHED RT)在它们各自的应用领域表现非常出色。然而,它们无法提供时间敏感应用程序可能需要的保证。在使用SCHED FAIR时,任务无法关联到时间约束的概念。任务经历的延迟(即任务连续执行之间的时间)是不确定的,无法限定,因为它高度依赖于系统中运行的任务数量。这些问题在运行时间敏感或控制应用程序时特别关键。没有实时调度器,事实上,无法对正在开发的系统进行可行性研究,开发人员不能确保在任何情况下都能满足时间要求。这阻碍了Linux在工业环境中的使用。 

 Main Features 

        "SCHED DEADLINE"实现了最早期限优先(Earliest Deadline First)算法,并使用了恒定带宽服务器(Constant Bandwidth Server)来提供任务之间的带宽隔离。这个调度策略不对任务的特性做出任何限制性假设:它可以处理周期性、间歇性或非周期性的任务。这个新的调度类是从零开始开发的,没有从任何现有项目开始,利用了Linux调度器目前提供的模块化特性,以减少对系统的侵入性。这个实现与当前(撰写本文时)的主流内核保持一致,将会与未来的内核版本保持一致。

        "SCHED DEADLINE"依赖于标准的Linux机制(例如,控制组)来本地支持多核平台,并通过标准API提供分层调度。这使得它能够更灵活地适应不同的系统需求,而不需要对内核进行大规模修改。

Interaction with Existing Policies

        在Linux内核中添加SCHED DEADLINE调度类不会改变现有的调度策略的行为,包括最佳效能和实时调度策略。然而,由于当前的Linux调度器架构,不同的调度类之间存在一些交互作用。事实上,由于每个调度类都被要求按照它们在链表中的顺序提供可运行的任务,"较低"的调度类实际上在"较高"调度类的空闲时间内运行。确定新的调度类应该放在何处是确保它的行为正确的关键点。开发人员选择将其放置在现有的实时和正常调度类之上,以便截止时间调度可以以最高优先级运行,否则无法保证截止时间将会被满足。这确保了SCHED DEADLINE能够优先运行,并在其期限内完成任务。

Current Multiprocessor Scheduling Support

        Linux中的每个CPU都有自己的就绪队列,因此Linux处理多处理器调度的方式通常称为分布式运行队列。如果需要的话,任务可以在不同的队列之间迁移。也可以将某些任务固定在某个处理器或处理器集上,设置所谓的调度亲和性。SCHED DEADLINE的开发人员最初选择实现P-EDF解决方案,其中不会发生动态进程迁移,除非更改任务的亲和性。这意味着任务在执行过程中不会自动在不同的处理器之间迁移,除非明确更改了它们的亲和性设置。

        最近,SCHED DEADLINE项目的当前维护者Juri Lelli扩展了其实现,以允许G-EDF和H-EDF调度方案。在SCHED DEADLINE的新版本中发现了与所有其他调度类别遵循的相同的分布式运行队列方法,还包括用于在系统中平衡负载的推送和拉取算法。显然,这里的迁移是通过比较任务的截止日期来完成的。这一设计的目标是尽可能接近G-EDF规则:“在具有M个CPU的系统上,M个最早的截止日期就绪任务在CPU上运行”。我们使用“尽可能接近”这个术语,因为很明显,可能会有一些违反上述规则的时间间隔:实际上,调度程序只能在任务被唤醒或其相对截止日期发生更改时迁移任务,类似于我们在SCHED RT调度类任务。换句话说,调度程序仅使用本地信息来强制执行计划,偶尔依靠推送和拉取机制来实现全局平衡。

        与具有单一系统范围运行队列的全局调度策略相比,这种解决方案在底层核心数量增加时具有更好的可扩展性优势。事实上,我们必须记住,在M处理器的SMP系统上,我们可以同时执行多达M个调度程序实例,这些实例竞争获取单一运行队列上的锁。现在,让我们简要讨论SCHED DEADLINE对多核环境的支持背后的数据结构和算法。在这里,像在SCHED RT中一样,使用根域的概念,但将struct根域扩展为管理截止日期任务,因此我们可以找到以下附加字段:

        

        字段dlo-mask显示了哪些CPU过载,dlo-count 记录了过载CPU的数量。剩下的字段struct cpudl对于加速push机制非常重要。在当前的实现中,这个数据结构是一个最大堆,它保存了所有运行队列中最早期限任务的截止日期。正如我们将在本文档的剩余部分中看到的,本论文的主要目标是设计和开发更有效的数据结构,以加速迁移算法。

        为了实现任务迁移机制,SCHED DEADLINE还在其运行队列结构上使用了一些特殊字段,

struct earliest dl

        这是一个用于缓存两个最早期的截止时间(deadline)任务的数据结构。这个缓存的目的是为了加速任务的推送(push)和拉取(pull)决策。通常,在实时任务调度中,任务需要按照截止时间的先后顺序进行调度,因此这个字段存储了最重要的两个任务的信息。

dl nr migratory

        这个字段表示可以迁移的截止时间任务的数量。在某些情况下,任务可能需要从一个运行队列(runqueue)迁移到另一个,这个字段记录了可以执行这种迁移的任务数量。

dl nr total

        这个字段表示在运行队列中排队的总截止时间任务的数量。这个字段通常用于跟踪队列中实时任务的数量。

pushable dl tasks root

        这是一个数据结构,用于管理可推送(pushable)的截止时间任务。通常,截止时间任务可以按照它们的截止时间被推送到更高优先级的队列中。这个字段可能表示一个红黑树,其中包含了可以推送的任务。

pushable tasks leftmost

        这是指向最早截止时间可推送任务的指针。截止时间任务通常按照它们的截止时间排序,这个字段指向最早截止时间的任务,以便能够快速地找到它并进行推送操作。

        总的来说,struct dl_rq 存储了与实时任务调度和管理相关的信息。这些信息对于确保任务按照它们的截止时间得到适时调度非常重要,特别是在实时操作系统中,如嵌入式系统、实时控制系统等。这些字段的存在和管理有助于提高调度器的性能和效率,确保实时任务得到及时处理。

SCHED DEADLINE Push implementation

        

关于推送函数(push_dl_task)的操作流程。

检查过载标志(overloaded flag)

首先,推送函数检查了操作系统内的一个标志,即overloaded标志。该标志表示当前运行队列是否处于过载状态,也就是是否有太多实时任务等待执行。如果没有过载,就没有必要进行推送操作,因此需要先检查这个标志。

从可推送红黑树(pushable rbtree)中选择任务

如果发现当前队列过载,函数接下来会从可推送红黑树中选择一个任务。这个树结构存储了可以被推送的实时任务。它们是那些可以在不破坏系统性能的前提下被推送到其他队列的任务。

查找并锁定可以立即运行的运行队列

一旦选择了一个要推送的任务,接下来的目标是找到一个可以立即运行该任务的运行队列,也就是目标运行队列。这个目标运行队列应该可以立即运行这个任务,而不是等待其他任务的完成。这就意味着要找到一个目标运行队列,该队列中的任务会被新任务(推送的任务)抢占执行。

迁移任务

如果找到了目标运行队列,那么实际的任务迁移就会发生。这意味着将被推送的任务从当前运行队列中移除,并将其迁移到目标运行队列。这可以通过锁定目标队列、解锁当前队列、将任务从一个队列移到另一个队列以及重新调度任务来完成。

重试或退出

如果找不到合适的目标运行队列,那么函数可以选择重试,即重新选择要推送的任务,然后再次尝试找到目标队列。如果没有适合的目标队列并且没有更多可推送的任务,那么函数可能会退出,表示没有可推送的任务或推送操作不再适用。

Max-heap cpudl data structure for push operation

        cpudl 数据结构是一个用于跟踪每个 CPU 上运行任务的截止时间,并确保任务按照最早截止时间的顺序进行调度的数据结构。这有助于 SCHED DEADLINE 在多核系统上有效地管理任务的调度。具体实现可以采用不同的数据结构,但最大堆是一种有效的选择。

  • 查找操作(cpudl find):当一个运行在某个 CPU 上的调度程序实例需要迁移一个任务并需要知道可以推送到哪里时,它会调用查找操作。查找操作会在 cpudl 数据结构中查找一个适当的 CPU,用于迁移任务。在这个操作中,有以下参数:

    • cp:同上文描述,是当前 CPU 的索引。
    • dlo_mask:在当前版本中未使用。
    • p:指向要迁移的任务的指针,用于读取任务结构中的 CPU 亲和性。这样,cpudl find 可以始终返回一个任务 p 可以运行的合格 CPU 索引。
    • later_mask:指向 CPU 位掩码的指针,用于在有多个空闲 CPU 时设置所有与迁移有关的位。特别是,当有多个空闲 CPU 时,cpudl find 允许调用者选择最佳的 CPU。
  • 设置操作(cpudl set):当一个运行在某个 CPU 上的调度程序实例需要更新 cpudl 数据结构以反映底层运行队列状态的更改时,它会调用设置操作。在这个操作中,有以下参数:

    • cp:指向 cpudl 数据结构实例的指针。实际上,每个根域(root domain)都有一个不同的 cpudl 数据结构实例。
    • cpu:调用该函数的 CPU 的索引。
    • dl:当前正在运行的任务的新截止时间值。
    • is_valid:一个标志,指示运行队列中是否至少有一个带截止时间的任务。

 SCHED DEADLINE Pull implementation

        在 pull 操作中,调度程序会检查所有根域的超负载运行队列,以查看是否有任务可以立即运行在调用运行队列中。如果找到这样的任务,该操作将执行迁移,否则它将继续查找其他运行队列,如果没有更多的运行队列需要考虑,则退出。因此,可以说 push 和 pull 操作的主要目标是在目标运行队列中执行抢占。

        与 pull 操作不同,push 操作不需要检查每个运行队列,因为它的目标是将任务推送到另一个运行队列。在当前的实现中,与 pull 操作的 cpudl 数据结构不同,push 操作似乎没有类似的数据结构来选择目标运行队列。在大规模的 SMP 系统中,拥有大量核心的情况下,这可能导致执行 pull 操作的延迟不可接受。

Task Scheduling

        

SCHED DEADLINE调度类的关键特点和工作原理:

  1. 任务类型:SCHED DEADLINE不对任务的特性做出严格的限制,可以处理以下类型的任务:

    • 周期性任务:通常在实时和控制应用程序中使用。
    • 非周期性任务:可以处理不具有固定周期的任务。
    • 间歇性任务:这些任务是非周期性任务,但它们具有最小的任务释放间隔,通常在软实时和多媒体应用程序中使用。
  2. 时间隔离:SCHED DEADLINE确保了时间隔离,这意味着每个任务的时间行为(即其满足截止时间的能力)不受系统中任何其他任务的行为影响。即使一个任务的行为不当,它也不能利用比其分配的执行时间更多的时间并垄断处理器。

  3. 任务预算:每个任务都被分配一个预算,包括任务运行时间(sched runtime)和周期,周期被视为其截止时间(sched period)。任务被保证在每个sched period(任务利用率或带宽)内执行一定时间。当一个任务试图执行超过其预算时,它会被减速,直到其下一个截止时间的时间点。然后,任务再次可运行,其预算被重新填充,新的截止时间为其计算。这是CBS算法在其硬预留配置下的工作方式。

  4. 周期性任务的优化:对于“标准”周期性任务,为了减少CBS的干扰,开发人员允许这些任务在等待下一次激活之前指定当前实例的结束。这可以避免它们(如果它们表现良好)受到CBS的干扰。

Usage and Tasks API 

        

  1. 系统范围的带宽控制:SCHED DEADLINE用户需要在运行其实时应用程序之前指定系统范围的SCHED DEADLINE带宽。他们可以通过将所需的值写入 /proc/sys/kernel/sched_dl_period_us/proc/sys/kernel/sched_dl_runtime_us 文件来完成此操作。这两个文件的值将表示允许SCHED DEADLINE任务使用的整体系统带宽。如果不希望限制SCHED DEADLINE的带宽控制,可以将值 -1 写入 /proc/sys/kernel/sched_dl_runtime_us 文件。

  2. 系统调用的扩展:由于修改现有系统调用 sched_setscheduler(...)struct sched_param 参数会对现有应用程序的二进制兼容性产生问题,因此未对其进行扩展。因此,开发人员引入了另一个系统调用,称为 struct sched_param2,它允许为使用SCHED DEADLINE策略运行的任务分配或修改上述的调度参数(即 sched_dl_runtimesched_dl_period

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值