编辑推荐: |
本文主要介绍AUTOSAR OS的错误处理,并对基于英飞凌Aurix TC3XX系列芯片的Vector
MICROSAR代码和配置进行部分讲解。 希望对您的学习有所帮助。
本文来自于微信公众号TecLlink汽车软件,由火龙果软件Linda编辑、推荐。
|
|
本文主要介绍AUTOSAR OS的错误处理,并对基于英飞凌Aurix TC3XX系列芯片的Vector MICROSAR代码和配置进行部分讲解。
01 模块简介
AUTOSAR操作系统具有多种保护机制,比如内存保护、时间保护、栈保护等,以及芯片系统的异常。对于这些保护机制,当产生错误时,操作系统需要进行捕捉,根据用户的配置和代码逻辑执行相应的动作,以防止系统崩溃,或者以预期外的逻辑运行。
在AUTOSAR中,当出现保护错误时,操作系统模块会调用用户提供的保护钩子(Protection
Hook)来通知运行时保护错误。该保护钩子在操作系统模块的上下文中运行,因此必须为可信代码。
所谓的Hook——钩子函数,就是操作系统执行到某处调用该函数,该函数的实现由用户来定义,用以将用户的操作意图插入到某个操作系统的执行步骤中,类似穿针引线,因此称为钩子函数。
操作系统模块本身仅需检测错误并提供处置能力。保护钩子可从操作系统模块提供的四个选项中选择其一,根据钩子函数的返回值,在从保护钩子返回后执行相应操作。
忽略错误;
强制关闭任务、中断;
强制关闭一个Os-Application;
关闭整个操作系统;
除了错误处理以外,AUTOSAR OS还提供了其他类型的Hook,比如PreTaskHook和PostTaskHook,用户可以在Hook中放入监控逻辑,以实现任务追踪。
02功能介绍
2.1 错误类型及其他Hook类型
在AUTOSAR中,定义了ProtectionHook()和ErrorHook错误类型,在Vector
MICROSAR中,定义了三种错误类型,分别是Application Errors、Kernel Errors和Protection
Errors。另外还提供了启动、任务切换的Hook,共有如下类型:
ErrorHook:Application Errors,处理OS运行过程中的一般性错误;
ProtectionHook:Protection Errors,处理用户运行错误如栈溢出、异常等;
Os_PanicHook:Kernel Errors,处理操作系统内核错误。
StartupHook:启动过程钩子函数;
ShutdownHook:关闭OS的钩子函数,一般用来回调EcuM_Shutdown()以执行下电;
PreTaskHook:线程切换前钩子函数,一般用于任务追踪计时等;
PostTaskHook:线程切换后钩子函数,可同PreTaskHook配合使用。
这些钩子函数都具有操作系统相同的访问权限,也就是内核访问权限,因此代码逻辑要非常谨慎,尤其是ProtectionHook,会在Trap场景下调用,注意临界区的数据相关保护。
2.2 错误及Hook相关配置
在Vector工具中,Hook需要通过配置进行使能。

另外一些Hook如ProtectionHook是具有独立的线程的,并且需要分配独立的栈空间,系统默认一般是1024。

2.3 ErrorHook
如前所述,ErrorHook是用来处理一些常见的Os应用错误,比如Task的过度激活等。
其调用时序图如图所示,当Os执行内核逻辑时,某些接口执行错误会返回异常值,Os内核会检查这些异常返回值,并根据返回错误类型返回对应的Os错误。

这里我们以最常见的OS_STATUS_LIMIT错误类型为示例,该错误表示Task的激活次数超过了最大限制,在Task章节我们介绍过,基础任务有一个OsTaskActivation配置,一般配置为1,表示该Task不具备缓存功能,激活一次之后必须执行完才能激活下一次,如果前一次没有执行完毕,直接激活,则会发生该错误。
我们在中断中打断点之后运行,即可复现该问题,这是因为打断点之后定时器没有停(即使硬件定时器配置了断点停止功能,调试器仍然具有延迟),因此中断会过多进入。
这里是Vector工程自带的示例代码,可以看到这里使用Os_GetDetailedError接口获取错误细节,这是个结构体,稍后我们介绍。
FUNC(void, OS_ERRORHOOK_CODE) ErrorHook(StatusType
Error)
{
Os_ErrorInformationType CurrentError;
volatile uint8 endless = 1u;
(void)Os_GetDetailedError(&CurrentError);
while(endless)
{
}
}
|
我们复现问题之后将断点打在Os_TaskActivateLocalTask函数中这个位置:

由于编译优化可能这里的断点不太好打,需要查询下汇编,这里通过汇编看到0x8001ddd6进行了判断,判断失败会执行下一行,也就是Task激活次数超过的代码逻辑。

然后这里会调用Os_ErrApplicationError,最终切换到ErrorHook的线程,中间过程不用详细介绍,可参考前面关于Task的文章。最终会执行到ErrorHook()。

重点来了,我们看ErrorHook中的Os_GetDetailedError接口的使用,这个接口会将内核记录的错误状态返回给用户,我们观察这个变量。

可以看到这里把服务、错误细节都给出来了,这种情况我们只需要根据这些信息去查询对应的接口和错误值就可以了。这里我们根据服务就可以搜到(不要全局)对应的位置:

可以看到是Os_TaskActivateTaskInternal的返回值异常,并进行了回调。
关于错误类型,我们可以查询AUTOSAR OS文档,或者Vector计数文档,也可以通过代码中Os_Types.h文件,查询Enum类型Os_StatusType。

我们也可以在ErrorHook中增加代码屏蔽该错误,毕竟调试会经常遇到。
/* Bypass debug error.*/
if((CurrentError.Error == E_OS_LIMIT) &&
(CurrentError.Service == OSServiceId_AlarmActionActivateTask)){
endless = 0u;
}
|
2.4 AUTOSAR OS Trap处理
在介绍ProtectionHook之前,我们先介绍下AUTOSAR OS的Trap处理。
在前面关于英飞凌芯片系列文章中,我们已经介绍过Trap的硬件处理流程,由于异常作为Os处理的一个重要职责,这里我们介绍下Vector工具中关于Trap的内容。
在AUTOSAR工具中,Trap是可以配置的,其配置方法是在ISR中进行配置。

这里注意把类型配成1类中断,InterruptSource就是TrapClass,InterruptType配置为EXCEPTION。
我们在Os_Hal_Entry_Lcfg.c文件中,可以看到Trap向量表的宏定义。
Os_Hal_ExceptionSectionDeclaration(0)
Os_Hal_UnhandledTrapEntry(0, 0)
Os_Hal_MemoryTrapEntry(0, 1)
Os_Hal_UnhandledTrapEntry(0, 2)
Os_Hal_UnhandledTrapEntry(0, 3)
Os_Hal_UnhandledTrapEntry(0, 4)
Os_Hal_UnhandledTrapEntry(0, 5)
Os_Hal_SysCallTrapEntry(0)
Os_Hal_UserTrapEntry(0, 7, Trap7)
|
一般我们不需要进行定义,除了开启了SC3内存保护的Trap会单独定义以外,其他默认Os_Hal_UnhandledTrapEntry就可以了。
# define Os_Hal_UnhandledTrapEntry(core, class)
\
__asm (" .SECT \".text.OS_EXCVEC_CORE"#core"_CODE\"");
\
__asm (" .ALIGN 32"); \
__asm (" .EXTERN Os_Hal_UnhandledExceptionHandler");
\
__asm (" .GLOBAL osTrap_" #class "_Core"
#core); \
__asm ("osTrap_" #class "_Core"
#core ": svlcx"); /* save lower context
*/ \
__asm (" movh d4, #" #class); /* prepare
parameters for C-Function; D4 = Exception class
*/ \
__asm (" or d4, d15"); /* prepare
parameters for C-Function; D4 |= TIN (D15) */
\
__asm (" movh.a a14, #@his(Os_Hal_UnhandledExceptionHandler)");
/* load address of Os_Hal_UnhandledExceptionHandler
*/ \
__asm (" lea a14,[a14]@los(Os_Hal_UnhandledExceptionHandler)");
/* load address of Os_Hal_UnhandledExceptionHandler
*/ \
__asm (" ji a14 ");
|
2.5 ProtectionHook
ProtectionHook是这里面最重要的一个错误处理钩子函数,操作系统出现栈溢出、系统异常都会流经这里。并且AUTOSAR
OS中的内存保护、时间保护等异常,都会调用该钩子函数,因此这里是我们集中处理系统异常的地方。
AUTOSAR中定义的ProtectionHook时序图如图所示,程序运行出现异常之后会调用操作系统的错误处理,并最终执行到ProtectionHook,并根据用户在钩子函数中的返回值,执行对应的动作,比如忽略或者下电。

下面我们通过一个Trap示例,介绍下Trap的引导链路和信息获取。
在英飞凌芯片中,0x10000是不存在的地址,我们通过代码模拟一段Trap:
voidMyTest()
{
/* Init Data Ptr and Test Var. */
volatile uint32 *TestAddr = (uint32*)0x10000;
volatile uint32 TestVar;
/* Here test memory access. */
TestVar = *TestAddr;
*TestAddr = 1u;
}
|
我们将断点打在异常执行的代码上,对应的地址为0x8005baba。

在发生Trap时,系统会执行到Trap向量表,然后进入Os_Hal_UnhandledExceptionHandler。
OS_FUNC_ATTRIBUTE_DECLARATION(void, OS_CODE,
OS_HAL_NOINLINE, Os_Hal_UnhandledExceptionHandler,
(void));
OS_FUNC_ATTRIBUTE_DEFINITION(void, OS_CODE,
OS_HAL_NOINLINE, Os_Hal_UnhandledExceptionHandler,
(void))
{
__asm ("mfcr d6, #" OS_HAL_EXPAND(OS_HAL_COREMPU_DPR_LOW0));
/* prepare parameters for C-Function; D6 = OS_HAL_COREMPU_DPR_LOW0
*/
__asm ("mfcr d7, #" OS_HAL_EXPAND(OS_HAL_COREMPU_DPR_UPPER0));
/* prepare parameters for C-Function; D7 = OS_HAL_COREMPU_DPR_UPPER0
*/
__asm ("mfcr d14, #" OS_HAL_EXPAND(OS_HAL_CORE_ID_REGISTER));
/* get core ID; D14 = Core ID */
__asm ("movh.a a4, #@his(OsCfg_Stack_KernelStacks)");
/* get address of the correct kernal stack (indexed
by core ID) */ /* PRQA S 0286 *//* MD_Os_Hal_Dir1.1_0286
*/
__asm ("lea a4,[a4]@los(OsCfg_Stack_KernelStacks)");
/* get address of the correct kernal stack (indexed
by core ID); A4 = &OsCfg_Stack_KernelStacks
*/ /* PRQA S 0286 *//* MD_Os_Hal_Dir1.1_0286
*/
__asm ("addsc.a a15,a4,d14,#2"); /*
choose the correct element out of the array;
A15 = &OsCfg_Stack_KernelStacks[Core ID]
*/
__asm ("ld.a a15,[a15]"); /* get the
pointer to the Os_Hal_ContextStackConfigType
struct */
__asm ("ld.w d12,[a15]"); /* get the
content of Stack.StackRegionStart; D12 = Stack.StackRegionStart
*/
__asm ("mtcr #" OS_HAL_EXPAND(OS_HAL_COREMPU_DPR_LOW0)",
d12"); /* OS_HAL_COREMPU_DPR_LOW0 = D12
*/
__asm ("ld.w d12,[a15]4"); /* get
the content of Stack.StackRegionEnd; D12 = Stack.StackRegionEnd
*/
__asm ("mtcr #" OS_HAL_EXPAND(OS_HAL_COREMPU_DPR_UPPER0)",
d12"); /* OS_HAL_COREMPU_DPR_UPPER0 = D12
*/
__asm ("isync");
__asm ("mov.a a10, d12"); /* SP =
Stack.StackRegionEnd; stack switch */
__asm ("mfcr d5, #" OS_HAL_EXPAND(OS_HAL_PCXI_OFFSET));
/* prepare parameters for C-Function; D5 = PCXI
*/
__asm ("mov.aa a4, a11"); /* prepare
parameters for C-Function; A4 = Return Address
(A11) */
__asm ("call Os_Hal_UnhandledExc");
/* call the C-Function; the stack MPU regions
are restored within the C-Function */
__asm ("rslcx"); /* restore lower
context saved exception vector handler */
__asm ("rfe"); /* return from exception;
old stackpointer is restored with this instruction
*/
}
|
在Os_Hal_UnhandledExceptionHandler中将Trap Class和TIN写入到D4寄存器中,将发生Trap的代码地址写入到A4中,并写入当前栈上下限信息等,最终执行Os_Hal_UnhandledExc进入到OS的处理函数中。
最终会执行到ProtectionHook函数,并提供一个入参。
FUNC(ProtectionReturnType, OS_PROTECTIONHOOK_CODE)
ProtectionHook(StatusType Fatalerror)
{
volatile Os_ErrorInformationType CurrentError;
volatile Os_ExceptionContextType Context;
(void)Os_GetDetailedError(&CurrentError);
/* 这个接口这里无法得到有效信息 */
Os_GetExceptionContext(&Context);
return PRO_SHUTDOWN;
}
|
首先我们在入口处观察入参Fatalerror,其值为18。

这里我们同样是查询Os_StatusType类型,得到OS_STATUS_PROTECTION_EXCEPTION。表明发生的类型为Trap。这里需要注意,如果是内存保护错误,也就是Trap
Class 1,是有单独的错误类型定义的,即OS_STATUS_PROTECTION_MEMORY。

注意在这里Os_GetDetailedError就没有作用了,因为这里的错误不是OS的应用错误。我们可以在这里观察得到的值,发现都是0。

然后下面的接口Os_GetExceptionContext是用来获取系统保存的异常信息的接口,这个接口里的信息非常丰富,通过这个接口我们能够得到异常发生的所有寄存器状态、地址等。我们观察这个接口获取的内容。

我们一一介绍这些成员变量。
uint32 AddressRegisters[16]:16个地址寄存器;
uint32 DataRegisters[16]:16个数据寄存器;
uint32 Ra:返回地址,即发生异常的代码地址,与前面我们断点的位置相符;
uint32 Psw:程序状态字,记录了异常发生时的状态;
uint32 ExceptionSource:异常源,这里是Trap Class4,TIN 2;
uint32 Pcpn:前一个中断优先级;
uint32 Pie:中断使能位;
uint32 MpuRegionForStackLow:MPU中保存的栈上边界;
uint32 MpuRegionForStackUpper:MPU中保存的栈下边界;
uint32 RawPCXI:异常发生时的PCXI。
这里首先最重要的是返回地址Ra,因为它能告诉我们发生异常的位置,然后就是中断源。
然后比较关键的是最后一个成员RawPCXI,我们可以根据TriCore上下文的原理(读者可参考之前的文章,内核详解——上下文),一层层向上索引记录函数的调用信息,这样MCU在没有调试的情况下,也能把调用栈记录下来,并进行保存,我们就能用于后续的故障排查和定位。
最后返回值PRO_SHUTDOWN,表示之后OS会执行关闭动作,用户也可以根据错误类型,进行Application重启或者忽略错误等。
2.6 Os_PanicHook
Vector MICROSAR中除了上述两种错误,还定义了Os_PanicHook错误类型,用于捕获Os内核错误。一般是OS执行到内核状态时发生的一些错误,会进入该Hook。比如在ProtectionHook代码中出现了错误,或者内核的执行出现了逻辑错误。
该函数中可以使用Os_GetDetailedError接口获取错误信息,这里就不进行演示了,排查方法同ErrorHook。
2.7 StartupHook
StartupHook是一个启动钩子函数,在Os_CoreInit函数中会调用,用于用户执行一些启动逻辑,这个使用的比较少。
2.8 ShutdownHook
ShutdownHook用于执行OS关闭的钩子函数,在OS关闭之后,此时系统已经不具备调度能力了,需要把控制权交给EcuM,由EcuM进行单线程工作,执行系统的下电。
FUNC(void, OS_SHUTDOWNHOOK_CODE) ShutdownHook(StatusType
Fatalerror)
{
EcuM_Shutdown();
}
|
2.9 PreTaskHook & PostTaskHook
关于PreTaskHook和PostTaskHook的调用时序,我们在Task文章介绍中已经进行了说明,读者可自行查阅。
在这两个Hook中,我们可以放入任务监控或者追踪代码,进行任务的运行数据记录,以实现OS的调度性能测试。但由于任务切换频繁进行,所以在这两个Hook中不能放入逻辑过多的代码,否则系统会消耗过多额外的负载。
另外关于任务的响应性测量,AUTOSAR中提供了RTM(Runtime Measurement)模块,用于进行负载监控和任务响应性监控。
03小结
本文介绍了AUTOSAR OS中的错误处理,通过多个示例展示了AUTOSAR OS对于各种类型错误的执行路径,以及问题的排查技巧,能够帮助读者解决OS调试过程中遇到的一般性问题。并顺带介绍了OS的其他钩子函数。
|