RTOS 诊断和错误检查
扫描二维码
随时随地手机看文章
错误处理不太可能成为任何用于嵌入式系统应用的操作系统的主要功能。这是资源限制的必然结果——所有嵌入式系统都有某种限制。这也是合乎逻辑的,因为只有有限数量的嵌入式系统有机会像桌面系统一样运行——即为用户提供在发生某些异常事件时决定下一步做什么的机会。
在 Nucleus SE 中,错误检查大致有三种类型:
·
对所选配置进行“健全性检查”的设施——只是为了确保所选选项是一致的
·
·
可选包含代码来检查运行时行为
·
·
特定的 API 函数有助于设计更强大的代码
·
本文将介绍这些内容以及一些关于用户实现的诊断的想法。
配置检查
Nucleus SE 的设计非常便于用户配置,因此可以根据需要进行定制,以充分利用可用资源。这种可配置性是一个挑战,因为选项的数量以及它们之间的相互依赖性非常大。正如之前许多文章中所述,Nucleus SE 的大多数用户配置都是通过 在文件nuse_config.h中设置#define常量来执行的。
为了帮助识别配置错误,包含了一个文件 - nuse_config_check.h (即通过#include 到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
执行的检查包括以下内容:
·
验证已配置至少一个(但不超过十六个)任务
·
·
确认所选的 API 函数与所选的调度程序或其他选项不一致
·
·
验证是否已指定不超过 16 个其他内核对象的实例
·
·
确认没有为根本没有实例化的对象选择 API 函数
·
·
确保在未启用这些功能时不选择信号和系统时间的 API 函数
·
·
验证所选调度程序类型和相关选项
·
在所有情况下,检测到错误都会导致编译#error 语句。这通常会导致编译以指定的消息终止。
此文件不会使创建不合逻辑的配置变得不可能,但会使这种配置不太可能发生。
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 的完整源代码可供开发人员使用,因此如果需要绝对的精度,可以“手动”对生产代码进行检查和调试。
任务堆栈检查
只要未使用“运行至完成”调度程序,Nucleus SE 就会提供任务堆栈检查功能,该功能与 Nucleus RTOS 中提供的类似,并提供剩余堆栈空间的指示。此 API 调用 - NUSE_Task_Check_Stack() - 在上一篇文章中进行了详细描述。本文后面的“用户诊断”部分将讨论有关堆栈错误检查的一些想法。
版本信息
Nucleus RTOS 和 Nucleus SE 有一个 API 函数,它简单地返回有关内核的版本/发布信息。
Nucleus RTOS API 调用
服务调用原型:
字符*NU_Release_Information(VOID);
参数:
没有任何
返回:
指向以 NULL 结尾的版本字符串的指针
Nucleus SE API 调用
此 API 调用支持 Nucleus RTOS API 的关键功能。
服务调用原型:
char *NUSE_Release_Information(void);
参数:
没有任何
返回:
指向以 NULL 结尾的版本字符串的指针
Nucleus SE 实施发布信息
这个API调用的实现几乎很简单。返回一个指向在nuse_globals.c中声明和初始化的字符串常量NUSE_Release_Info的指针。
该字符串采用 Nucleus SE – Xyymmdd的形式,其中:
X 是发布状态:A = alpha;B = beta;R = 已发布
yy 是发布年份
mm 是发布月份
dd 是发布日期
与 Nucleus RTOS 的兼容性
Nucleus RTOS 包含一个可选功能,用于维护历史记录。内核记录各种系统活动的详细信息。提供 API 函数以使应用程序能够:
·
启用/禁用历史记录保存
·
·
记录历史
·
·
检索历史记录条目
·
Nucleus SE 不支持此功能。
Nucleus RTOS 还包括一些错误管理宏,它们执行断言并提供调用用户定义的致命错误函数的方法。这些宏有条件地包含在 OS 构建中。Nucleus SE 不支持此类功能。
用户诊断
到目前为止,我们在本文中研究了 Nucleus SE 本身提供的诊断和错误检查功能。现在是一个很好的机会来考虑如何使用内核提供的功能和/或应用我们对其内部结构和实现的了解来实现用户定义或面向应用程序的诊断。
特定应用诊断
几乎任何应用程序都可以添加额外的代码来在运行时检查其自身的完整性。使用多任务内核,让特定任务执行这项工作非常方便和直接。显然,特定于应用程序的诊断不在本系列文章的讨论范围内,但我们可以考虑一些广泛的想法。
内存检查
内存的正确运行显然对任何基于处理器的系统的完整性都至关重要。同样明显的是,灾难性故障会阻止任何软件(更不用说诊断程序)运行。但在某些情况下,一定程度的故障会发生,这是一个主要问题,但不会完全阻止代码执行。测试内存是一个相当复杂的主题,远远超出了本文的范围,所以我只能给出一些一般性的想法。
在 RAM 中可能发生的两个最常见故障是:“卡住位”——某个位的值为零或一,无法更改;以及“串扰”——相邻位相互干扰。可以通过依次将适当的测试模式写入和读回每个 RAM 位置来测试这些故障。有些测试实际上只能在启动时执行,甚至在建立堆栈之前;例如,“移动位”测试,其中内存中的每个位都设置为 1,并检查其他每个位以确保其为零。其他逐字节模式测试可以即时执行,只要您确保在 RAM 位置损坏时不会发生上下文切换。使用 Nucleus SE 临界区分隔宏NUSE_CS_Enter() 和NUSE_CS_Exit()在nuse_types.h中定义,简单且可移植。
各种类型的 ROM 也有可能偶尔出现故障,但软件能够进行的检查有限。在构建代码时生成的校验和会很有用。可以在启动时检查,也可以在运行时检查。
内存寻址逻辑故障会影响 ROM 和 RAM。可以设计专门针对此问题的测试,但最有可能在上述其他测试中出现。
外围设备检查
除了 CPU 之外,外围电路也有可能出现故障。当然,这种情况在不同系统之间存在很大差异,但设备通常都有一些方法可以通过诊断软件来验证其完整性。例如,通信线路可能具有环回模式,任何写入该线路的数据都会立即返回。
看门狗服务
嵌入式系统设计人员通常会使用“看门狗”电路。这是一种外围设备,它要么中断 CPU 并期望得到及时响应,要么(更确切地说)需要软件发起定期访问。无论是哪种情况,看门狗“咬人”的常见结果是系统重置。
在多任务环境中有效使用看门狗是一项挑战。仅通过一项任务对其进行维护只能确认该特定任务正在运行。解决此问题的一种方法是实施“监控任务”——本文后面将介绍此示例。
堆栈溢出检查
除非您选择“运行至完成”调度程序,否则 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 开销),则这种方法是有意义的,如果事件标志用于其他目的,则这种方法更有意义。
跟踪和分析
尽管许多现代嵌入式软件调试器都具有很高的可定制性,并且可以“感知 RTOS”,但调试多线程应用程序仍然具有挑战性。一种广泛使用的方法是执行后分析,其中对 (RTOS) 代码进行检测,以便可以回顾性地分析其操作的详细审计。通常,实现这样的功能涉及两个组件:
1.
向 RTOS 添加附加代码(即“仪表化”)以记录活动。通常,这将包含在预处理器指令中以方便条件编译。当发生重大事件(如 API 调用或上下文切换)时,此代码会存储几个字节的信息。这些信息包括以下内容:
2.
– 当前地址(PC)。
– 当前任务 ID(索引)。
– 所涉及的任何其他对象的索引。
– 指示已执行操作的代码。
3.
专门用于将配置文件信息缓冲区清空到外部存储器(通常是主机)的任务。
4.
这些捕获数据的分析也需要一些工作,但这可能就像使用 Excel 电子表格一样简单。