RTOS诊断和错误检查
RTOS诊断和错误检查
RTOS diagnostics and error checking
查看RTOS显示系列
错误处理不太可能是任何用于嵌入式系统应用程序的操作系统的主要功能。这是资源限制的必然结果,而且所有嵌入式系统都有某种限制。这也是合乎逻辑的,因为只有有限数量的嵌入式系统才有机会像桌面系统一样工作,即为用户提供一个机会,以便在发生异常事件时决定下一步要做什么。
在Nucleus SE中,大致有三种类型的错误检查:
对所选配置进行“健全性检查”的功能-只是为了确保所选选项的一致性
可选地包含用于检查运行时行为的代码
有助于设计更健壮代码的特定API函数
这些都将在本文中讨论,并提供一些关于用户实现诊断的想法。
配置检查
Nucleus SE的设计非常便于用户配置,因此可以对其进行定制,以充分利用可用资源。这种可配置性是一个挑战,因为选项的数量以及它们之间的相互依赖性非常大。正如前面的许多文章中所描述的,Nucleus SE的大多数用户配置都是通过在nuse_config.h文件中设置#define常量来执行的。
为了帮助识别配置错误,包含了一个文件–nuse_config_check.h–即通过一个#include into nuse_config.c),该文件对#define符号执行多项一致性检查。以下是此文件的摘录:
/*** Tasks and task control ***/
#if NUSE_TASK_NUMBER < 1 || NUSE_TASK_NUMBER > 16
#error NUSE: invalid number of tasks – must be 1-16
#endif
#if NUSE_TASK_RELINQUISH && (NUSE_SCHEDULER_TYPE ==
NUSE_PRIORITY_SCHEDULER)
#error NUSE: NUSE_Task_Relinquish() selected – not valid with
priority scheduler
#endif
#if NUSE_TASK_RESUME && !NUSE_SUSPEND_ENABLE
#error NUSE: NUSE_Task_Resume() selected – task
suspend not
enabled
#endif
#if NUSE_TASK_SUSPEND && !NUSE_SUSPEND_ENABLE
#error NUSE: NUSE_Task_Suspend() selected – task
suspend not
enabled
#endif
#if NUSE_INITIAL_TASK_STATE_SUPPORT && !NUSE_SUSPEND_ENABLE
#error NUSE: Initial task state enabled – task suspend not
enabled
#endif
/*** Partition pools ***/
#if NUSE_PARTITION_POOL_NUMBER > 16
#error NUSE: invalid number of partition pools – must be 0-16
#endif
#if NUSE_PARTITION_POOL_NUMBER == 0
#if NUSE_PARTITION_ALLOCATE
#error NUSE: NUSE_Partition_Allocate()
enabled – no
partition pools configured
#endif
#if NUSE_PARTITION_DEALLOCATE
#error NUSE:
NUSE_Partition_Deallocate() enabled – no
partition pools configured
#endif
#if NUSE_PARTITION_POOL_INFORMATION
#error NUSE:
NUSE_Partition_Pool_Information() enabled –
no partition pools configured
#endif
#endif
执行的检查包括:
验证是否至少配置了一个但不超过16个任务
确认所选API函数与所选调度程序或其他选项不一致
验证是否指定了不超过16个其他内核对象的实例
确认没有为根本没有实例化的对象选择API函数
确保在未启用信号和系统时间的API函数时未选择这些功能
验证选定的计划程序类型和相关选项
在所有情况下,检测到错误都会导致编译错误语句。这通常会导致编译被指定的消息终止。
此文件不会使创建不合逻辑的配置成为不可能,但会使其变得极不可能。
API参数检查
与Nucleus RTOS一样,Nucleus SE还可以选择包含代码,以便在运行时验证API函数调用参数。通常这只会在最初的调试和测试中使用,因为在生产代码中内存和运行时开销是不需要的。
通过将NUSE_config.h中的NUSE_API_Parameter_checking设置为TRUE来启用参数检查。这样就可以编译所需的附加代码。下面是一个API函数的参数检查示例:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX
mailbox, ADDR *message,
U8 suspend)
{
STATUS return_value;
#if NUSE_API_PARAMETER_CHECKING
if (mailbox >=
NUSE_MAILBOX_NUMBER)
{
return NUSE_INVALID_MAILBOX;
}
if (message == NULL)
{
return NUSE_INVALID_POINTER;
}
#if NUSE_BLOCKING_ENABLE
if ((suspend
!= NUSE_NO_SUSPEND) &&
(suspend != NUSE_SUSPEND))
{
return NUSE_INVALID_SUSPEND;
}
#else
if (suspend != NUSE_NO_SUSPEND)
{
return NUSE_INVALID_SUSPEND;
}
#endif
#endif
此参数检查可能导致从API函数调用返回错误代码。这些都是NUSE_INVALID_xxx形式的负值(例如NUSE_INVALID_POINTER)–NUSE_codes.h中包含一整套定义。
可以包含额外的应用程序代码(可能是有条件地编译)来处理这些错误值,但是最好使用现代嵌入式调试器的数据监视工具来检测它们。
参数检查会导致内存开销(额外的代码)和运行时性能,因此它的使用有一定的侵入性。由于开发人员可以使用Nucleus SE的完整源代码,如果需要绝对精度,则可以在生产代码上“手动”进行检查和调试。
任务堆栈检查
只要Run-to-Completion调度器不在使用,Nucleus SE就有一个可用的任务堆栈检查工具,它与Nucleus RTOS中提供的类似,并提供剩余堆栈空间的指示。这个API调用–NUSE_Task_Check_Stack()–在上一篇文章中有详细描述。本文后面的“用户诊断”部分将讨论堆栈错误检查的一些想法。
版本信息
Nucleus RTOS和Nucleus SE有一个API函数,它只返回关于内核的版本/版本信息。
Nucleus RTOS API Call
Service call prototype:
CHAR *NU_Release_Information(VOID);
Parameters:
None
Returns:
Pointer to NULL-terminated version string
Nucleus SE API Call
This API call supports the key functionality of the Nucleus RTOS API.
Service call prototype:
char *NUSE_Release_Information(void);
Parameters:
None
Returns:
Pointer to NULL-terminated version string
发布信息的Nucleus SE实现
这个API调用的实现几乎是微不足道的。返回一个指向字符串常量NUSE_Release_Info的指针,该常量在NUSE_globals.c中声明并初始化。
该字符串的形式为Nucleus SE–Xyymmdd,其中:
X是释放状态:A=α;B=β;R=释放
yy是发布的年份
mm是释放月份
dd是释放日
与Nucleus RTOS的兼容性
Nucleus RTOS包括一个用于维护历史日志的可选工具。内核记录各种系统活动的细节。提供API函数使应用程序能够:
启用/禁用历史保存
做历史记录
检索历史记录条目
Nucleus SE不支持此功能。
Nucleus RTOS还包括一些错误管理宏,这些宏执行断言,并提供一种调用用户定义致命错误函数的方法。这些是有条件地包含在OS构建中的。Nucleus SE不支持此类设施。
用户诊断
到目前为止,在本文中,我们已经了解了Nucleus SE本身提供的诊断和错误检查工具。现在是一个很好的机会来考虑如何使用内核提供的工具和/或应用我们对其内部结构和实现的知识来实现用户定义或面向应用程序的诊断。
特定于应用程序的诊断
几乎任何应用程序都可以添加额外的代码来在运行时检查其自身的完整性。使用多任务内核,让一个特定的任务来完成这项工作是非常方便和直接的。显然,对于应用程序非常特殊的诊断不在本系列文章的范围内,但是我们可以考虑一些广泛的想法。
内存检查
内存的正确操作显然对任何基于处理器的系统的完整性至关重要。同样明显的是,灾难性的故障会阻止任何软件的运行,更不用说诊断了。但是有些情况下会发生一定程度的失败,这是一个主要的问题,但并不能完全阻止代码的执行。测试内存是一个相当复杂的主题,这远远超出了本文的范围,所以我只能给出一些一般性的想法。
在RAM中,最常见的两种故障是:“卡住的位”—一个位的值为零或一,无法更改;另一个是“串扰”,相邻的位相互干扰。它们都可以通过依次向每个RAM位置写入和读回适当的测试模式来进行测试。有些测试实际上只能在启动时执行,甚至在建立堆栈之前;例如,“移动1”测试,即内存中的每个位都设置为1,并且每隔一位进行检查以确保其为零。其他逐字节模式测试可以动态执行,只要确保在RAM位置损坏时不会发生上下文切换。使用Nucleus SE critical部分来定义NUSE_CS_Enter()和NUSE_CS_Exit()宏,这两个宏是在NUSE_types.h中定义的,使用这些宏是直接和可移植的。
各种类型的ROM也会偶尔出现故障,但软件所能做的检查是有限的。生成代码时生成的校验和将很有用。这可以在启动时检查,也可以在运行时检查。
内存寻址逻辑故障会影响ROM和RAM。可以设计一个专门针对这一点的测试,但它最有可能出现在上述其他测试中。
外围设备检查
在CPU之外,外围电路可能会出现故障。当然,这将有很大的不同,从一个系统到另一个,但这是非常常见的设备有一些手段来验证其完整性与诊断软件。例如,一条通信线路可能具有回送模式,由此写入的任何数据都会立即返回。
看门狗服务
嵌入式系统设计者通常包括一个“看门狗”电路。这是一种外围设备,它可以中断CPU并期望得到迅速的响应,或者(更好)需要由软件启动的周期性访问。在这两种情况下,看门狗“咬人”的常见结果是系统重置。
在多任务环境中有效地使用看门狗是一项挑战。只需从一个任务中对其进行维护,就可以确认此特定任务正在运行。解决这一问题的一种方法是实现一个“主管任务”—本文后面将介绍这方面的一个示例。
堆栈溢出检查
除非您选择Run to Completion scheduler,否则Nucleus SE应用程序将为每个任务包含一个堆栈。这些堆栈的完整性是至关重要的,但是RAM可能是有限的,因此获得最佳大小是至关重要的。静态地预测每个任务的堆栈需求是可能的,但非常困难,它需要有足够的容量来满足最嵌套的函数调用和最需要堆栈的中断服务例程的需求。一种更简单的方法是执行详尽的运行时测试。
堆栈验证大致有两种方法。如果使用了一个复杂的嵌入式软件调试器,则可以监视堆栈边界,并检测到任何越界行为。Nucleus SE堆栈的位置和大小可以通过ROM中的全局数据结构轻松访问:NUSE_Task_Stack_Base[]和NUSE_Task_Stack_size[]。
另一种方法是执行运行时测试。通常的方法是在每个堆栈的末尾添加“保护字”–通常这些是每个堆栈数据区域中的第一个位置。这些字被初始化为可识别的非零值。然后,诊断例行程序/任务检查它们是否已更改并采取适当的措施。保护字的过度写入并不意味着堆栈实际上已经溢出,而是表示堆栈即将溢出;因此,软件可能会继续执行足够长的时间来采取纠正措施或警告用户。
监督任务
尽管Nucleus SE不保留16个任务中的任何一个供自己使用,但是用户可以选择将一个任务专用于诊断。这可能是一个低优先级的任务,它只利用任何“空闲”的CPU时间;也可能是一个高优先级的任务,偶尔会运行很短的时间,从而确保总是定期执行诊断。
下面是一个示例的功能:
主管任务的信号标志用于监视系统中六个关键任务的运行。这些任务都使用一个特定的标志(位0到位5),并且需要定期设置它。主管任务清除所有标志,然后将自身挂起一段时间。当它恢复时,它希望通过设置适当的标志来“签入”所有六个任务;它寻找与值b00111111(来自nuse_binary.h)的完全匹配。如果满足此条件,它将清除标志并再次挂起。如果没有,它将调用关键错误处理例程,例如,该例程可能执行系统重置。
另一种实现可能使用了事件标志组。如果在应用程序的其他地方没有使用信号(因为所有任务都会产生RAM开销),如果将事件标志用于其他目的,则更是如此。
跟踪和分析
尽管许多现代嵌入式软件调试器都是可定制的,并且可以使其具有“实时操作系统意识”,但调试多线程应用程序仍然具有挑战性。一种广泛使用的方法是执行后分析(post-execution profiling),其中(RTOS)代码被检测,以便对其操作的详细审计进行回顾性分析。通常,实施此类设施涉及两个部分:
附加代码被添加到RTOS(即,它被“检测”)以记录活动。通常,这将被包含在预处理器指令中,以便于条件编译。当发生重要事件(如API调用或上下文切换)时,此代码将存储一些字节的信息。这些信息应该是:
–当前地址(PC)。
–当前任务ID(索引)。
任何其他相关对象的索引。
–指示所执行操作的代码。
专门用于将配置文件信息的缓冲区清空到外部存储器(通常是主机)的任务。
分析这些捕获的数据也需要一些工作,但这可能与使用Excel电子表格一样简单。