大多数 RTOS 提供对服务调用和其他类型错误的监控和报告。尽管人们普遍认为良好的错误处理有助于调试并提高可靠性,但很少有时间这样做。问题是我们已经面临实现主要项目目标的挑战,处理错误是一种不受欢迎的麻烦。这是不幸的,因为良好的错误检测和处理可以减少调试时间并有助于处理交付系统中的问题。
所需要的是将错误和超时管理合并到嵌入式系统中的简单方法。有两种错误报告,本地和中央。由于并非所有 RTOS 都提供后者,让我们从前者开始。
本地错误报告
本地报错基本上有两种方法。第一个直接返回状态信息并将所需结果放入作为参数提供的地址中。例如:
在这种情况下,任务控制块 (TCB) 已被静态定义。OSTaskCreate() 使用它的地址填写 TCB 字段并返回 status = OK。如果发生错误,例如无效的 TCB 地址,则 status 是错误类型。这种方法的缺点是需要一个额外的参数来告诉将结果放在哪里以及需要静态定义 TCB。
第二种方法直接返回结果,并将状态放入当前任务的 TCB 中的一个字段中。例如:
在这种情况下,TCB 是从池中动态分配的,如果成功,则 RTOS 返回其句柄。(句柄是taskA,它是一个指向taskA 的TCB 的指针。)如果不是,它返回NULL。在这种情况下,发生了错误或超时,并且状态已存储在 smx_ct-》err 中,其中 smx_ct 是当前任务,即尝试创建 taskA 的任务。这些方法是等价的,但我更喜欢第二种,因为它更直接。
本地错误和超时处理
这是一种处理本地错误和超时的方法,而不会使主代码复杂化:
在此示例中,while (1) 循环执行主要处理。它在 port_in 交换处等待消息。如果在 TMO 周期内收到一条消息,它会被处理,然后释放,控制返回到消息接收。如果由于错误或超时未收到消息,则控制转到 else 语句。如果发生超时 (SMXE_TMO),则会对其进行处理并控制返回消息接收,除非超时次数过多。否则,控制会跳出 while (1) 循环并转到 switch 语句。在这里,存在所有 smx_MsgReceive() 错误类型的情况,以及超时过多的情况。
错误处理后,taskA_Main() 返回到调度程序,调度程序将其停止。这确保了 taskA 在问题得到解决之前不会造成进一步的损害。一旦问题得到解决,taskA 可以重新启动,它会回到它的主处理循环。在调试时,停止任务有助于诊断问题并决定如何在已发布的系统中修复它。
taskA 也可以在不停止的情况下重新启动,如无效 MCB 情况所示。在这种情况下,消息被丢弃,taskA 返回其主处理循环。重启taskA是错误处理的程度。此外,任务重启后没有中断,因为它之后不会执行任何语句。
此示例说明如何区分超时和错误以及如何区分错误类型。请注意,可能需要通知然后重试的超时处理保留在主 while 循环中,而错误处理在循环之外执行。这使得主循环更容易理解,因为它没有杂乱的错误处理代码。
通过将错误处理代码与主处理代码分开,更容易将时间和精力集中在后者上。在调试期间,switch 语句可能仅用于放置断点的位置。稍后,它可能会用案例充实,如图所示,或者用中央错误处理代替。
中央错误处理
中央错误处理减少了对本地错误处理的需求,如果由 RTOS 提供,则应使用它。每当 RTOS 服务或 RTOS 本身检测到错误时,都会调用 RTOS 错误管理器 EM()。理想情况下,EM() 在系统堆栈中运行,因此处理错误不会导致任务堆栈溢出。这可能是一团糟,因为它是出乎意料的,而且 EM() 不太可能是可重入的。EM() 应该执行以下部分或全部操作:
将错误号加载到全局变量 errno 中。这是系统遇到的最后一个错误。
将错误号加载到当前任务的 tcb.err 字段中。如果在每个 RTOS 服务启动时清除此字段,它将反映此任务使用的最后一个服务中发生的情况。如前所示,这对于本地错误管理是必要的。
增加一个全局错误计数器,errctr。这表明自系统启动以来发生了多少错误。
为错误类型增加一个特定的计数器,errctrs[e]。这有助于确定交付的系统中出现了哪些问题。
将错误信息保存在错误缓冲区 EB 中,例如发生时间、errno 和发生错误的线程。
将错误信息保存在事件缓冲区 EVB 中,以便在使用内核感知插件时可以相对于其他事件显示错误。
调用用户挂钩函数 EMHook() 以添加特定于错误和线程的处理。
如果当前任务损坏,则停止当前任务,如果错误不可恢复,则调用 EMExitHook() 重新启动系统。
EM() 仅在发生错误时才会增加系统开销。因此,可以将大量错误处理放入 EM() 中,而对正常系统操作的影响可以忽略不计。此外,相对于总代码大小,代码大小的增加很小。
决定使用什么
本地错误管理往往会增加主代码的复杂性,使其变得更大、更慢。然而,错误通常可以在调用点得到最有效的处理,在某些情况下,这样做是必不可少的。例如,在网络软件中,输入数据包的空闲块用完可能在重负载条件下是正常的。因此,在本地处理它是必要的。如前所述,这可以在不使主代码严重复杂化的情况下完成。
但是,一旦调试完成,大多数错误将不再发生。超时可能是唯一需要处理的频繁事件,并且可以相当简单地处理它们。因此,在许多系统中,依赖中央错误管理可能是最佳选择,因为它不会增加太多开销,也不会使主代码复杂化。然而,它允许查看错误发生的位置及其频率。
使用 EMHook() 提供了一个中间地带,可以为特定的错误和线程收集额外的信息。然后可以启动错误或线程特定的恢复。
在典型系统中,许多路径流经 RTOS,因此它处于检测和处理错误的良好位置。一般来说,函数的返回值,尤其是 RTOS 服务,不应该在没有检查的情况下使用。不这样做可能会导致严重的故障,并可能为恶意软件提供入口点。完成主要代码后,可以将注意力转向适合已发布系统的错误处理的数量和类型。可能会选择上述方法的某种组合。
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !