【OS基础】符合AUTOSAR标准的RTA-OS-Task详解

目录

2.任务Task

2.1 调度Scheduling

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

2.2.1 任务状态Task State

2.2.2 任务优先级Task Priorities

2.2.3 任务队列激活Queued Task Activation

2.2.4 异步任务激活Asynchronous Task Activation

2.3 一致性类Conformance Classes

2.4 最大化性能和最小化内存Maximizing Performance and Minimizing Memory

2.5 任务配置Task Confifiguration

2.5.1 调度策略Scheduling Policy

2.5.2 排队激活Queued Activation

2.5.3 自动启动任务Auto-starting Tasks

2.6 栈管理Stack Management

2.6.1 使用扩展任务Working with Extended Tasks

2.6.2 强制性堆栈信息Mandatory Stack Information

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

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

2.6.5 处理堆栈溢出Handling Stack Overrun

2.7 实现任务Implementing Tasks

2.8 激活任务Activating Tasks

2.8.1  直接激活Direct Activation

2.8.2  间接激活Indirect Activation

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

2.9.1 直接激活链Direct Activation Chains

2.9.2 使用优先级Using Priority Levels

2.10  RTA-OS中的合作调度Co-operative Scheduling in RTA-OS

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

2.11 终止任务Terminating Tasks

2.11.1 优化RTA-OS中的任务终止Optimizing Termination in RTA-OS

2.12 延迟任务Delayed Tasks

2.13 空闲机制The Idle Mechanism

2.14 任务开始和结束的构子函数Pre and Post Task Hooks

2.15 通过抢占保存硬件寄存器Saving Hardware Registers across Preemption

2.16 小结


2.任务Task

必须同时执行许多不同活动的系统称为并发。这些活动可能包含一些软件部分,因此提供这些活动的程序必须同时执行。这些程序必须在任何必要的时候进行合作,例如,当它们需要共享数据时。

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

实现并发行为的一个更经济有效的方法是在单个处理器上一次运行一个任务。然后可以在任务之间切换,以便它们看起来同时执行

2.1 调度Scheduling

RTA-OS提供了一个调度器,它根据在配置时分配的固定优先级在任务之间切换。优先级只是反映了任务的相对紧迫性。可以使用许多方案来分配任务的优先级,常见方案有:

Deadline Monotonic Assignment: 更高的优先级被分配给最后期限较短的任务。

Rate Monotonic Assignment: 更高的优先级被分配给需要更频繁地运行的任务。

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

AUTOSAR操作系统支持两种调度策略:

1. 抢占式调度Preemptive Scheduling.

固定优先级抢占式调度算法很简单:运行准备运行的最高优先级任务。如果任务正在运行,而优先级较高的任务准备运行,则优先级较高的任务优先于正在运行的任务。这叫做任务切换。当高优先级任务完成后,抢占任务恢复。

对于所有任务都需要在运行时满足其最后期限的系统,抢占式的调度是最有效的调度策略,并将保证任务被激活(准备运行)和终止之间的最短时间。这个时间被称为对该任务的响应时间。抢占式调度的系统需要考虑抢占对共享数据的影响,并且可能需要引入并发控制机制。

图2.1 任务的抢占式调度

2. 非抢占式调度 Non-Preemptive scheduling.

操作系统运行优先级已准备运行的最高任务,和抢占式调度一样。然而,抢占式调度不同的是,如果一个更高优先级的任务准备好了,那么它就会一直准备好运行,直到正在运行的任务终止——它不会抢占正在允许的低优先级的任务。这意味着,开始运行的非抢占式任务将始终运行到完成,然后终止。

非抢占式调度导致系统比抢占式调度的响应性更低(即任务通常有更长的响应时间),但系统不需要担心访问共享数据时出现的并发问题,因为调度模型不允许并发访问共享数据。

实际上,AUTOSAR OS提供了第三种类型的调度支持,因为它允许非抢占任务告诉操作系统什么时候可以被抢占。我们说AUTOSAR操作系统支持2个策略的原因是只有两个配置——第三个配置,必须自己构建。

图2.2 任务的非抢占式调度

3. 协同调度Cooperative scheduling.

操作系统将运行优先级最高的准备运行的任务。如果一个更高优先级的任务准备好了,那么它会一直准备运行直到:正在运行的任务终止(就像非抢占式调度);或者正在运行的任务调用Schedule() API来告诉操作系统它可以被抢占。当Schedule()调用时,高优先级任务抢占正在运行的任务,任务切换就发生了(就像抢占调度一样)。当高优先级任务完成后,被抢占的任务恢复。

通过仔细的设计,协同模式提供的系统虽然不像完全抢占式系统那样响应迅速,但不会像非抢占式调度那样缺乏响应能力。

图2.3 任务的协同调度

对于所有这些类型的调度,重要的是要意识到任何任务,无论是否抢占人,都可以被中断服务例程中断(抢占)。

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

RTAOS支持两种类型的Task:

1. 基本任务Basic Task

基本任务的开始、执行和结束(通常称为单次任务模式)。一个基本任务只有在它被终止或被一个更高优先级的任务抢占时才释放处理器。这种行为使它们非常适合嵌入式控制功能。基本任务是快速高效的。

2. 扩展任务Extended Task

扩展任务启动、执行、等待事件和(可选)终止。扩展任务在执行期间自动挂起自身的能力为任务提供了一种具有同步点的方法。这个特性使得扩展任务比基本任务更适合需要执行中间同步的功能(例如,等待用户交互)。

2.2.1 任务状态Task State

基本任务在三状态模型上运行。基本任务有以下几种状态:

  1. 挂起Suspended
  2. 准备Ready
  3. 允许Running

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

  1. 等待Waiting

图2.4展示了任务3或者4状态模型。

所有任务的默认状态是挂起。任务通过激活过程进入就绪状态。重要的是要理解激活并不会导致任务运行——它只是让任务准备好运行。激活可以通过多种方式发生,例如在代码中调用ActivateTask() API,或者作为某些触发器的结果,例如警报(Alarm)到期或调度表到期点( schedule table expiry point)

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

图2.4 任务状态模型

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

任务终止后返回挂起状态。任务可以在以后再次准备好,整个过程可以重复。

基本任务和扩展任务在准备、运行和挂起状态方面表现相同。但是,扩展任务也可以进入等待状态。当扩展任务通过等待事件自动挂起自己时,它将从运行状态转移到等待状态。

事件只是一个OS对象,用于为系统事件提供指示器。事件的例子包括数据准备使用或传感器值正在读取。

当扩展任务进入等待状态时,操作系统将分派准备运行的最高优先级任务。当设置事件时,任务将从等待状态移动到准备状态。注意,扩展任务返回到就绪状态,而不是运行状态。这是因为,在扩展任务处于等待状态期间,其他一些更高优先级的任务可能已经被激活,然后被分派。

2.2.2 任务优先级Task Priorities

AUTOSAR OS允许任务共享优先级。当任务具有相同优先级时,具有共享优先级的每个任务将相互排斥运行。这意味着如果一个任务正在运行,那么它的执行将与所有其他具有相同优先级的任务序列化。

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

Note:当共享优先级和排队任务激活一起使用时,RTA-OS在优先级级别上维护一个内部队列。如果你想要一个快速高效的操作系统,你应该避免这种类型的配置。

如果您需要序列化一组任务的执行,那么最好使用惟一的优先级和AUTOSAR OS的内部资源来实现,而不是共享任务优先级。使用内部资源保证了序列化,就像共享优先级一样,任务优先级的唯一性意味着当多个任务同时就绪时,操作系统有一个静态定义的分派顺序。

Note:在任务之间共享优先级是一种糟糕的实时编程实践,因为它会阻止您在系统上执行可调度性分析。这是因为,在一般情况下,共享优先级使得任务的释放点(即测量响应时间的点)在计算上无法计算。如果不可能计算出何时发布,那么就不可能决定任务是否会在截止日期前完成!

2.2.3 任务队列激活Queued Task Activation

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

然而,在某些情况下,我们可能需要实现这样一个系统:相同的任务必须多次激活,但连续激活之间的最短时间可能小于运行任务所需的时间。例如,您可能正在一个任务中解包CAN总线帧,并且需要处理网络上帧的瞬时爆发( transient bursting)。

这意味着我们需要在运行时排队等待任务激活。AUTOSAR OS操作系统允许排队激活基本任务,以帮助构建这类应用程序。与自动共享操作系统中的其他东西一样,任务队列的大小也是静态配置的。我们必须指定该任务可以挂起的最大激活数。

如果在尝试激活任务时队列已满,则这将作为错误处理,激活将被忽略。

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

2.2.4 异步任务激活Asynchronous Task Activation

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

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

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

Note: 当选择异步TASK激活时,E_OS_LIMIT指示会在拥有该任务的核上发出,而不是在激活它的核上

2.3 一致性类Conformance Classes

我们现在知道,任务可以:

  1. 基本的或扩展的
  2. 可以共享优先级
  3. 可以排队激活

然而,AUTOSAR操作系统对一起使用的特性进行了一些限制。这些特性被称为一致性类,用于对任务特征进行分组,以便于理解,支持标准的部分实现,并为不同类别的应用程序提供了可伸缩性。

AUTOSAR操作系统有四个一致性类:

BCC1 -基本任务,唯一的优先级和没有排队的激活( Basic tasks, unique priority and no queued activation).

BCC2 - 基本任务、共享优先级和/或排队激活(Basic tasks, shared priorities and/or queued activation).

ECC1 - 扩展的任务,唯一的优先级和没有排队的激活。ECC1任务类似于BCC1任务,但它可以等待事件(Extended tasks, unique priority and no queued activation. An ECC1 task is like

a BCC1 task, but it can wait on events.)

ECC2 - 扩展的任务,共享的优先级和没有排队的激活。请注意,与BCC2任务不同,ECC2任务不能排队激活(Extended tasks, shared priorities and no queued activation. Note that, unlike

BCC2 tasks, ECC2 tasks cannot queue activations.)

下表给出了可以在不同类型的AUTOSAR操作系统中使用的任务类型的快速摘要:

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

1但仅针对ECC2系统中的基本任务。扩展任务的激活不能被排队。

2.4 最大化性能和最小化内存Maximizing Performance and Minimizing Memory

RTA-OS被设计成非常积极地最小化目标应用程序上的代码和数据使用。它将分析应用程序的特性,并生成一个只包含所需特性的系统。

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

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

带有ECC1任务的系统开销更大,而具有一个或多个ECC2任务的系统开销是最大的。

为了使RTA-OS尽可能高效,您应该只使用基本任务,而不是共享优先级。

2.5 任务配置Task Confifiguration

与您可能见过的其他实时操作系统不同,AU TOSAR OS(因此,RTA-OS)中的任务是静态定义的。

这种技术被广泛使用,因为它节省了RAM和执行时间。

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

RTA-OS支持的最大任务数取决于您的端口,您应该参考目标/编译器端口指南了解更多细节。

对于所有端口,RTA-OS可以提供一个高度优化的系统,如果你限制你的任务数量到你的微控制器的本机字大小。

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

图2.5 任务配置

一个AUTOSAR任务有5个属性:

Name. 该名称用于引用或提供要用来实现任务功能的C代码的句柄。

Priority. 调度程序使用优先级来确定任务何时运行。优先值不能动态更改。在RTA-OS中,0是最低的任务优先级。更高的任务优先级由更大的整数表示。任务可以共享优先级,但如果您正在构建一个实时系统,那么就不应该这样做,因为它不能被分析。

Scheduling. 任务可以完全抢先运行,也可以非抢先运行。一般来说,为了获得最佳的应用程序性能,应该选择完全抢占式调度而不是非抢占式调度。

Activations. 可在就绪状态下排队的任务激活的最大数量。对于一个BCC1、ECC1和ECC2任务,激活的次数为1。这意味着这些类型的任务只有在处于挂起状态时才能被激活。任何尝试在未挂起的情况下激活这样的任务都会导致错误。大于1的值表示操作系统将排队激活(例如平滑应用程序中的瞬时峰值负载)。

Autostart. 这将控制在启动操作系统时是否自动启动任务。

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

2.5.1 调度策略Scheduling Policy

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

可以通过在配置时将任务声明为不可抢占来防止其被抢占。声明为非抢占的任务不能被其他任务抢占。当一个非抢占任务移动到运行状态时,它将运行到完成,然后终止(除非它进行Schedule()调用)。因此,使任务具有非抢占性意味着,如果低优先级任务在高优先级任务之前启动,那么在低优先级任务运行期间,高优先级任务将被阻止执行。这叫做阻塞。通常情况下,使用非抢占式任务的系统比抢先式运行的系统响应速度要慢。

即使一个任务不是可抢占的,它仍然可以被ISR中断。

使用不可抢占的任务是不必要的,因为有其他更合适的方法可以用来实现相同的效果。如果使用这些其他技术,通常会产生一个响应性更强的系统。稍后将介绍更多关于这些技巧的内容,其中包括:

  1. 使用标准资源来序列化对数据或设备的访问。
  2. 使用内部资源来精确地指定哪些其他任务不能导致抢占。

2.5.2 排队激活Queued Activation

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

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

要防止失去激活,必须指定任务所需的多次激活的最大激活数。

Note: 根据AUTOSAR操作系统标准,此功能仅可用于基本任务。您不能为扩展的任务指定多个激活。

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

图2.6 指定排队激活的数量

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

当BCC2任务共享优先级时,RTA-OS使用FIFO队列来保存挂起的激活。如果一个BCC2任务在您的AUTOSAR OS应用程序中具有唯一的优先级,那么RTA-OS将自动优化排队策略以计数激活。计数激活法比先进先出激活法效率高得多,应在任何可能的地方使用。

2.5.3 自动启动任务Auto-starting Tasks

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

对于启动、运行然后终止的基本任务,自动启动任务将使它只运行一次,然后才返回挂起状态(在那里它可以再次被激活)。

自动启动主要用于启动等待事件的扩展任务,因为它不需要编写代码来激活任务。

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

在图2.7中,TaskD在OSDEFAULTAPPMODE和ServiceMode应用程序模式下是自动启动的,而在LimpHomeMode和NormalOperatingMode下不是自动启动的。

图2.7 配置自动启动的任务

2.6 栈管理Stack Management

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

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

图2.8 单堆栈行为

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

图2.9显示了相同任务集的执行,具有与图2.8相同的到达模式,但这次任务是非抢先调度的。您可以看到,高优先级任务的响应时间比t时要长得多,但总体堆栈消耗要低得多。

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

2.6.1 使用扩展任务Working with Extended Tasks

RTA-OS独特地扩展了单堆栈模型,以提供对扩展任务的支持,而对基本任务的性能没有任何影响。

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

Suspended ➔ Ready 该任务将被添加到已准备就绪的队列中。

Ready ➔ Running 任务被分派了,但与基本任务不同的是,上下文被放置在堆栈的顶部,上下文被放置在堆栈空间中,位于所有低优先级任务的预先计算的最坏情况抢占深度。

Running ➔ Ready 扩展任务被抢占。如果抢占任务是一个基本任务,那么它将正常地在堆栈顶部分派。如果抢占任务是一个扩展任务,那么它将按照所有低优先级任务预先计算的最坏情况抢占深度进行分派。

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

图2.9 具有非抢占式任务的单堆栈行为

Waiting ➔ Ready 该任务将被添加到已准备就绪的队列中。

Ready ➔ Running 任务的“等待事件堆栈”上下文从内部操作系统缓冲区复制到堆栈中,并以所有低优先级任务的预先计算的最坏情况抢占深度进行计算。

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

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

复制和打开允许恢复扩展任务堆栈上下文。这是必要的,因为高优先级任务和/或isr可能在扩展任务等待时发生。这些可能会消耗比最坏情况抢占点更大的堆栈空间(记住,最坏情况抢占点只适用于较低优先级的对象),因此会超过写入扩展任务的上下文。但是,固定优先级抢占调度。

图2.10 具有扩展任务的单堆栈管理

保证在扩展任务恢复时没有更高优先级的任务可以准备好运行(如果是这种情况,它就不能恢复)。

扩展任务管理需要告诉RTA-OS任务和ISR使用了多少堆栈。下面几节描述各种配置参数。

2.6.2 强制性堆栈信息Mandatory Stack Information

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

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

RTA-OS提供了运行时特性,用于测量任务和ISR的最坏情况下的堆栈值。

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

用于C启动的堆栈(Stack used for C-startup, SpPreStartOS)在调用StartOS()时已经在使用的堆栈量。

该值被简单地添加到操作系统在运行时支持所有任务和中断所需的总堆栈大小中。

通常,您使用它来获得链接器必须分配的堆栈数量。

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

正常情况下,当操作系统配置发生变化时,该值不会改变。

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

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

正常情况下,当操作系统配置发生变化时,该值不会改变。

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

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

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

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

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

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

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

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

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

当操作系统配置有较大变化时,该值可能会发生变化。还需要注意的是,如果你正在使用堆栈重定位(将不受信任的代码堆栈对齐以适应MPU),那么你将需要减少调整量的值。

ISR的堆栈开销(Stack overheads for ISR SpPreemption

图2.11 基本堆栈值

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

该值也用于计算系统最坏情况下的堆栈使用情况。该值可以通过在中断之前测量堆栈值,并在测试期间立即在类别2 ISR的输入函数中获得。注意要准确地设置这个值。如果它的值太高,那么当发生减法时,就会发生32位下溢,并导致操作系统认为已经检测到预算超支。

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

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

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

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

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

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

•操作系统上下文

•任务主体中局部变量的空间

•任务体中调用的任何函数(以及它们的局部函数)所需的空间

可以使用RTA-OS的堆栈度量特性来获取堆栈分配的准确值。

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

图2.12显示了如何配置堆栈分配。

图2.12 堆栈分配配置Stack Allocation Configuration

虽然RTA-OS使用单堆栈模型,但在某些端口上,这并不一定意味着只使用一个物理堆栈。

可能是编译器或硬件自动将数据强制放到不同的堆栈上。例如,TriCore设备使用CSA内存快速保存呼叫上下文。RTA-OS将其视为等同于堆栈。当在你的配置中输入堆栈值时,你应该提供两个逗号分隔的值;第一个用于Supervisor堆栈,第二个用于Context堆栈。

即使有多个物理堆栈,RTA-OS仍然提供了单一堆栈架构的好处——当任务和/或isr共享优先级时,每个物理堆栈上所需的堆栈空间可以叠加。但是,为了使堆栈分配正确工作,您需要指定每个堆栈上所需的空间。

如果您配置了需要此信息的目标,RTA-OS将要求您提供多个堆栈值。图2.13显示了这样一个配置的对话框,其中有两个堆栈:' Supervisor '和' Context '。

图2.13针对多个堆栈的堆栈分配配置Stack Allocation Configuration for multiple stacks

图2.14 指定WaitEvent()堆栈分配

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

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

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

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

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

Note: 如果您保留等待事件()堆栈分配为“未定义的”,那么RTA-OS将默认使用您为堆栈分配指定的字节数。

使用默认值

虽然您应该为每个任务设置一个堆栈值,但RTA-OS允许您设置一个被所有任务使用的全局默认值。这可以在常规➔默认堆栈值中找到。

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

  1. 计算最坏情况下的堆栈偏移量
  2. 配置等待事件(WaitEvent())保存/恢复区域
  3. 堆栈监控(当配置时)

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

2.6.5 处理堆栈溢出Handling Stack Overrun

如果您提供给RTA-OS的堆栈分配数字是错误的(即它们太小),那么在运行时这是一个潜在的错误来源。

有三件事可能会出错:

1. 当RTA-OS试图分发补丁扩展任务时,由于堆栈指针的当前值高于计算出的最坏情况分派点,因此无法启动扩展任务。这意味着堆栈上的一个(或多个)低优先级任务消耗了太多空间。

后面章节中描述的堆栈监视可用于确定哪个任务出现故障。

2. 扩展任务不能从等待状态恢复,因为堆栈指针高于它应有的位置。当为扩展任务正在等待的事件调用SetEvent()并且扩展任务现在是系统中最高优先级的任务时,可能会发生这种情况。

3.扩展任务不能进入等待状态,因为该任务当前使用的堆栈数量大于配置的' WaitEvent()堆栈'的大小。

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

如果你想调试这个问题,那么你可以启用堆栈故障钩子,如图2.15所示。

图2.15 使能Os_Cbk_StackOverrunHook()

#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 2.1: Minimum recommended Os_Cbk_StackOverrunHook()

当配置了Os_Cbk_StackOverrunHook(),当发生堆栈故障时,RTA-OS将调用用户提供的回调Os_Cbk_StackOverrunHook(),而不是关闭OS()。该回调被传递了两个参数:

  1. 溢出数的字节数
  2. 溢出原因

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

•OS_ECC_START—扩展任务无法启动,因为当前堆栈指针超过了构建时计算的最坏情况分派点。此故障的原因是一个(或多个)低优先级任务超过了配置的堆栈分配。要解决这个问题,您需要确定哪个任务出错了。栈监控和测量章节解释了如何使用RTA-OS的堆栈监视特性来做到这一点。

•OS_ECC_RESUME—扩展任务不能从等待中恢复,因为当前堆栈指针超过了构建时计算的最坏情况分派点。此故障的原因是一个(或多个)低优先级任务超过了配置的堆栈分配。

要解决这个问题,您需要确定哪个任务出错了。栈监控和测量章节解释了如何使用RTA-OS的堆栈监视特性来做到这一点。

•OS_ECC_WAIT—扩展任务使用的堆栈空间超过WaitEvent()设置的堆栈大小,无法进入等待状态。要解决这个问题,您应该至少将WaitEvent()堆栈大小增加到溢出参数所指示的字节数。

2.7 实现任务Implementing Tasks

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

TASK(task_identifier)

{

/* Your code */

}

Note: 不需要为任务输入功能提供任何C函数原型。这些都是通过RTA-OS生成的Os.h头文件提供的。

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

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

示例2.3显示了一个名为BCC_Task的基本任务的代码。

#include <Os.h>

TASK(BCC_Task) {

    do_something();

    /* Task must finish with TerminateTask() or equivalent. */

    TerminateTask();

}

Example 2.3: A Basic Task

现在,将示例2.3中的示例与示例2.4进行比较。示例2.4显示,扩展的任务不一定需要终止,并且可以保持在一个循环中等待事件。

#include <Os.h>

TASK(ECC_Task) {

    InitializeTheTask();

    while (WaitEvent(SomeEvent)==E_OK) {

    do_something();

    ClearEvent(SomeEvent);

    }

    /* Task never terminates. */

}

Example 2.4: Extended Task Waiting for Events

2.8 激活任务Activating Tasks

任务只有激活后才能运行。激活可以将任务从挂起状态移动到就绪状态,也可以将另一个条目添加到就绪任务队列(如果任务支持多个激活)。该任务将为每个激活运行一次。

超过激活计数是一个错误,当这种情况发生时,应用程序将生成E_OS_LIMIT错误(即使在标准构建状态下)。

任务可以从两个任务和(第2类)ISRs中激活。

激活任务并不会使任务立即开始执行,它只是使任务准备好运行。然而,RTA-OS需要检查激活的任务是否比当前运行的任务具有更高的优先级,如果是,则导致上下文切换,以便新任务可以抢占当前运行的任务。

当您从另一个任务激活一个任务RTA-OS时,确切的行为取决于相对的任务优先级。如果激活的任务优先级高于当前运行的任务,则新激活的任务将抢占当前任务。否则,该任务将保持在就绪队列中,直到它成为最高优先级的就绪任务。在一个设计良好的实时系统中,一个任务激活一个更高优先级的任务是不寻常的。通常isr捕获系统触发器,然后激活任务以执行任何相关处理。反过来,这些任务可能激活较低优先级的任务,以实现具有较长截止日期的触发器响应。

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

这类似于从ISR激活任务时的行为。所有ISR都有一个严格高于最高任务优先级的优先级。

当一个任务从ISR激活时,它永远不会立即进入运行状态,因此不需要检查上下文切换。

只有在离开ISR时才需要这样的检查。

2.8.1  直接激活Direct Activation

任务可以通过许多不同的方式激活。任务激活的基本机制是ActivateTask() API调用,它直接激活任务。ActivateTask(TaskID)调用将指定任务置于就绪状态。ChainTask(TaskID)调用终止了调用任务(见2.11节),并将命名任务置于就绪状态。

2.8.2  间接激活Indirect Activation

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

报警激活(Activation by an Alarm)。对于系统中的每个告警,您可以指定每次告警过期时激活的任务。

#include <Os.h>

TASK(Task1) {

    /* Task1 functionality. */

    ActivateTask(Task2);

    TerminateTask();

}

TASK(Task2) {
    
    /* Task2 functionality. */

    ActivateTask(Task3);

    TerminateTask();

}

TASK(Task3) {

    /* Task3 functionality. */

    TerminateTask();

}

Example 2.5: Using Direct Activation Chains

通过调度表激活(Activation by a Schedule Table)。对于系统中的每个调度表,可以指定在表上的一个或多个到期点上激活的任务。

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

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

•直接激活链(见2.9.1节)。

•优先级(见章节2.9.2)。

•非抢占任务

2.9.1 直接激活链Direct Activation Chains

当使用直接激活链来控制执行顺序时,任务对必须在进行调用的任务之后执行的任务执行ActivateTask()调用。

考虑以下几点;

有三个任务Task1、Task2和Task3,它们必须按照Task1、Task2、Task3的顺序执行。

示例2.5给出了任务体示例。

图2.16显示了假设Task1优先级最高,Task 3优先级最低,这些任务将如何执行。

图2.16 控制任务执行顺序的直接激活

2.9.2 使用优先级Using Priority Levels

约束任务执行顺序的优先级级方法可以用来利用抢占调度策略的性质来控制激活顺序。

回顾第2.1节,在固定优先级抢占调度下,调度器总是运行最高优先级的任务。如果许多任务被释放到就绪队列中,它们将按优先级顺序执行。这意味着您可以使用任务优先级来控制执行顺序。

从前面的例子来看,在例2.5中,让我们假设Task1的优先级最高,Task3的优先级最低。这意味着可以重写任务主体以利用优先级控制的激活。这可以在例2.6中看到。

图2.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 2.6: Using Priority Level Controlled Activation

图2.17 使用优先级来控制任务的执行顺序

2.10  RTA-OS中的合作调度Co-operative Scheduling in RTA-OS

当一个任务是非抢占式运行时,它会阻止任何任务(包括那些高优先级的任务)的执行。然而,有时对于非抢占式任务来说,提供可以进行重调度的显式位置是有用的。这比简单地非抢先运行更有效,因为高优先级任务对系统刺激的响应时间更短。在一个系统中,任务以非抢先的方式运行,并为重新调度提供点,这种系统被称为协作调度系统。

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

当调用Schedule()时,允许运行任何优先级高于调用任务的就绪任务。Schedule()直到所有高优先级任务结束才返回。

例2.7展示了一个非抢占任务Cooperative,它包括一系列函数调用。一旦启动,每个函数运行到完成时不会抢占,但是任务本身可以在每个函数调用之间被抢占。

图2.18显示了Task1和Task2这两个相互协作的任务在战时如何发挥作用。白色部分表示不可抢占的代码部分。

#include <Os.h>

TASK(Cooperative){

    Function1();

    Schedule();/* Allow preemption */

    Function2();

    Schedule();/* Allow preemption */

    Function3();

    Schedule();/* Allow preemption */

    Function4();

    TerminateTask();

}

Example 2.7: Making a task run co-operatively

图2.18 合作任务Co-operative tasks

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

Schedule()在完全抢占式系统中没有用处。如果不打算使用它,可以使用“优化,RTA-OS, disallow Schedule()”来禁止在rtaoscfg中调用Schedule()。如果不允许对Schedule()的调用,那么将看到系统的最差情况堆栈需求降低了。

2.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)。链接任务将指定的任务置于就绪状态。

2.11.1 优化RTA-OS中的任务终止Optimizing Termination in RTA-OS

AUTOSAR OS标准允许任务在任何时候调用任务终止API调用,包括在嵌套很深的函数调用集中。

这是一种糟糕的编程实践——相当于goto的使用。

在运行时,RTA-OS必须存储允许它在任务终止时清除堆栈的信息,而不是入口函数。这通常使用setjmp/longjmp对来完成。

例2.8显示了对其他函数进行嵌套调用的任务。Task1运行时,它调用Function1()。

Function1()然后调用Function2()。Function2()包含可以终止调用任务的代码(在本例中是Task1)。

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

RTA-OS允许您使用快速终止优化(Optimizations➔fast Terminate)来开发良好的应用程序设计。当所有执行TerminateTask()或ChainTask() api的任务只在它们的entry函数中执行此优化时,您可以启用此优化。优化告诉RTA-OS不生成代码以节省不必要的上下文,从而节省堆栈空间。

#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 2.8: Terminating a Task

2.12 延迟任务Delayed Tasks

OS选项“支持延迟任务执行”可用于添加对api Os_SetDelayedTasks()、Os_AddDelayedTasks()和Os_RemoveDelayedTasks()的支持。

这些api允许您告诉RTA-OS延迟一组任务的执行。延迟任务可以被激活,但直到从集合中删除它们才会实际运行。

Os_SetDelayedTasks()用于指定需要延迟哪些任务。如果一个任务在调用之前被延迟,但它不在新的延迟任务集中,那么如果它的优先级高于调用方,那么它将在此调用返回之前执行。

您必须只设置在调用核心上运行的任务。

Os_AddDelayedTasks()用于向已有的延迟任务集中添加任务。多次添加任务是允许的,但没有效果。必须只添加在调用核心上运行的任务。

Os_RemoveDelayedTasks()用于从去袒护的任务集中移除任务。如果被删除的任务的优先级高于调用方,则它们将在此调用返回之前执行。

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

当启用延迟任务时,任务状态模型变得有点复杂。下表试图解释可能的转换。

2.13 空闲机制The Idle Mechanism

当没有任务或ISR要运行时,任何抢占式操作系统都必须有事可做。在AUTOSAR OS中,这是通过空闲机制实现的。在RTA-OS中,当没有任务或ISR要运行时,操作系统将处于繁忙等待循环中,什么也不做。

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

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

•无法激活

•不能终止

•它不能等待事件

•它不能被束缚

•不能使用内部资源

Os_Cbk_Idle的优先级比系统中的任何任务都低,因此它只在没有准备运行的任务(或ISR)时运行。

因此,空闲机制为您提供了一个几乎完全不受系统开销影响的“额外任务”。

例2.9显示了Os_Cbk_Idle用于控制RTA(参见第17章)的实现。

Os_Cbk_Idle在退出时返回一个布尔值,告诉RTA-OS是否再次调用Os_Cbk_Idle。当返回TRUE时,RTA-OS立即再次调用Os_Cbk_Idle。当返回FALSE时,RTA-OS停止调用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 */

}

2.14 任务开始和结束的构子函数Pre and Post Task Hooks

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

当任务进入运行状态时,RTA-OS会调用PreTask钩子。

这意味着当一个任务在抢占后恢复时,PreTask钩子也将被调用。

当任务移出运行状态时,RTA-OS会调用PostTask钩子。

PostTask钩子将在任务终止时调用,并且每次任务被抢占时调用。

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

这两个钩子只有在配置时才被调用。图2.20显示了如何启用钩子。

图2.19 PreTaskHook()和PostTaskHook()相对于任务抢占

图2.20 Enabling the PreTaskHook() and PostTaskHook()

FUNC(void, OS_CALLOUT_CODE) PreTaskHook(void) {

/* PreTask hook code. */

}

FUNC(void, OS_CALLOUT_CODE) PostTaskHook(void) {

/* PostTask hook code. */

}

Example 2.10: The PreTaskHook and PostTaskHook

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

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

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

•OS_PRETASKHOOK

•OS_POSTTASKHOOK

#ifdef OS_PRETASKHOOK

FUNC(void, OS_CALLOUT_CODE) PreTaskHook (void)

{

/* Your code */

}

#endif /* OS_PRETASKHOOK */

Example 2.11: Conditional Compilation of PreTaskHook

这允许编写代码,其中可以有条件地编译钩子,如示例2.11所示。

2.15 通过抢占保存硬件寄存器Saving Hardware Registers across Preemption

RTA-OS在上下文切换时尽可能少地保存上下文—只保存操作系统正确操作的上下文。但是,可能会发现需要在运行时保存和恢复附加的依赖于应用程序的上下文。例如,可能有使用浮点寄存器的任务,因此需要通过上下文切换保存微控制器的浮点上下文。

可以选择使用PreTask和PostTask钩子和应用程序管理的堆栈手动实现这一点。但是,很难在不改变操作系统配置的情况下优化这种类型的实现。可以:

•始终保存每个交换机上的上下文到一个任务,然后在每个交换机上恢复。这个模型意味着你可能会做不必要的保存和恢复(例如,当切换到一个不使用它的任务时,保存一个寄存器集);或

•离线计算所需的保存,然后编写一个更复杂的钩子对,使用GetTaskID()/GetISRID()来计算是否需要保存/恢复。这个模型是脆弱的,因为对配置的更改,例如添加新的任务/ isr或修改优先级,将意味着需要重新工作。为了避免这些问题,RTA-OS提供了一种简单的通用机制,用于保存特定于用户的上下文和操作系统上下文。RTA-OS能够利用其优先级空间的knowl edge来精确计算哪些任务需要在运行时保存寄存器集,从而优化掉不必要的保存,节省上下文切换所需的时间和堆栈。例如:

•如果你只有一个任务或二类ISR使用一个给定的寄存器集,那么不需要保存或恢复。

•如果多个任务使用相同的寄存器集,但不能同时执行(因为它们不可抢占,共享内部资源或共享优先级),那么RTA-OS不需要保存寄存器集。

图2.21 Register saving in action

•上下文切换到使用寄存器集的最低优先级任务不需要进行保存,因为可以保证没有其他任务可以使用该集(因为如果高优先级任务正在使用寄存器集,则最低优先级任务不可能运行)。

•类似地,从使用寄存器集的最高优先级任务进行上下文切换不需要进行保存,因为没有更高优先级的任务使用寄存器集,因此不会破坏上下文。

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

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

使用寄存器集的每个任务都需要在运行时声明这一点,以便rtaosgen可以计算需要保存的最大集数。图2.23显示了如何为任务执行此操作。

RTA-OS不知道如何或在哪里保存和恢复寄存器集——它只知道需要保存多少次以及何时保存和重新存储它们。对于定义的每个寄存器集,RTA-OS生成一个宏OS_REGSET_<RegisterSetName>_SIZE,用于定义所需的最坏情况下的寄存器集保存数量。应该在应用程序代码中使用它来定义一个大小为OS_REGSET_<RegisterSetName>_SIZE的数组,其中数组的每个元素都包含保存的寄存器集。

图2.22 寄存器集定义

图23. 在一个任务中使用寄存器集

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 2.12: Register Set Save And Restore

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

•Os_Cbk_RegSetSave_<Name>(Os_RegSetDepthType Depth)在需要保存寄存器集时被RTA-OS调用。

•Os_Cbk_RegSetRestore_<NAME>(Os_RegSetDepthType Depth)在需要恢复寄存器集时被RTA OS调用。

两个回调函数都传递了一个Depth值,该值指示要保存或恢复的寄存器集。

例2.12显示了回调函数应该如何出现在你的代码中。

2.16 小结

• 任务是一个并发活动。

• 有两类任务:基本任务和扩展任务。

• 任务可以共享优先级,但建议不要这样做。

• 根据优先级安排任务。

• 当一个高优先级的任务准备好运行时,它将抢占低优先级的任务,但它不会抢占任何已配置为非抢占的任务。

• 任务以就绪、运行、挂起或等待状态存在(但是,只有已扩展的任务可以进入等待状态)。

• 如果一个任务终止,它必须调用TerminateTask()或ChainTask(TaskID)来终止。

• 所有任务在其入口函数中终止的系统可以使用“快速终止”优化来最小化堆栈使用和上下文切换时间。

• 任务只能在处于挂起状态时被激活,除非指定了多个激活。

• PreTask和PostTask钩子允许你在任务开始前和结束后执行代码。这可用于在运行时分析应用程序。

参考文档:

[1] RTA-OS V6.1.3 User Guide

[2] Specification of Operating System AUTOSAR Release 4.2.2

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

汽车电子嵌入式

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

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

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

打赏作者

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

抵扣说明:

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

余额充值