高级核心应用程序设计建议

若要在坚实的基础上构建高级 (HL) 核心应用程序,应使用基本的最佳做法。 以下是最相关的内容:

高级 (HL) 核心应用程序在 Azure Sphere OS 上运行容器化。 在对客户解决方案进行代码和设计评审期间,我们发现了 HL 核心应用程序的几个典型问题。 本主题讨论有关解决这些问题的设计改进建议。

常规基础知识

若要在坚实的基础上生成 HL 核心应用程序,应使用基本的最佳做法。 以下是最相关的内容:

  • 初始化和终止: 始终确保处理 Azure Sphere OS 的 SIGTERM 信号,并正确初始化和销毁所有处理程序, (例如在退出时) 外围设备的处理程序(在发生故障或出错时)。 有关详细信息,请参阅 初始化和终止 以及有关 终止信号的 GNU 文档。
  • 始终使用退出代码: 例如,确保 HL 核心应用程序在退出或崩溃时始终提供有意义的返回代码 (使用 SIGTERM 处理程序) 对于正确诊断设备的行为(尤其是从设备的故障转储遥测中)至关重要。 有关详细信息,请参阅退出代码和收集和解释错误数据
  • 确保失败情况始终导致应用程序退出或崩溃,而不是死锁状态: 精心策划的故障恢复逻辑可能会适得其反,因为它可能会引入 bug 或行为,从而导致死锁或难以诊断的状态。 设计良好的 Azure Sphere 应用程序应始终倾向于使用非零退出代码 (崩溃或退出,) 潜在的死锁情况,因为这会导致两者:
    • 错误遥测,启用此问题诊断
    • 有可能立即恢复到工作状态,因为 Azure Sphere OS 将重启应用程序
  • 错误处理和日志记录: 精确的错误处理和日志记录是高质量应用程序开发的核心。 快速功能实现可以一直隐藏在代码层中,然后随着应用程序全面发展而构建起来。 有关最佳做法的详细信息,请参阅 错误处理和日志记录
  • 使用系统计时器作为监视器: 最重要的最佳做法之一是实现“看门程序计时器”回调 (与裸机 MCU 中提供的硬件回调非常类似,) 跟踪关键应用程序状态、检测死锁并采取相应的 (操作,例如退出和发送遥测) 。 有关详细信息,请参阅 使用系统计时器作为监视器
  • 切勿部署针对 beta 版本工具集构建的生产应用程序: 不建议使用 beta 版本工具集,因为无法保证 beta 子集不会在后续 OS 版本中更改。 Beta 版工具集仅用于在正式 SDK 发布之前测试新功能。

处理并发

  • 尽可能使用 EventLoop: 线程和同步对象 (即互斥体、信号灯等) 用于完成几乎并发的任务,但在嵌入式系统中,这些在系统资源使用方面成本高昂。 因此,若要提高性能,请考虑使用 epolls 而不是线程,以用于那些不严格时间关键且对相互阻塞不敏感的任务。 有关如何使用 EventLoop 监视和调度事件的信息,请参阅 Applibs eventloop.h,包括相关示例。
  • 查看并发任务的效率: 请务必确保在 epoll 回调中将阻止操作和超时保持在最小,否则所有其他 epoll 回调都将受到影响。
  • 何时使用线程 (pthread) : 对于特定方案(例如,当阻止调用不可避免时),使用线程可能很有好处,尽管这些方案通常的生存期有限,并且应限定为特定任务。 例如,鉴于运行 Linux) 的 Azure Sphere OS (不会向 HL 核心应用程序公开 IRQ, (这仅适用于 RT 核心应用) ,因此结合使用 epoll 和 pthread 任务可能是处理最佳方法,例如,从 Internet 下载数据时进行下游串行通信。

重要

Azure Sphere OS 可能会中断及时操作,尤其是在执行设备证明、检查更新或上传遥测数据时。 对于时间关键型控制任务,请考虑将它们移动到 M4 核心,并通过核心间邮箱与适当的协议进行协调。 有关详细信息,请参阅 核心间通信示例

除了这些建议,请查看有关 异步事件和并发的 Azure Sphere 文档。

连接监视

设计良好的高级 (HL) 核心应用程序必须实现适当的 连接运行状况检查 任务,该任务应基于可靠的状态机,该状态机定期检查 Internet 连接状态 (,例如,使用 epoll 计时器) 利用 Networking_IsNetworkingReady API。 在某些情况下,可以使用 Networking_GetInterfaceConnectionStatus 函数,因为它提供了与 HL 核心应用程序可用于更好地处理其状态的特定网络接口相关的连接状态的更深入的状态,尽管这样做会付出代价,因为不建议每 90 秒调用一次更频繁。

状态机回调通常应具有以下属性:

  • 尽快执行。
  • 必须根据特定的应用程序方案和总体解决方案要求 (例如固定时间、增量延迟等) ,仔细设计其轮询间隔。
  • 检测到断开连接后,调用 Networking_GetInterfaceConnectionStatus 一次以记录特定网络接口的状态可能很有用,该状态可用于诊断问题,并通过 UI ((如 LED、显示器、终端) )通知用户。 可以在 Azure Sphere DHCP 示例的main代码中找到此方法的示例。
  • (激活机制,例如,通过全局变量) 来停止 HL 核心应用程序中执行 (或绑定到) 网络通信的其他所有任务,以优化资源消耗,直到重新建立连接。
  • cURL最近更新了回调行为和最佳做法。 虽然 Azure Sphere 已努力确保旧版cURL行为继续按预期工作,但建议在使用curl_multi时遵循安全性和可靠性的最新指南,因为使用递归回调可能会导致意外崩溃、连接中断和潜在的安全漏洞。 如果 TimerCallback 以 0 毫秒的超时值触发,则将其视为 1 毫秒的超时,以避免递归回调。 在调用curl_multi_add_handle后,请确保至少显式调用curl_multi_socket_action一次。

除了上述建议之外,还应考虑以下电源管理方案:

  • 发送数据后关闭 Azure Sphere 芯片的电源。 有关详细信息,请参阅 管理 Azure Sphere 设备的关机状态
  • 由于长时间的指数退避超时可能会导致几个问题,因此跟踪总运行时间并将关机计时器设置为合理的限制至关重要,以免在由于外部中断或应用程序无法控制的其他因素而无法连接的情况下耗尽电池。
  • 在中断期间控制连接监视时,Wi-Fi 收发器可以通过禁用wlan0网络接口 (看到Networking_SetInterfaceState并等待下一检查再次连接,从而关闭电源,从而节省大约 100mW。

内存管理和使用情况

在内存受限的平台上,执行频繁的内存分配和取消分配的应用程序可能会导致操作系统的内存管理难以高效运行,从而导致过度碎片和内存耗尽。特别是在 Azure Sphere MT3620 上,这可能会导致内存不足的情况,从而触发 Azure Sphere OS 的 cgroup OOM 杀手 启动。

可以理解的是,应用程序通常是从初始概念证明开始开发的,随着渐进式版本所需的功能变得更加全面,最终忽略了最初包含的次要功能。 以下是已证明对现场分析的许多方案有效的建议和优化:

  • 特别是在大量使用内存的 HL 核心应用程序中,必须通过 确定运行时应用程序 RAM 使用情况中所述的 Azure Sphere API 来跟踪应用程序内存使用情况。 通常,这是在 epoll-timer 监视器中实现的,应用程序会相应地对意外的内存使用量做出反应,以便以合理的方式重启:例如,使用相应的退出代码退出。

    一些客户和合作伙伴发现使用 Azure Sphere 库中发布的堆跟踪器内存跟踪实用工具非常有用。 此库以透明方式链接到现有的 HL 核心应用程序,并跟踪内存分配及其相关指针,从而简化对大多数内存泄漏和指针滥用情况的检测。

重要

这种做法可以减少显然无法解释的设备无响应或经常从现场报告的故障。 此类故障通常是由 HL 核心应用程序未正确处理的内存泄漏或溢出引起的,导致 OOM 杀手关闭应用程序的进程。 这与阻止 Azure Sphere OS 发送遥测数据的不良连接性可能会导致潜在的现场事件,因为只能通过拉取 Azure Sphere OS 的诊断日志来检测诊断。

  • 在内存受限的平台上,通常最好尽可能避免动态内存分配,尤其是在经常调用的函数中。 这将大大减少堆的内存碎片和后续堆分配失败的可能性。 另请考虑从重复分配临时工作缓冲区到直接访问堆栈 (的模式转变,以获取合理大小的变量) 或全局分配的缓冲区,这些缓冲区在溢出时通过 realloc) 增加大小 ( (请参阅 动态容器和缓冲区) 。 如果要求卸载内存,请考虑利用 M4 核心上未使用的内存 (请参阅 Azure Sphere) 上的可用内存 (每个内存有 256KiB),以及用于数据缓存的轻型 RT 核心应用程序。 最终可以使用外部 SD 卡或闪存。 可以在以下存储库中找到示例:

遵循上述建议还有助于估算和保留 HL 核心应用程序在其生命周期内以满载容量工作所需的内存,同时使你能够更好地估计应用程序的总体内存占用量,以便以后进行设计优化。 有关优化 HL 核心应用程序中的内存使用情况的详细信息(包括 Azure Sphere OS 和 Visual Studio 中的功能),请参阅以下文章: