高级核心应用程序设计建议
重要
这是 Azure Sphere(旧版)文档。 Azure Sphere(旧版)将于 2027 年 9 月 27 日停用,用户此时必须迁移到 Azure Sphere(集成)。 使用位于 TOC 上方的版本选择器查看 Azure Sphere(集成)文档。
若要在坚实的基础之上构建高级 (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): 对于特定方案,例如当阻止调用不可避免时,使用线程可能很有用,尽管通常这些方案将生存期有限,并且应限定为特定任务。 例如,鉴于 Azure Sphere OS(正在运行 Linux)不会向 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 示例的主要代码中找到此方法的示例。
- 激活机制(例如,通过全局变量),以停止执行(或绑定到)网络通信的 HL 核心应用程序中的所有其他任务,以优化资源消耗,直到重新建立连接。
- cURL 最近更新了回调行为和最佳做法。 尽管 Azure Sphere 已努力确保旧版 cURL 行为继续按预期工作,但建议遵循使用 curl_multi 时的安全性和可靠性的最新指南,因为使用递归回调可能会导致意外崩溃、连接中断和潜在的安全漏洞。 如果 TimerCallback 触发超时为 0 毫秒,则将其视为 1 毫秒的超时,以避免递归回调。 请确保在调用curl_multi_add_handle后至少显式调用curl_multi_socket_action一次。
除了前面的建议,还应考虑以下电源管理方案:
- 发送数据后,关闭 Azure Sphere 芯片。 有关详细信息,请参阅 管理 Azure Sphere 设备的 Power Down 状态。
- 由于多个问题可能会导致长时间的退避超时,因此,跟踪总运行时间并将关闭计时器设置为合理的限制至关重要,以免在由于外部中断或其他超出应用程序控制因素而无法连接的情况下耗尽电池。
- 在控制服务中断期间的连接监视时,Wi-Fi 光纤可以通过禁用
wlan0
网络接口(请参阅 Networking_SetInterfaceState)并等待,直到下次检查连接,从而节省大约 100mW。
内存管理和使用情况
在内存受限的平台上,执行频繁内存分配和取消分配的应用程序可能会导致 OS 的内存管理难以提高效率,从而导致过多的碎片和内存耗尽。具体而言,在 Azure Sphere MT3620 上,这可能会导致内存不足的情况,这些条件可能会触发 Azure Sphere OS 的 cgroup OOM 杀手 启动。
可以理解的是,应用程序通常是从初始概念证明开始开发的,它变得更加全面,具有渐进式版本所需的功能,最终忽略了最初包含的次要功能。 以下是在字段中分析的许多方案证明有效的建议和优化:
- 特别是在使用大量内存的 HL 核心应用程序中,必须通过 Azure Sphere API 跟踪应用程序内存使用情况,如“确定运行时应用程序 RAM 使用情况”中所述。 通常,这是在 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 中的功能,请参阅以下文章: