系统学习AutoSAR ETAS RTA-OS嵌入式操作系统(三)任务Tasks

系统学习AutoSAR ETAS RTA-OS嵌入式操作系统(三)任务Tasks

4. Tasks

一个需要同时执行多个不同活动的系统被称为并发系统。这些活动可能有一些软件部分,因此提供它们的程序必须同时执行。当它们需要共享数据时,这些程序必须相互合作。

实时系统中的每个并发活动都由一个任务表示。大多数应用程序代码存在于任务中。如果您有许多必须同时执行的任务,则需要提供一种允许并发的方法。一种方法是为每个任务设置一个单独的处理器。您可以使用并行计算机,但是这种解决方案对于许多应用程序来说过于昂贵。

更具成本效益的方法是上逐个运行一个任务,并在它们之间切换,以使它们看起来像是同时执行的。

4.1 Scheduling 调度

RTA-OS提供了一个调度器,根据在配置时分配的固定优先级来切换任务。优先级对紧急程度的反映。有许多方案可以用来先级,但你可能听说过的常见方案有:

Deadline Monotonic Assignment 截止时间单调分配将更高优先级分配给具有较短截止日期的任务。

Rate Monotonic Assignment 速率单调分配将更高优先级分配给需要最频繁运行的任务。

无论您选择如何分配优先级,任务执行的顺调度策略确定。调度策略决定了任务实际运行的时间。

AUTOSAR OS支持两种调度策略:

  1. 抢占式调度。Preemptive Scheduling.
    固定优先级抢占式调度算法很简单:运行最高优先级的就绪任务。如果一个任务正在运行,而有一个更高优先级的任务准备好要运行,那么更高优先级的任务将抢占正在运行的任务。这被称为任务切换。当更高优先级的被抢占的任务恢复执行。
    对于运行时满足所有任务截止时间的系统来说,抢占式调度是最有效的调度策略,并且能够保证从激活(准备好要运行)到终止之间所需时间最短。这个时间被称为该任务的响应时间。预先调度的系统需要考虑抢占对共享数据的影响,并可能需要引发控制机制(参见第6章)。

  1. 非抢占式调度。Non-Preemptive scheduling.

    操作系统运行最高优先级的准备就绪任务,类似于抢占式调度。然而,与抢占式如果有一个更高优先级的任务变为就绪状态,则它将保持就绪状态直到正在运行的任务结束 - 不会进行抢占。这意味着一旦一个非抢占开始运行,它将一直执行直到完成并停止。

非抢占式调度比抢占式调度系统响应性较差(即任务通常具有更长的响应时间),但是该系统不需要担心并发问题,因为调度模型不允许对共享数据进行并发访问。实际上,AUTOSAR OS提供了对一种称为合作调度的第三种调度类型的支持,因为它允许非抢占式任务告诉操作系统何时可以被抢占。我们之所以说AUTOSAR OS支持2种策略是因为只有两个配置选项 - 第三个必须自行构建。

  1. 合作式调度。Cooperative scheduling.

    操作系统运行最高优先级的准备就绪任务。如果有更高优先级的任务变为就绪状态,那么它将保持就绪状态直到以下情况之一发生:正在运行的任务结束(与非抢占式调度类似);或者正在运行的任务通过调用Schedule() API告知操作系统可以被抢占。当进行Schedule()调用时,较高优先级正在运行的任务,并且称为发生了任务切换(与抢占式调度类似)。当较高优的任务完成后,被会恢复执行。

在所有这些类型的调度中,重要的是要无论是抢占式还是非抢占式任务都可以打断(即可被抢占)。第5章提供了如何处理中断的更多信息。

4.2 Basic and Extended Tasks 基本和扩展任务

RTA-OS支持两种类型的任务:

  1. 基本任务。基本任务开始、执行和终止(这通常被称为单次任务模型)。只有在基本任务终止或被优先级更高的任务抢占时,它才会释放处理器。这种行为使得它们非常适合嵌入式控制功能。基本任务快速高效。
  2. 扩展任务。扩展任务开始、执行、等待事件并终止(可选择的)。扩展任务能够在执行过程中自愿暂停自己,提供了一种同步点的方式。这个特性使得扩展任务比基本任务更适合需要中间同步(例如等待用户交互)的功能需求。
4.2.1 Task States 任务状态

基本任务在一个三状态模型上运行。一个基本任务可以存在于以下状态

  1. Suspended. 挂起
  2. Ready. 就绪
  3. Running. 运行

扩展任务在等待事件时可以进入额外的状态:

  1. Waiting.

图4.4展示了3和4状态任务模型:

所有任务的默认状态都是挂起。通过激活过程动到就绪状态。重要的是要理解,激活并不会导致任务运行 - 它只是使其准备好运行。激活可以通过多种方式进行,例如在代码中调用ActivateTask() API或由某些触发器引起,如警报到期alarm(见第9章)或时间表到期点schedule(见第10章)。

当一个任务成为系统中优先级最高的任务时,RTA-OS将该任务移至运行状态,并在任务中出现第一条语句时开始执行任务。这通常被称为调度任务。一个任务可能在执行过程中被准备好的其他更高优先级的任务抢占。

如果一个优先级更高的任务准备好运行,当前正在执行的任务将被抢占,并从运行状态转移到就绪状态。这意味着在任何时候只能有一个任务处于运行状态。

一个任务通过终止返回到挂起状态。稍后可以再次使一个任务变为就绪,并且整个过程可以重复。

基本任务和扩展任务在就绪、运行和挂起状态方面的行为是相同的。然而,扩展任务还可以进入等待状态。当一个扩展任务自愿地通过等待事件来暂停自己时,它会从运行状态转移到等待状态。

事件简单地指的是用于提供系统事件指示器的操作系统对象。事件的例子包括数据准备好使用或传感器值被读取。关于事件的更多信息可以在第7章中找到。

当一个扩展任务进入等待状态时,操作系统将调度最高优先级的准备运行的任务。当事件被设置时,该任务从等待状态转移到就绪状态。需要注意的是,扩展任务返回到就绪状态而不是运行状态。这是因为,在扩展任务处于等待状态期间,可能会激活并调度其他更高优先级的任务。

4.2.2 Task Priorities 任务优先级

AUTOSAR OS允许任务共享优先级。当任务具有相同的优先级时,共享优先级中的每个任务将互斥地运行。这意味着如果一个任务正在运行,则其执行将与所有其他共享相同优先级的任务串行进行。

当任务共享优先级时,它们按照先进先出(FIFO)的顺序从就绪状态中释放。

**集成指南4.1:**当共享优先级和排队任务激活同时使用时,RTA-OS在优先级水平上维护一个内部队列。如果您希望操作系统快速高效,应避免这种类型的配置。

如果需要对一组任务进行序列化执行,则最好使用唯一的优先级和AUTOSAR OS的内部资源(参见第6.5节),而不是共享任务优先级。使用内部资源可以保证序列化,就像共享优先级一样,并且任务优先级的唯一性意味着当多个任务同时准备就绪时,操作系统具有静态定义的调度顺序。

**集成指南4.2:**在实时编程中,任务之间共享优先级是不好的做法,因为它会阻止您对系统进行可调度性分析。这是因为,在一般情况下,共享优先级使得任务的释放点(即响应时间测量的起始点)无法计算。如果无法确定释放发生的时间,则无法判断任务是否能够满足其截止日期!

4.2.3 Queued Task Activation 队列任务激活

在大多数情况下,只有当任务处于挂起状态时才会激活它。实际上,AUTOSAR OS将任务在就绪、运行或等待状态下激活视为错误情况。

然而,有些情况下你可能需要实施一个系统,在这个系统中同一任务必须被激活多次,但是连续激活之间的最短时间可以小于运行该任务所需的时间。例如,你可能正在一个任务中解包CAN总线帧,并且需要处理网络上突发出现的帧。

这意味着您需要在运行时对任务激活进行排队。AUTOSAR OS允许您排队激活基本任务,以帮助您构建这类应用程序。像AUTOSAR OS中的其他东西一样,任务队列的大小是静态配置的。您必须指定该任务可以挂起的最大激活数

如果在尝试激活任务时队列已满,则会将其视为错误并忽略激活。

当然,你可能有一些共享优先级并使用排队激活的任务。在这种情况下,任务按照FIFO顺序排队在一个队列中,该队列的长度等于每个共享相同优先级的任务的队列长度之和。然而,每个任务只能使用自己数量的条目。

4.2.4 Asynchronous Task Activation 异步任务激活

AUTOSAR OS允许从任务实际运行的内核以外的内核激活任务。
虽然这可能很有用,但可能会影响性能,因为要完全符合AUTOSAR,所有任务激活(包括SetEvent)必须阻塞调用者,直到任务状态更新为止。在内部,操作系统必须使用内部自旋锁来与拥有的核心协调状态变化。这可能会对所有核心的性能产生重大影响

RTA-OS提供了一个操作系统选项异步任务激活,它可以改变激活行为,这样就不会阻塞内核,而是将消息发送到拥有要激活的任务的内核上的队列。拥有核心是执行任务实际激活的核心,因此它是唯一更改任务状态的核心。不使用任务自旋锁,因为不需要保护任何状态。

任务激活队列的大小默认设置为10。如果该队列已满,将返回错误代码E_OS_SYS_XCORE_QFULL。可以使用AsyncQ OS选项来更改队列大小。

集成指南4.3: 当选择异步任务激活时
E_OS_LIMIT指示在拥有该任务的内核上触发,而不是激活它的内核。

4.3 Conformance Classes 一致性等级

你现在知道任务可以:

  • Be basic or extended 可以是基本的或扩展的
  • Can share priorities 可以共享优先级
  • Can queue activations. 可以排队激活。

然而,AUTOSAR OS对可以一起使用的功能施加了一些限制。这些限制被称为符合性类别,用于将任务功能分组以便理解,并允许标准的部分实现,并为不同类别的应用提供可扩展性。

AUTOSAR OS有四个符合性类别:

  • BCC1 - 基本任务,具有唯一的优先级且没有排队激活。
  • BCC2 - 基本任务,共享优先级和/或排队激活。
  • ECC1 - 扩展任务,具有唯一的优先级且没有排队激活。ECC1 任务类似于 BCC1 任务,但它可以等待事件。
  • ECC2 - 扩展任务,共享优先级且没有排队激活。需要注意的是,与 BCC2 任务不同,ECC2 任务不能进行排队激活。

下表简要总结了可以在不同类别的AUTOSAR OS系统中使用的任务类型:

每个符合类别都需要更多的资源——一个BCC1系统比ECC2系统要快得多且更小。您不需要担心使用哪种符合类别——RTA-OS支持所有符合类别,并将从您的操作系统配置中计算出符合类别。

注1: 仅适用于ECC2系统内的基本任务。无法排队激活扩展任务。

4.4 Maximizing Performance and Minimizing Memory 最大化性能,最小化内存消耗

RTA-OS被设计成在目标应用程序上极力减少代码和数据的使用。它将分析应用程序的特性,并生成一个只包含所需功能的系统。

任务特征的选择对最终应用程序的大小和速度有很大的影响。天下没有免费的午餐,所以当你在应用程序中添加使用更高级任务类型的任务时,系统将不可避免地变得更大、更慢。

一个具有一个或多个BCC2任务的系统比只有BCC1任务的系统开销更大。即使允许多个激活,没有共享优先级的系统也比具有共享优先级的系统更高效。

具有ECC1任务的系统开销更大,而具有一个或多个ECC2任务的系统开销最大。
为了使RTA-OS尽可能高效,您应该只使用基本任务,而不是共享优先级。

4.5 Task Configuration 任务配置

与您可能见过的其他实时操作系统不同,AUTOSAR OS(以及RTA-OS)中的任务是静态定义的。采用这种技术是因为它可以节省RAM和执行时间。

不能动态地创建或销毁任务。关于任务的大部分信息可以离线计算,从而允许将其存储在ROM中。

RTA-OS支持的任务数量取决于您的端口,您应该参考目标/编译器端口指南以获取更多详细信息。对于所有端口,如果将任务数量限制在微控制器的本机字大小范围内,RTA-OS可以提供高度优化的系统。

当您配置任务属性时,您很可能会使用rtaoscfg配置工具。图4.5显示了任务配置条目。

AUTOSAR任务有5个属性:

  • Name 名称。这名称用于引用或提供一个句柄,以便您编写C代码来实现任务功能。

  • Priority 优先级。调度程序使用优先级来确定任务运行的时间。优先级不能动态改变。在RTA-OS中,零是最低可能的任务优先级。较高的任务优先级由较大的整数表示。任务可以共享相同的优先级,但如果您正在构建实时系统,则不应该这样做,因为无法进行分析。

  • Scheduling 调度。一个任务可以完全可抢占或不可抢占地运行。一般来说,为了获得最佳的应用性能,应完全可抢占的调度方式而非不可抢占的调度方式。

  • Activations. 激活。在准备就绪状态下可以排队的任务激活的最大数量。对于BCC1、ECC1和ECC2类型的任务,激活数始终为一。这意味着只有当这些类型的任务处于挂起状态时才能被激活。如果尝试在非挂起状态下激活此类任务,将会导致错误发生。大于一的值表示操作系统将排队激活(例如平滑应用程序中瞬态峰值负载)。

  • Autostart 自动启动。这控制着当你启动操作系统时,任务是否会自动开始。

集成指南4.4: 每个目标(通常是256或1024,取决于目标处理器)可以定义的任务数量是固定的。您的目标机/编译器端口指南将包含更多信息。

4.5.1 Scheduling Policy 调度策略

一个完全可抢占的任务可以被优先级更高的任务抢占。这意味着一个优先级更高的任务准备好运行时,它将被首选运行。

在配置时,您可以通过声明任务为非可抢占的来防止其被抢占。声明为非可抢占的任务不能被其他任务抢占。当一个非可抢占的任务进入运行状态时,它将一直执行到完成并终止(除非它进行了Schedule()调用,如4.10节所述)。因此,将任务设置为非可抢占意味着如果一个较低优先级的任务在高优先级任务之前启动,则高优先级任务将被阻塞,在较低优先级任务运行期间无法执行。这称为阻塞。使用非可抢占性任务的系统通常比采用可抢占性运行方式的系统响应速度较慢。

即使一个任务是非抢占式的,它仍然可以被isr中断

您经常会发现,没有必要使用不可抢占的任务,因为还有其他更合适的方法可以达到相同的效果。如果您使用这些其他技术,通常会产生一个响应更快的系统。稍后您将了解更多有关这些技术的信息,但它们包括:

  • 使用标准资源序列化对数据或设备的访问。
  • 使用内部资源来精确指定哪些其他任务不会导致抢占
4.5.2 Queued Activation 队列激活

在大多数情况下,只有当任务处于挂起状态时才会激活它。但是,您可能需要实现这样一个系统:同一个任务必须被多次激活,并且连续激活之间的最短时间小于运行该任务所需的时间。

如果发生这种情况,您将在任务处于就绪状态或运行状态时激活它。这意味着激活将丢失。

为防止丢失激活,必须指定任务所需的最大多重激活数。

集成指南4.5: 根据AUTOSAR OS标准,此功能仅适用于基本任务。不能为扩展任务指定多个激活。

您将使用rtaoscfg来指定同时激活任务的最大数量。 图4.6显示,对于本例中的任务,最大激活数设置为20。

当指定多个激活时,RTA-OS会自动识别该任务为BCC2。在构建应用程序时,RTA-OS将计算每个BCC2任务所需的多个激活队列的最大大小。

当BCC2任务共享优先级时,RTA-OS使用FIFO队列来保存待处理的激活。如果一个BCC2任务在您的AUTOSAR OS应用程序中具有唯一的优先级,那么RTA-OS将自动优化排队策略以计数激活。计数激活比先进先出激活更有效,应该尽可能使用。

4.3.5 Auto-starting Tasks 自动开始任务

任务可以自动启动,这意味着当操作系统启动时,它们在StartOS()期间被自动激活。

对于启动、运行然后终止的基本任务,自动启动任务将使其运行一次,然后返回到挂起状态(从那里可以再次激活它)。自动启动主要用于启动等待事件的扩展任务,因为它不需要编写代码来激活任务。

rtaoscfg可用于指定任务仅在特定的应用程序模式下自动激活,选择相关的应用程序模式并选择要自动激活的任务。

在图4.7中,TaskD在OSDEFAULTAPPMODEServiceMode应用模式下自动启动,而在LimpHomeModeNormalOperatingMod下不会自动启动

4.6 Stack Management 堆栈管理

RTA-OS使用单堆栈模型,这意味着所有任务和ISR都在单个堆栈上运行。单栈就是应用程序的C栈。

当任务运行时,它的堆栈使用量会像正常情况一样增长和减少。当任务被抢占时,高优先级任务的堆栈使用继续在同一堆栈上(就像标准函数调用一样)。当任务终止时,它正在使用的堆栈空间被回收,然后重新用于下一个最高优先级的任务运行(同样,就像标准函数调用一样)。图4.8显示了单个堆栈在任务被声明、抢占和终止时的行为。

在单栈模型中,栈的大小中优先级级别的数量成比例,而不是任务/ISR的数量。这意味着共享优先级(直接共享或通过共享内部资源或配置为非抢占式)的任务永远不能同时存在于堆栈上。硬件中共享优先级也是如此。这意味着通过简单地更改配置,您可以系统响应性(即任务或ISR完成所需时间)来换取堆栈空间。

图4.9展示了相同任务集的执行情况,与图4.8中的到达模式相同,但这次任务是非抢占式调度。可以看到,高优先级任务的响应时间比抢占式调度时要长得多,但整体堆栈要低得多。

单堆栈模型还极大地简化了在链接时的堆栈空间分配,因为您只需要为整个系统堆栈分配一个内存部分,就像您根本不使用操作系统一样。

4.6.1 Working with Extended Tasks 处理扩展任务

RTA-OS独特地扩展了单堆栈模型,在不影响基本任务性能的情况下为扩展任务提供支持。

在RTA-OS中,扩展任务的生命周期如下:

Suspended -> Ready 挂起 -> 就绪,该任务已添加到就绪队列中。

Running -> Ready 运行 -> 就绪, 扩展任务被抢占。如果抢占任务是基本任务,则按照正常情况将其调度到堆栈的顶部。如果抢占任务是扩展任务,则按照预先好的所有较低优先级任务的最坏情况抢占深度进行调度

Running -> Waiting 运行 -> 等待, 任务的等待事件堆栈上下文,包括操作系统上下文、本地数据、函数调用的堆栈帧等,被保存到内部操作系统缓冲区中。

Waiting -> Ready 等待 -> 就绪, 该任务被添加到就绪队列中。

Ready -> Running,就绪 -> 运行, 任务的“等待事件堆栈”上下文从内部操作系统缓冲区复制回到堆栈中,其预计最坏情况抢为所有较低优先级任务。

此流程允许管理扩展任务的额外成本仅应用于扩展任务本身。包含扩展任务的系统中的基本任务与只有基本任务的系统中的基本任务具有相同的性能。

此生命周期的关键部分是在最坏情况下抢占深度的调度/恢复以及堆栈上和堆栈外的复制。在最坏情况下抢占点的调度保证了无论何时扩展任务在等待后恢复,它都可以在内存中完全相同的位置恢复其局部变量。它保证了低优先级任务的每一个可能的抢占模式都不会超过扩展任务的调度点。扩展任务Task D的调度-等待-恢复周期如图4.10所示。

复制开和关闭允许恢复扩展任务堆栈上下文。这是必要的,因为在等待扩展任务期间可能会发生更高优先级的任务和/或ISR。这些可能况抢占点(请记住,最坏情况点仅适用于较低优先级对象)更大的堆栈空间,从而覆盖了扩展任务的上下文。然而,固定优先级抢占调度保证,在恢复扩展有更高优先级的任务准备运行(如果存在这种情况,则无法恢复)。

扩展任务管理需要告知RTA-OS每个任务和中断服务程序(ISR)使用了多少堆栈空间。下面的部分将种配置参数。

4.6.2 Mandatory Stack Information 强制的堆栈信息

计算出的最坏情况调度点定义了调用StartOS()时需要启动扩展任务的相对于堆栈指针地址的字节数。这些偏移量作为ROM数据存储在扩展任务控制块中,并在运行时添加到堆栈的基址中。

这意味着RTA-OS需要告知关于堆栈使用的各种参数。捕获端口相关的,您应该阅读目标/编译器端南以获取额外的指导。

RTA-OS提供了用于测量任务和中断服务程序最坏情况下堆栈值的运行时特性。有关更多信息,请参见第14.3节

通常,所有端口都允许您指定以下值。所有数字以字节为单位:

用于c启动的堆栈(SpPreStartOS): Stack used for C-startup (SpPreStartOS)

在调用StartOS()时,已经使用的堆栈量。

这个值只是添加到操作系统在运行时支持所有任务和中断所需的总堆栈大小上。通常,您使用它来获取链接器必须分配的堆栈量。

在计算最坏情况调度点时不需要此值,可以安全地将其设置为零,除非您希望在堆栈使用情况报告中计算最坏情况的总体堆栈使用情况。

如果操作系统配置发生变化,该值通常不会改变。

空闲时使用的堆栈(SpStartOS) Stack used when idle (SpStartOS)

操作系统处于空闲状态(通常在Os_Cbk_Idle()内)时使用的最大堆栈量。这只是调用StartOS()时使用的堆栈与没有任务或中断运行时使用的堆栈之间的差异。如果没有使用Os_Cbk_Idle(),这个值可以为零。它必须包括在空闲状态下调用的任何函数所使用的堆栈。

如果操作系统配置发生变化,该值通常不会改变。

ISR激活的堆栈开销(SpIDisp) Stack overheads for ISR activation (SpIDisp)

与从task内激活相比,从ISR内激活任务所需的额外堆栈量。如果在Category 2 ISR中激活了一个任务,并且该任务的优先级高于当前正在运行的任何任务,那么对于某些目标,操作系统可能需要使用比激活优先级较低的任务更多的堆栈。这个值说明了这一点。在大多数目标上,这个值为零。

此值用于最坏情况下的堆栈大小计算。

当操作系统配置发生重大变化时,该值可能会发生变化。

用于ECC任务的堆栈开销 (SpECC) Stack overheads for ECC tasks (SpECC)

启动ECC任务所需的额外堆栈量。与BCC任务相比,ECC任务在启动时需要在堆栈上保存更多的状态。该值包含差异。

该值可以通过测量堆栈值得到:

  1. 在(向上)激活基本任务之前,并立即在激活任务的输入函数中。
  2. 在(向上)激活扩展任务之前,并立即在激活任务的输入函数中。

然后用第一个值减去第二个值。

当操作系统配置发生重大变化时,该值可能会发生变化。

还要注意,如果您使用堆栈重定位(对齐不受信任的代码堆栈以适应MPU),那么您将需要减少调整量的值。

中断的堆栈开销 Stack overheads for ISR (SpPreemption)

用于服务二类ISR的堆栈数量。当一个二类ISR中断一个任务时,它通常把一些数据放在堆栈上。如果ISR测量堆栈以确定被抢占的任务是否超出了它的堆栈预算,那么它将高估堆栈使用量,除非从测量的大小中减去这个值。

该值也用于计算系统的最坏情况堆栈使用情况。

这个值可以通过在中断发生之前测量堆栈值来获得,并且在测试期间立即在类别2 ISR的入口函数中测量堆栈值。

要注意准确地设置这个值。

如果它的值太高,那么当减法发生时,可能会发生32位的下溢,并导致操作系统认为已经检测到预算超支。

当操作系统配置发生重大变化时,该值可能会发生变化。

集成指南4.6: 在检查期间,除了ISR (SpPreemption)的堆栈开销外,所有强制性堆栈值都添加到堆栈值中。这意味着指定比实际值更大的值是安全的。然而,ISR的堆栈开销
(SpPreemption)从进入ISR时的堆栈指针的当前值中减去,以检查被抢占的任务或ISR是否超过了其堆栈使用率。
因此,在这里指定一个较大的值可能会导致在没有发生错误的地方报告错误(即RTA-OS堆栈管理将报告“误报”)。

图4.11显示了基本堆栈值的配置。

4.6.3 Specifying Task Stack Allocation 指定任务堆栈分配

在只包含基本任务的系统中,没有必要告诉RTA-OS任何堆栈分配,除非您正在进行堆栈监视(参见第14.1节)。您只需要在链接器/定位器中为您的应用程序分配一个足够大的堆栈部分。这是单堆栈架构的好处之一。

对于使用扩展任务的应用程序,您可以像以前一样分配链接器部分,但是您还必须告诉RTA-OS配置中优先级低于最高优先级扩展任务的每个任务的堆栈分配,即使它们是基本任务。
RTA-OS使用堆栈分配信息来计算每个离线扩展任务的最坏情况抢占点。

您指定的堆栈分配是用于任务的整个堆栈,包括:

  • 操作系统上下文
  • 任务主体中的本地变量空间
  • 从任务主体调用的任何函数所需的空间(以及它们的局部变量)

您可以使用RTA-OS的堆栈测量特性来获得堆栈分配的准确值。更多细节请参见第14.1节。

集成指南4.7: RTA-OS仅使用您提供的堆栈信息来计算最坏情况的抢占点。RTA-OS不保留任何堆栈空间。
您仍然必须按照与普通应用程序相同的方式指定堆栈应用程序堆栈空间。

图4.12展示了堆栈分配的配置方式。

虽然RTA-OS使用单堆栈模型,但在某些端口上,这并不一定意味着只使用一个物理堆栈。可能是编译器或硬件自动将数据强制放到不同的堆栈上。例如,TriCore设备使用CSA内存来快速保存调用上下文。RTA-OS将其等同于堆栈。

即使有多个物理堆栈,RTA-OS仍然提供了单堆栈架构的好处——当任务和/或isr共享一个优先级时,每个物理堆栈上所需的堆栈空间可以被覆盖。但是,要使堆栈分配正常工作,您需要指定每个堆栈上所需的空间。如果您配置的目标需要此信息,RTA-OS将要求您提供多个堆栈值。
图4.13显示了这样一个配置的对话框,其中有两个堆栈:
“Supervisor”和“Context”。

4.6.4 Optimizing the Extended Task context save 优化扩展任务上下文保存

回顾4.6.1节,每次扩展任务进入等待状态时,
RTA-OS保存任务的“等待事件堆栈”上下文,并在任务重新进入运行状态时恢复上下文。

RTA-OS将“等待事件堆栈”上下文保存在内部缓冲区中。缺省情况下,RTA-
操作系统分配的缓冲区等于您为任务指定的最坏情况堆栈分配。
假设您的堆栈分配是正确的,当您调用WaitEvent()时,这应该总是足以容纳最坏情况下的堆栈使用情况。

通常情况下,大多数使用扩展任务的应用程序只从任务的入口函数调用WaitEvent()。此时,堆栈上只有少量的本地数据(可能为零)。您可以通过仅分配足够的缓冲空间来保存最坏情况下的“等待事件堆栈”上下文,而不是任务所需的绝对最坏情况空间来减小RTA-OS保留的RAM大小。

您可以通过在WaitEvent()执行时给RTA-OS提供最坏情况下的堆栈深度来指定为这个缓冲区保留多少字节(图4.14)。请记住,最坏情况的值可能包括WaitEvent()调用本身使用的一些堆栈。获得正确值的一种方法是从一个小值开始,并不断增加它,直到你不再看到Os_Cbk_StackOverrunHook()回调中的OS_ECC_WAIT错误。如果不能保存堆栈上下文,RTA-OS将不会将ECC TASK置于等待状态

集成指南4.8: 如果您将WaitEvent()的堆栈分配设置为“未定义”,那么RTA-OS将默认使用您指定的字节数进行堆栈分配。

Using Default Values

为了提高内存效率,您应该为每个任务设置堆栈值,而RTA-OS允许您设置所有任务使用的全局默认值。这可以在General中找到默认堆栈值。

如果没有为任务配置堆栈分配,则RTA-OS将使用默认值:

  • 计算最坏情况下的堆栈偏移量

  • 配置WaitEvent()保存/恢复区域

  • 堆栈监控(配置时)

特定于任务/ isr的堆栈分配规范将覆盖默认值。

4.6.5 处理堆栈溢出 Handling Stack Overrun

如果您提供给RTA-OS的堆栈分配数据是错误的(例如,它们太小),那么这是运行时错误的潜在来源。有三件事可能出错:

  1. 当RTA-OS尝试调度一个扩展任务时,由于当前栈指针的值高于计算出的最坏情况分派点,因此无法启动扩展任务。这意味着其中一个(或多个)较低优先级的任务在栈上消耗了过多空间。可以使用第14.1节中描述的堆栈监控来确定是哪个任务有问题。
  2. 由于堆栈指针比应该更高,所以无法从等待状态恢复扩展任务。当对扩展任务正在等待的事件调用SetEvent()并且该扩展任务现在是系统中最高优先级的任务时,可能会发生这种情况。
  3. 由于当前使用的堆栈量大于配置为“WaitEvent() stack”大小的大小,因此无法使扩展任务进入等待状态。

当RTA-OS检测到扩展任务堆栈管理的问题时,它将调用ShutdownOS()函数,并传递错误代码E_OS_STACKFAULT。

如果你想调试问题,可以像图4.15所示启用堆栈故障钩子:

当配置完成后,RTA-OS在发生堆栈故障时将调用用户提供的回调函数’Os_Cbk_StackOverrunHook()‘,而不是调用’ShutdownOS()’。该回调函数会传递两个参数。

#ifdef OS_STACKOVERRUNHOOK
FUNC(void, OS_CALLOUT_CODE) Os_Cbk_StackOverrunHook(Os_StackSizeType
Overrun, Os_StackOverrunType Reason) {
    /* Identify problem */
    for(;;) {
        /* Do not return! */
    }
}
#endif /* OS_STACKOVERRUNHOOK */
Example 4.1: Minimum recommended Os_Cbk_StackOverrunHook()
  1. Overrun告诉你溢出的字节数。
  2. Reason告诉你是什么导致了溢出。

对于没有启用堆栈监视的扩展任务系统,溢出可能是以下情况之一:

  • OS_ECC_START - 扩展任务无法启动,因为当前堆栈指针超过了在构建时计算的最坏情况调度点。此故障的原因是一个或多个较低优先级任务中有一个已超出配置的堆栈分配。要解决这个问题,您需要确定哪个任务存在错误。第14章介绍了如何使用RTA-OS的堆栈监控功能来完成这一点。
  • OS_ECC_RESUME - 扩展任务无法从等待状态恢复,因为当前堆栈指针超过了在构建时计算的最坏情况调度点。此故障的原因是一个或多个较低优先级任务中有一个已超出配置的堆栈分配。要解决这个问题,您需要确定哪个任务存在错误。第14章介绍了如何使用RTA-OS的堆栈监控功能来完成这一点。
  • OS_ECC_WAIT - 扩展任务无法进入等待状态,因为其消耗的堆栈空间超过了配置的WaitEvent()堆栈大小。要解决这个问题,您应该至少增加WaitEvent()堆栈大小以满足Overrun参数所示字节数量以上需求。

例4.1展示了一个简单的示例。

4.7 Implementing Tasks 任务执行

任务类似于C函数,在RTA-OS调用时实现某种形式的系统功能。

**集成指南4.9:**您不需要为任务入口函数提供任何C函数原型。这些通过RTA-OS生成的Os.h头文件提供。

TASK(task_identifier)
{
    /* Your code */
}
Example 4.2: A Task Entry Function

当任务开始运行时,从任务输入函数开始执行。任务输入函数是使用例4.2中的C语法编写的。

记住,基本任务是一次性完成的。这意味着它们从固定的任务入口点执行,并在完成时终止。

例4.3展示了一个名为BCC_Task的基本任务的代码:

#include <Os.h>
TASK(BCC_Task) {
    do_something();
    /* Task must finish with TerminateTask() or equivalent. */
    TerminateTask();
}

Example 4.3: A Basic Task

现在,比较例4.3和例4.4中的示例。例4.4展示了扩展任务不一定要终止,可以保持在循环中等待事件。

#include <Os.h>
TASK(ECC_Task) {
    InitializeTheTask();
    while (WaitEvent(SomeEvent)==E_OK) {
        do_something();
        ClearEvent(SomeEvent);
    }
    /* Task never terminates. */
}

Example 4.4: Extended Task Waiting for Events

4.8 Activating Tasks 激活任务

一个任务只有在被激活后才能运行。激活将任务从挂起状态转移到就绪状态,或者向就绪任务队列中添加另一个条目(如果该任务支持多次激活)。每个激活都会使任务运行一次。当超过激活计数时,这将导致应用程序生成E_OS_LIMIT错误(即使是在标准构建状态下也是如此)。

任务可以从任务和(第二类)中断服务程序(ISR)中激活。

激活一个任务并不会立即开始执行该任务,它只是使其准备好运行。然而,RTA-OS需要检查被激活的任务是否具有比当前正在运行的任务更高的优先级,并且是这样,就会引发上下文切换以便新任务可以抢占当前正在运行的任务。

当你从另一个任务中激活一个RTA-OS任务时,其具体行为于相对任务的优先级。如果被激活的任务比当前运行的任务优先级更高,则新激活的任务将抢占当前任务。否则,该任务将保持在就绪队列中,直到它成为最高优先级的就绪任务。

在一个设计良好的实时系统中,一个任务激活一个更高优先级的任务是不常见的。通常情况下,中断服务程序(ISRs)会捕获系统触发器,然后激活相应的任务来进行任何相关处理。反过来,这些任务可能会激活较低优先级的任务来实现具有较长截止日期的触发响应。

观察到这个事实会导致RTA-OS中的一个主要优化。如果您指定您的任务永远不会激活更高优先级的任务,RTA-OS可以消除每次激活后是否需要进行上下文切换测试的内部代码。通过选择“禁止向上激活”优化来配置此功能。

这类似于从中断服务程序(ISR)激活任务时的行为。所有的ISR都具有比最高任务优先级更高的优先级。当从ISR激活一个任务时,它永远不会立即进入运行状态,因此无需检查是否需要进行上下文切换。只有在离开ISR时才需要进行这样的检查。

4.8.1 Direct Activation 直接激活

任务可以通过多种方式激活。基本的任务激活机制是使用ActivateTask() API调用,该调用直接激活一个任务。ActivateTask(TaskID) 调用将指定的任务置于就绪状态。ChainTask(TaskID) 调用会终止当前任务(参见第4.11节),并将指定的任务置于就绪状态。

4.8.2 Indirect Activation 间接激活

除了直接激活任务外,还可以使用其他AUTOSAR OS机制间接激活任务。这些方法将在本用户指南后面的章节中更详细地描述。

  • Activation by an Alarm. 通过警报激活。 对于系统中的每个警报,您可以指定一个任务,在每次警报过期时激活该任务。
  • Activation by a Schedule Table. 通过时间表激活。对于系统中的每个时间表,您可以指定一个任务,在表上一个或多个到期点上激活该任务。

4.9 Controlling Task Execution Ordering 控制任务执行顺序

在许多情况下,您需要约束特定任务的执行顺序。在基于数据流的设计中尤其如此,其中一个任务需要在另一个任务使用计算值之前执行一些计算。如果执行顺序不受约束,则可能出现竞争条件,并且应用程序的行为将不可预测。任务执行顺序可以通过以下方式进行控制:

  • 直接激活链(见第4.9.1节)。
  • 优先级水平(见第4.9.2节)。
  • 不可抢占任务(见第2节)。
4.9.1 Direct Activation Chains 直接激活链

当您使用直接激活链来控制执行顺序时,任务会在调用makeActivateTask()的任务之后调用需要执行的任务。

考虑以下情况:有三个任务Task1、Task2和Task3,它们必须按照Task1、然后是Task2、最后是Task3的顺序执行。

示例4.5展示了示例任务体。图4.16展示了这些任务如何执行,假设Task1具有最高优先级,而Task3具有最低优先级。

#include <Os.h>
TASK(Task1) {
    /* Task1 functionality. */
    ActivateTask(Task2);
    TerminateTask();
}
TASK(Task2) {
    /* Task2 functionality. */
    ActivateTask(Task3);
    TerminateTask();
}
TASK(Task3) {
    /* Task3 functionality. */
    TerminateTask();
}
Example 4.5: Using Direct Activation Chains

4.9.2 Using Priority Levels使用优先级

优先级级别方法用于限制任务执行顺序,可以利用抢占式调度策略的特性来控制激活顺序。回想一下,在固定优先级抢占式调度下,调度器总是运行最高优先级的任务。

如果一些任务被释放到就绪队列中,它们将按照优先级顺序执行。这意味着您可以使用任务的优先级来控制执行顺序。

接着我们之前的例子,在示例4.5中假设Task1具有最高优先级,而Task3具有最低优先级。这意味着任务体可以重新编写以利用控制激活。在示例4.6中展示了这种情况。图4.17显示了这些任务如何执行

#include <Os.h>
TASK(Task1) {
    /* Task1 functionality. */
    ActivateTask(Task2);
    /* Runs when Task1 terminates. */
    /* More Task1 functionality. */
    ActivateTask(Task3);
    /* Runs when Task2 terminates. */
    TerminateTask();
}
TASK(Task2) {
    /* Task2 functionality. */
    TerminateTask();
}
TASK(Task3) {
    /* Task3 functionality. */
    TerminateTask();
}
Example 4.6: Using Priority Level Controlled Activation

4.10 Co-operative Scheduling in RTA-OS RTA-OS中的协同调度

当一个任务正在非抢占式地运行时,它会阻止任何任务(包括优先级更高的任务)执行。然而,有时候非抢占式任务提供明确的重新调度点是很有用的。这比简单地式运行更有效率,因为优先级更高的任务可以对系统刺激作出更短的响应时间。一个同时具备非抢占式运行和提供重新调度点功能的系统被称为合作调度.

Schedule() API调用可以用来暂时消除由非抢占任务和使用内部资源的任务施加的抢占约束。

当Schedule()被调用时,任何具有比调用任务更高优先级的就绪任务都被允许运行。Schedule()在所有高优先级任务终止之前不会返回。

例4.7展示了一个非抢占式任务Cooperative,它包含一系列函数调用。一旦启动,每个函数运行到完成,没有抢占,但任务本身可以在每个函数调用之间被抢占。

#include <Os.h>
TASK(Cooperative){
    Function1();
    Schedule();/* Allow preemption */
    Function2();
    Schedule();/* Allow preemption */
    Function3();
    Schedule();/* Allow preemption */
    Function4();
    TerminateTask();
}

Example 4.7: Making a task run co-operatively

图4.18显示了Task1和Task2这两个相互合作的任务是如何交互的。白色部分表示不可抢占的代码部分。

4.10.1 Optimizing out the Schedule() API 优化Schedule() API

"在完全抢占式系统中,Schedule() 没有任何用处。如果您不打算使用它,在 rtaoscfg 中可以通过“Optimizations, RTA-OS,
Disallow Schedule()”来禁止调用 Schedule(). 如果您不允许调用Schedule(),那么您将看到系统的最坏情况堆栈需求减少了。

4.11 Terminating Tasks 终止任务

在AUTOSAR操作系统中终止的任务必须调用API来告诉操作系统正在发生这种情况。AUTOSAR OS标准为任务终止定义了两个API调用。必须使用其中一个来终止任何任务。这些API调用是:

  • TerminateTask()
  • ChainTask(TaskID)

当任务完成时,它必须调用这些API中的一个。这确保了RTA-OS可以正确地调度准备运行的下一个任务。

TerminateTask()强制调用任务进入挂起状态。RTA-OS将在就绪状态下运行下一个最高优先级的任务。

ChainTask(TaskID)终止调用任务并激活任务TaskID。
因此,该API类似于执行TerminateTask(),然后立即执行
ActivateTask (TaskID)。链接任务将指定的任务置于就绪状态。

4.11.1 Optimizing Termination in RTA-OS RTA-OS中的终端优化

AUTOSAR OS标准允许任务在任何时候调用任务终止API调用,包括在一个深度嵌套的函数调用集合中。这是糟糕的编程实践——相当于使用goto。在运行时,RTA-OS必须存储允许它在任务结束时清除堆栈的信息,而不是进入函数。这通常使用setjmp/longjmp对完成。

例4.8展示了一个嵌套调用其他函数的任务。当Task1运行时,它调用Function1()。
然后Function1()调用Function2()。
Function2()包含可以终止调用任务的代码(在本例中,这是Task1)。

#include <Os.h>
void Function1(void) {
    ...
    Function2();
    ...
}
void Function2(void) {
    if (SomeCondition) {
        TerminateTask();
    }
}
TASK(Task1) {
    /* Make a nested function call. */
    Function1();
    /* Terminate the task in the entry function*/
    TerminateTask();
}

Example 4.8: Terminating a Task

然而,单栈架构的一个关键好处是,在其入口函数中终止的任务可以简单地返回——TerminateTask()不需要做任何事情。如果所有的任务都没有终止,或者只是在它们的入口函数中终止,那么RTA-OS为允许从任何地方返回而保存的上下文就不需要存储。

RTA-OS允许您利用良好的应用程序设计,使用快速终止优化(Optimizations -> Fast Terminate)。当所有执行TerminateTask()或ChainTask() API的任务仅在其入口函数中执行时,您可以启用此优化。该优化告诉RTA-OS不生成保存不必要上下文的代码,并因此节省堆栈空间。

4.12 Delayed Tasks 延迟任务

操作系统选项“支持延迟任务执行”可用于添加对APIsOs_SetDelayedTasks()、Os_AddDelayedTasks()和Os_RemoveDelayedTasks()的支持。这些API允许您告诉RTA-OS延迟执行一组任务。延迟任务可以被激活,但直到它们从集合中移除后才会实际运行。

使用Os_SetDelayedTasks()来指定哪些任务应该被延迟。如果在调用之前某个任务正在被延迟,但它不在新的延迟任务集合中,则如果该任务的优先级高于调用者,它将在此调用返回之前执行。您只能设置在调用核心上运行的任务。

Os_AddDelayedTasks()用于向现有的延迟任务集添加任务。
可以多次添加任务,但没有效果。您必须只添加在调用核心上运行的任务。

Os_RemoveDelayedTasks()用于从延迟任务集中删除任务。如果被删除的任务具有比调用者更高的优先级,则它们将在此调用返回之前执行。

请注意,如果任务在特定核心上共享优先级,则必须指定共享优先级的所有任务,或者不指定

4.13 The Idle Mechanism 空闲机制

任何预防性操作系统在没有任务或中断服务程序运行时必须有一些事情要做。在AUTOSAR OS中,这是通过空闲机制实现的。在RTA-OS中,当没有任务或ISR可运行时,操作系统将坐在忙等待循环中什么也不做。

但是,您可以通过声明一个名为Os_Cbk_Idle的回调来提供自己的空闲机制实现以覆盖默认行为。

Os_Cbk_Idle的行为方式与任务相同,除了:

  • 它无法被激活
  • 它无法被终止
  • 它无法等待事件
  • 它无法链接在一起
  • 它不能使用内部资源

Os_Cbk_Idle的优先级低于系统中的任何任务,因此它只在没有准备运行的任务(或ISR)时才运行。因此,空闲机制为您提供了一个几乎完全不受系统开销影响的“额外任务”。

示例4.9展示了一个用于控制RTA-TRACE(见第17章)的Os_Cbk_Idle实现。

#include <Os.h>
FUNC(boolean, OS_CALLOUT_CODE) Os_Cbk_Idle(void) {
    #ifdef OS_TRACE
    CheckTraceOutput();
    UploadTraceData();
    #endif /* OS_TRACE */
    return TRUE;
}
OS_MAIN() {
    /* System hardware initialization. */
    StartOS(OSDEFAULTAPPMODE);
    /* The call never returns */
}
Example 4.9: An Idle Mechanism

Os_Cbk_Idle在退出时返回一个布尔值,告诉RTA-OS是否调用Os_Cbk_Idle。当返回TRUE时,RTA-OS立即再次调用Os_Cbk_Idle。当返回FALSE时,RTA-OS停止调用Os_Cbk_Idle并进入默认行为,在忙等待循环中等待。

4.14 Pre and Post Task Hooks 前置和后任务钩子

假设您需要在每个任务开始之前和/或结束之后执行一些代码,例如对执行进行分析。您可以使用AUTOSAR OS提供的PreTask和PostTask钩子来实现这一点。

每当任务进入运行状态时,RTA-OS都会调用PreTask Hook。这意味着在抢占后恢复任务时也会调用PreTask Hook。

每当一个任务从运行状态移出时,RTA-OS都会调用PostTask Hook。当任务终止或被抢占时,将会调用PostTask Hook。

图4.19显示了相对于任务抢占调用PreTask和PostTask钩子的位置。

这两个钩子只有在配置时才会被调用。图4.20展示了如何启用这些钩子。

例4.10展示了钩子应该如何在代码中出现。

FUNC(void, OS_CALLOUT_CODE) PreTaskHook(void) {
    /* PreTask hook code. */
}
FUNC(void, OS_CALLOUT_CODE) PostTaskHook(void) {
    /* PostTask hook code. */
}
Example 4.10: The PreTaskHook and PostTaskHook

PreTask和PostTask Hooks在任务进入和退出以及每次抢占/恢复时被调用。这意味着可以使用这些钩子来记录应用程序的执行跟踪。由于所有任务都必须使用相同的PreTask和PostTask Hooks,因此需要使用GetTaskID() API调用来确定在输入钩子例程时正在运行或将要运行的任务

RTA-OS定义了一组宏,只有在相应的钩子被启用时才会定义这些宏。这些宏被称为:

  • OS_PRETASKHOOK
  • OS_POSTTASKHOOK

这样可以编写代码,其中的钩子可以根据示例4.11中所示进行有条件地编译。

#ifdef OS_PRETASKHOOK
FUNC(void, OS_CALLOUT_CODE) PreTaskHook (void)
{
    /* Your code */
}
#endif /* OS_PRETASKHOOK */
Example 4.11: Conditional Compilation of PreTaskHook

4.15 Saving Hardware Registers across Preemption 跨抢占保存硬件寄存器

RTA-OS在上下文切换时只保存必要的上下文,即仅保存操作系统正确运行所需的上下文。然而,在运行发现需要保存和恢复额外的应用上下文。例如,您可能有一些使用浮点寄存器的任务,因此需要跨上下文切换保存微控制器的浮点上下文。

你可以选择使用PreTask和PostTask钩子以及应用程序管理的堆栈来手动实现这一点。然而,如果不对这种类型的实现进行优化,就很难使其在操作系统配置发生变化时保持稳定。你可以选择以下两种方式:

  • 在每次切换到一个任务时,始终保存上下文,并在每次切出时恢复。这种模式意味着你可能会进行不必要的保存和恢复(例如,在切换到一个不使用它的任务时保存寄存器集);或者

  • 离线计算所需的保存并编写一对更复杂的钩子函数,使用GetTaskID()/GetISRID()来确定是否需要保存/恢复。这种模式是脆弱的,因为配置发生变化,例如添加新任务/中断服务程序或修改优先级,将需要重新工作。

为了避免这些问题,RTA-OS提供了一个简单的通用机制,可以将用户特定的上下文与操作系统上下文一起保存。RTA-OS能够利用其对优先级空间的了解,在运行时精确计算出需要保存寄存器集的任务,从而优化掉不必要的保存操作,节省了切换上下文所需的时间和堆栈空间。例如:

  • 如果只有一个任务或使用给定寄存器集的Category 2 ISR,则不需要nosave或restore。
  • 如果多个任务使用相同的寄存器集,但不能同时执行(因为它们是非可抢占的、共享内部资源或共享优先级),那么RTA-OS不需要保存寄存器集。
  • 当切换到使用寄存器集的最低优先级任务时,不需要进行保存操作,因为可以保证没有其他存器集(因为如果有更高优先级的任务在使用该寄存器集,则最低优先级任务将法运行)。
  • 同样地,从使用寄存器集的最高优先级任务进行上下文切换时也不需要进行保存操作,因为没有更高寄存器集,因此不能破坏上下文。

图4.21显示了任务1、3和5共享的寄存器集。可以看到,当切换到不使用寄存器集的任务时,不需要进行上下文保存。

每个需要保存的寄存器集都需要在配置时间声明给RTA-OS。rtaosgen使用这个声明来定义两个回调函数,你必须提供这两个函数来保存和恢复寄存器集。图4.22显示了三个寄存器集的定义。

每个使用寄存器集的任务都需要在运行时声明,以便rtaosgen可以计算需要保存的最大集合数。图4.23展示了如何为一个任务完成这项工作。

RTA-OS不知道如何保存和恢复您声明的寄存器集,也不知道在哪里保存和恢复它们 - 它只知道需要多少次保存以及何时保存和恢复。对于每个您定义的寄存器集,RTA-OS会生成一个名为OS_REGSET__SIZE的宏,该宏定义了所需的最坏情况下寄存器集保存所需的数量。您应该在应用程序代码中使用这个宏来定义一个大小为OS_REGSET__SIZE的数组,其中数组的每个元素都保存了已经被保存过的寄存器集。

你还需要为保存和恢复操作提供回调函数:

  • 每当RTA-OS需要保存寄存器集时,将调用Os_Cbk_RegSetSave_(Os_RegSetDepthType Depth)函数。
  • “Os_Cbk_RegSetRestore_(Os_RegSetDepthType Depth)在需要恢复寄存器集时由RTA-OS调用。”

两个回调函数都会传递一个深度值,该值指示要保存或恢复的寄存器集。示例4.12展示了这些回调函数在您的代码中可能应该出现的方式。

typedef volatile uint32 RegType;
#define VOLATILEREGISTER
(*(RegType*)(0xDEAFBEEF))
uint32 VolatileRegisterSaveArea[OS_REGSET_VolatileRegister_SIZE];
FUNC(void, OS_CALLOUT_CODE)
Os_Cbk_RegSetSave_VolatileRegister(Os_RegSetDepthType Depth) {
    VolatileRegisterSaveArea[Depth] = VOLATILEREGISTER;
}
FUNC(void, OS_CALLOUT_CODE)
Os_Cbk_RegSetRestore_VolatileRegister(Os_RegSetDepthType Depth) {
    VOLATILEREGISTER = VolatileRegisterSaveArea[Depth];
}
Example 4.12: Register Set Save And Restore

4.16 总结

  • 任务是并发活动。
  • 有两类任务:基本任务和扩展任务。
  • 任务可以共享优先级,但建议不要这样做。
  • 任务按照优先级进行调度。当一个更高优先级的任务准备就绪时,它将抢占低优先级的任务,但不会抢占任何被配置为非抢占式的任务。
  • 任务存在于准备就绪、运行、挂起或等待状态(然而,只有扩展任务能进入等待状态)。
  • 如果一个任务终止,必须调用TerminateTask()或ChainTask(TaskID)来完成此操作。
  • 所有在其入口函数中终止的所有任务都可以使用“快速终止”优化来最小化堆栈使用和上下文切换时间。
  • 除非指定多个激活方式,否则只能在挂起状态下激活任务。
  • PreTask和PostTask Hooks允许您在您的任务开始之前和结束之后执行代码。这可用于在运行时对应用程序进行分析。
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾格北峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值