本系列文章将以RTA-OS为例详细介绍AUTOSAR OS标准及概念,并分享实际使用的一些案例,本文为符合AUTOSAR标准的RTA-OS--Interrupts介绍。
中断提供了应用程序与现实世界中发生的事情之间的接口。例如,你可以使用中断来捕捉被按下的按钮,来标记时间的流逝或捕捉一些其他事件。
当中断发生时,处理器通常会查看内存中一个称为向量(Vector)的预定义位置。一个向量通常包含相关的中断处理程序的地址。包含应用程序中所有向量的内存块称为中断向量表。
目标处理器根据所支持的中断优先级的数量进行分类。我们应该理解目标硬件上的中断机制。
有两种不同类型的目标平台:
单级平台Single-level:在单级平台上,有一个单一的中断优先级。如果正在处理中断,所有其他挂起的中断必须等待当前处理完成。
多级平台Multi-level:在多级平台上,有多个中断级别。如果正在处理一个中断,那么它可以被任何具有更高优先级的中断所抢占。也被称为嵌套的中断模型。
AUTOSAR操作系统使用中断服务路程(ISRs)捕获中断。ISR与任务相似;然而,ISRs的不同之处在于:
•ISR不能被RTA-OS API调用激活。
•ISR不能调用TerminateTask()和ChainTask() API。
•ISR从相关中断优先级的入口点开始执行。
•只能在ISR中使用部分RTA-OS API。
AUTOSAR操作系统将中断分为两类,称为1类和2类中断。类别指示操作系统是否参与处理中断。
第一类中断不与RTA-OS交互。它们应该始终是应用程序中优先级最高的中断。正确配置硬件、编写中断处理程序并从中断中返回取决于用户。
处理程序在RTA-OS的优先级级别或更高级别执行。但是,我们可以调用RTA-OS API来启用/禁用中断和恢复/挂起中断。
对于第2类中断,中断向量指向内部RTA-OS代码。当中断被触发时,RTA-OS执行内部代码,然后调用用户提供的处理程序。
这个处理程序是作为一个绑定到中断的ISR提供的(可以认为这是一个非常高优先级的任务)。从ISR的指定入口点开始执行,一直持续到入口函数返回。当入口函数返回时,RTA-OS执行另一小段内部代码,然后从中断返回。
图3.1 二类中断处理状态图
图3.2 可视化RTA-OS第2类包装器(Wrappers)
中断在中断优先级级别(IPL)上执行。RTA-OS对所有目标微控制器的IPL进行标准化,IPL 0表示用户级别,所有任务执行,1或以上的IPL表示中断级别。重要的是,不要混淆IPL与任务优先级。如果IPL为1,则会高于应用程序中使用的最高任务优先级。
IPL是对目标硬件上的中断优先级的与处理器无关的描述。
在一个单级平台上,有两个ipl,0和1。IPL 0表示目标没有中断,任务按优先级顺序运行。IPL 1表示目标正在为中断提供服务。由于只有一个非零的IPL,所有的中断,包括第1类和第2类,都以相同的优先级运行。这意味着所有的中断都是序列化的。
在多级平台上,较高优先级的中断可以优先于较低优先级的中断,因此,可以嵌套ISR处理程序。因此,例如,较高优先级ISR可以中断低优先级ISR的执行。但是,ISR永远不能被任务抢占。
第一类ISR不能被第二类ISR中断。这是因为2类ISR有可能激活一个任务,因此操作系统需要在离开ISR时检查上下文切换——这就是操作系统在图3.2所示的“包装器Wrappers”函数的第二部分所做的事情。由于ISR可以在多级平台上嵌套,因此必须在每个中断退出时进行此检查。现在,如果第1类ISR可以被第2类ISR抢占,那么在退出第1类ISR时,不会检查上下文切换,并且最初被抢占的任务将恢复,而不是激活的高优先级任务。这是优先级反转,可能在系统中导致未知的副作用。
此问题意味着所有第2类ISR必须具有不高于最低优先级的第1类ISR的IPL。RTA-OS在生成时自动检查它,如果是这种情况,将会产生一个错误。
单级平台和多级平台的中断优先级层次结构如图3.3所示。
图3.3 中断优先级层次结构
用户级别是允许处理所有中断的最低中断优先级级别。所有任务都从其入口点开始在用户级别上执行。
一个任务有时需要在用户级别以上运行,例如,它可能需要访问与ISR共享的数据。在访问数据时,它必须防止中断被提供服务。最简单的方法是让该任务在访问数据时禁用中断。一种机制是使用AUTOSAR操作系统的资源机制。
即使任务的优先级高于用户级别的中断,ISR也可以优先执行任务。但是,只有ISR的中断优先级高于当前的优先级,它才能做到这一点。
图3.4 配置中断
最高优先级类别2 ISR的优先级定义了操作系统级别。如果执行发生在操作系统级别或更高级别,则不会发生其他第2类中断。
RTA-OS使用OS级别来防止对内部OS数据结构的并发访问。任何操纵操作系统内部状态的RTA-OS API都将在操作系统级别执行部分(如果不是全部)执行时间。操作系统钩子(例如Error钩子,PreTask和PostTaskHook)和操作系统回调也在操作系统级别运行。如果任务在操作系统级别执行,则不会发生RTA-OS操作(除了任务进行的调用)。
在RTA-OS中,中断使用rtaoscfg进行静态配置。图3.4显示了一个中断是如何被构造出来的。
在最简单的级别上,一个中断具有以下属性:
中断名字:该名称用于引用实现中断处理程序功能的C代码。
中断类别:如果处理程序不需要执行RTA-OS API调用,则这是第1类,否则是第2类。
中断优先级:调度程序使用优先级来确定中断何时运行(类似于任务使用的任务优先级)。优先级是微控制器特定的参数,因此在设置优先级之前必须选择RTA-OS目标。请注意,有些目标只支持单个中断优先级。
中断向量:RTA-OS使用指定的向量为中断生成向量表条目(也就是中断在MCU中的地址)。与中断优先级一样,中断向量配置是微控制器特有的,因此在配置中断向量之前必须选择一个目标。
注意: 在IPL是用户可编程的微控制器上,集成者应该确保内部中断设备的编程优先级与RTA-OS配置的级别相匹配。由于此配置必须在操作系统启动之前进行,因此RTA-OS无法完成此操作,因为可能需要执行1类ISR。RTA-OS可能能够生成适当的配置数据供使用。集成者需要查阅目标/编译器端口指南以获得具体说明。
集成指导:RTA-OS GUI允许选择不同的目标(例如,允许快速迁移一个操作系统配置到一个新的微控制器)。当更改目标时,将删除所有特定于目标的配置,包括中断优先级和中断向量设置。因此,需要酌情提供新的目标设置。
在大多数情况下,RTA-OS可以自动生成向量表。rtaosgen将创建一个向量表,其中正确的向量指向内部包装器(Wrappers)代码,并将其放在生成的库中。
如果您想编写自己的向量表,那么您必须确保RTA-OS不会生成向量表。您可以通过禁用向量表生成(Target ➔ Disable Vector Table Generation)来防止生成向量表,如图3.5所示。
当自定义的编写向量表时,需要确保所有与第2类ISRs分支相关联的中断向量到RTA-OS中断包装器,该包装器设置ISR执行的上下文。
注意:不能直接编写中断处理程序。这样做将绕过RTA-OS,并且您试图在处理程序的上下文中与内核进行的任何交互都很可能导致内核状态的不可恢复的损坏。
通常,自己编写的向量表需要满足如下形式的标签:Os_Wrapper_VECTOR,其中vector是向量的十六进制地址。然而,确切的细节是具体硬件平台特定的。应该查阅目标/编译器端口指南,以获得如何提供自己的向量表的具体细节。
图3.5 防止RTA-OS自动生成向量表
编写第1类ISR的格式是不可移植的。微控制器的编译器通常定义一个特定于ANSI C的编译器扩展,允许将函数标记为中断。然而,有些编译器不能做到这一点。当这种情况发生时,需要编写一个汇编语言处理程序。
确保第1类ISR入口函数的名称与您在配置过程中为该ISR指定的名称相同。
对于第1类ISR,通常必须使用一个特定于编译器的关键字(有时称为实用码函数或指令)。RTA-OS提供了一个名为CAT1_ISR的宏,它可以扩展为编译器工具链的正确指令,使用它来将函数标记为第1类ISR。
CAT1_ISR(Interrupt1) {
/* Handler body. */
/* Return from interrupt. */
}
Example 3.1: Entry Function for a Category 1 ISR
2类中断是在RTA-OS的控制下处理的。一个2类ISR类似于一个任务。它有一个入口函数,当中断处理程序需要运行时,由RTA-OS调用。2类中断处理程序是使用示例3.2中的C语法编写的。
#include <Os.h>
ISR(isr_identifier){
/* Handler body. */
}
Example 3.2: Entry Function for a Category 2 ISR
不需要为第2类ISR入口函数提供任何C函数原型。这些在rtaosgen生成的Os.h头文件中提供。
注意:不能在类别2 ISR中放置“从中断返回”命令。从中断返回-由RTA-OS处理。
当硬件检测到一个中断时,它通常会设置一个挂起位,告诉中断控制器已经发生了一个中断。然后,中断控制器将通过中断向量表切换到处理程序。
挂起位的处理取决于目标硬件,但有两个基本模型:
1.挂起位在中断处理后自动清除(即当中断处理程序开始执行时)。当处理程序退出时,如果当前中断正在处理时,中断已成为挂起,则该处理程序将自动重新触发。
2.挂起的位必须由中断处理程序中的用户代码手动清除。中断处理程序的主体,无论是第1类还是第2类,都需要包括清除挂起位的代码,并向硬件发出信号,然后中断已被处理。
如果需要清除挂起位,最好在进入处理程序时立即执行此操作,因为这样可以最大限度地减少中断发生的第二个实例设置挂起位与随后清除挂起位之间的时间。这有助于防止中断多次挂起但硬件无法识别的问题。代码示例3.3展示了二类ISR处理程序的推荐结构。
#include <Os.h>
ISR(Interrupt1) {
/* Dismiss the interrupt where required */
/* Rest of the handler */
}
Example 3.3: Dismissing the interrupt
#include <Os.h>
ISR(InefficientHandler) {
/* Long handler code. */
}
Example 3.4: Inefficient interrupt handler
#include <Os.h>
ISR(EfficientHandler) {
ActivateTask(Task1);
}
TASK(Task1) {
/* Long handler code. */
TerminateTask();
}
Example 3.5: More efficient interrupt handler
需要查阅硬件参考手册,以了解需要在目标硬件上执行的操作。
编写的每个中断处理程序将在代码执行所需的时间内阻塞所有同等或更低优先级的中断。在编写中断处理程序时,使处理程序尽可能短是一种良好的做法。长时间运行的处理程序将为低优先级中断的服务增加额外的延迟。
通过最小化中断处理程序的执行时间,可以最大化整个系统的响应性。
如果需要执行一段长时间运行的代码来响应中断发生,那么可以将该代码放入任务中,然后从Category 2 ISR激活该任务。例3.4和例3.5展示了这些技术的不同之处。
使用第2类处理程序,可以将所需的功能移动到任务中,只需使用中断处理程序来激活任务,然后终止。
中断只有在启用时才会发生。默认情况下,RTA-OS确保在StartOS()返回时启用所有内部中断。
集成指导:AUTOSAR操作系统使用术语“禁用”表示屏蔽中断,“启用”表示取消屏蔽中断。因此,启用和禁用API调用不会启用或禁用中断源;它们只是阻止处理器识别中断(通常通过修改处理器的中断掩码)。
需要在短时间内禁用中断,以防止在任务或ISR中的关键代码部分发生中断。临界区可以是访问共享数据的代码区。
可以使用许多不同的API调用来启用和禁用中断:
• DisableAllInterrupts() and EnableAllInterrupts()
禁用和启用硬件上可以禁用的所有中断(通常是所有可以屏蔽的中断)。这些调用不能嵌套。在DisableAllInterrupts()之后,不允许调用除EnableAllInterrupts()之外的操作系统API。
• SuspendAllInterrupts() and ResumeAllInterrupts()
挂起并恢复所有可以在硬件上禁用的中断(通常是所有可以屏蔽的中断)。这些调用可以嵌套。在SuspendAllInterrupts()之后,不允许在SuspendAllInterrupts()/ResumeAllInterrupts()对和SuspendOSInterrupts()/ResumeOSInterrupts()对之外调用API。
• SuspendOSInterrupts() and ResumeOSInterrupts()
挂起和恢复硬件上的所有第2类中断。这些调用可以嵌套。除了SuspendAllInterrupts()/ResumeAllInterrupts()对和SuspendOSInterrupts()/ResumeOSInterrupts()对之外,不允许在SuspendOSInterrupts()之后调用API。
注意:必须确保“Resume”调用不会比“Suspend”调用多。如果存在,则可能导致严重错误,并且行为未定义。随后的' Suspend '调用可能无法工作。这将导致无保护的临界区。
示例3.6显示了是如何正确地使用中断控制API调用和嵌套的。
在第1类ISR的情况下,必须确保在禁用中断的整个时间内没有进行RTA-OS API调用(其他挂起/恢复调用除外)。
如果2类ISR通过调用DisableAllInterrupts()将中断级别提高到OS级别以上,那么它可能不会进行任何其他RTA-OS API调用,除了调用EnableAllInterrupts()来恢复中断优先级。当执行ISR时,不允许将中断优先级降低到初始级别以下。
#include <Os.h>
TASK(Task1) {
DisableAllInterrupts();
/* First critical section */
/* Nesting not allowed */
EnableAllInterrupts();
SuspendOSInterrupts();
/* Second critical section */
/* Nesting allowed. */
SuspendAllInterrupts();
/* Third critical section */
/* Nested inside second */
ResumeAllInterrupts();
ResumeOSInterrupts();
TerminateTask();
}
Example 3.6: Nesting Interrupt Control API Calls
RTA-OS提供了一种跨上下文切换保存寄存器集的机制,rtaosgen可以优化提高运行时性能所需的保存量。
2类ISR也可以使用相同的机制,只需选择哪些ISR使用图3.6所示的配置寄存器集。
图3.6:在类别2 ISR中的寄存器集
如果使用RTA-OS来生成一个向量表,那么可能需要用一个默认的中断来填充未使用的向量位置。
图3.7显示了如何定义默认中断的。
图3.7:在向量表中放置一个默认中断
注意:不要从默认中断进行任何RTA-OS API调用,并且不能从处理程序返回。
默认中断就像第1类中断一样实现,因此必须用CAT1_ISR宏标记为中断。默认中断处理程序中的最后一条语句应该是一个无限循环。示例3.7显示了如何做到这一点。
CAT1_ISR(DefaultInterruptHandler) {
/* invoke target-specific code to lock interrupts */
asm(’di’); /* or whatever on your platform */
for (;;) {
/* Loop forever */
}
/* Do NOT return from default handler. */
}
Example 3.7: The Default Interrupt Handler
•RTA-OS支持1类中断和2类中断。
•1类ISR是绕过RTA-OS的正常嵌入式系统中断。因此,它们不能与操作系统交互,并且被禁止进行(大多数)RTA-OS API调用。它们应该使用CAT1_ISR宏进行标记。
•第二类ISR是操作系统管理的中断,运行在RTA操作系统提供的包装器(Wrappers)中。这些中断可以调用RTA-OS API。它们必须使用ISR宏进行标记。
•所有中断运行在中断优先级(IPL),它总是严格高于最高任务优先级。
•IPL标准化了所有硬件设备的中断优先级模型-更高的IPL意味着更高的优先级。
•RTA-OS可以生成一个中断向量表,或者你可以选择自己编写。当生中断向量表时,RTA-OS可以用用户配置的默认中断插入未使用的位置。
参考文档:
[1] RTA-OS V6.1.3 User Guide