线程安全和移植
关于这份文件
本文档介绍了 Arm Mbed OS RTOS 和线程安全机制,然后讨论将它们移植到新目标。
RTOS
Mbed OS 5 中引入的主要改进之一是基于实时操作系统(RTOS)的新编程模型。一些早期版本的 Arm Mbed 已经为 RTOS 提供了可选支持。在版本 5 中,RTOS 支持是平台的标准功能,因此开发人员可以利用基于多线程的更灵活的编程模型。
与任何多线程环境一样,Mbed 开发人员需要使用各种同步原语来确保其代码不包含竞争条件或其他并发问题。他们还需要了解 Mbed OS 5 API 在使用它们时提供的线程安全性。这对于响应硬件中断服务例程(ISR)而运行的代码尤其重要,因为硬件中断服务例程需要仔细设计,以免损害整个系统的线程安全性。
Mbed OS 库包含内部同步,以提供各种级别的线程安全性。本文档描述了 Mbed OS 5 为构建线程安全应用程序提供的机制。
线程安全
同步级别
Mbed OS 5 中的不同组件提供不同级别的同步:
- 中断安全 - 可以安全地使用多个线程和中断;操作以原子方式或在关键部分完成。从中断和线程使用时,行为已明确定义。
- 线程安全 - 可安全地使用多个线程;操作受 RTOS 原语保护,可以从多个线程使用,但如果从中断服务程序使用,则会引起问题。
- 不受保护 - 操作不能防止并发访问,需要在外部同步。如果从多个线程调用而没有其他形式的同步,则数据可能会损坏并且行为未定义。
提示: API 参考指示每个功能的同步级别。
标准库
使用完整版标准库时,所有受支持的工具链都是线程安全的:
- 多线程支持:
- GCC Newlib - GNU Arm 嵌入式工具链。
- IAR 标准库 - IAR 工具链。
- ARMCC 标准库 - Arm 工具链。
- 仅支持单线程:
- Newlib Nano - GNU Arm 嵌入式工具链。
- Micro ARMCC - uARM 工具链。
注意: GCC 和 ARMCC 提供了较小的库变体。这些较小的版本不是线程安全的,使用它们的项目应始终只使用一个线程。
驱动程序
大多数驱动程序是线程安全下面列出了一些值得注意的例外;检查相关的手册页或 doxygen,了解有关所用驱动程序的更多具体细节。
中断安全的驱动程序:
- DigitalIn, DigitalOut, DigitalInOut。
- InterruptIn。
- PortIn, PortOut, PortInOut。
- PwmOut。
- Ticker, TimerEvent, Timeout。
- Timer。
未受保护的驱动程序:
- SPISlave。
- I2CSlave。
HAL C API
HAL C API 是 Mbed OS 5 的移植层,不是线程安全的。开发人员通常不应直接使用此 API,而应使用更高级别的驱动程序和库。如果直接编程到 HAL C API,则您有责任将操作与适当的机制(例如互斥锁)同步。
同步机制
Mutex
Mbed RTOS C++ API 提供了互斥锁类,它是用于使代码线程安全的最简单的基元之一。使用互斥锁是保护对象数据的推荐方法,并且通过使用互斥锁使大多数常见的 Mbed API 成为线程安全的。
但是,有时简单的互斥锁不是实现线程安全的适当机制。特别是,不应在中断服务例程(ISR)中使用互斥锁。使用 ISR 的正确方法是不让它们在响应中断时进行任何实际处理。相反,中断应该向线程发送事件或消息;然后中断完成,线程被重新安排,随后,它将执行处理的线程。这允许线程在需要进行处理时安全地获取互斥锁。
RTOS 提供了几种将中断处理移动到线程上的机制。这些包括但不限于:
注意: 在 Mbed OS 5 中,如果您尝试在中断中使用互斥锁,则不会发生任何事情;无论锁是否实际空闲,锁定互斥锁的尝试都会立即成功。换句话说,如果在中断中获得互斥锁,则可以破坏线程安全机制并将竞争条件引入其他安全的代码段。未来版本的 Mbed OS 将提供警告并最终防止这种情况发生。
有关更多信息,请参阅 rtos/Mutex.h。
Atomics
Mbed OS 提供原子功能,使代码中断安全。如果必须从中断修改对象或数据结构,则可以使用这些原子函数来同步访问。
有关更多信息,请参阅 platform/critical.h。
关键部分
关键部分禁用中断以提供对资源的不间断访问,因此您可以使用关键部分来使代码中断安全。但是,如果可以,应该避免使用关键部分,因为它们必须快速执行,否则会导致系统不稳定。
注意:
- 不要在关键部分内执行耗时的操作。这将对整个系统的时序产生负面影响,因为在关键部分期间禁用所有中断。
- 不要在关键部分内调用任何标准的 lib 或 RTOS 函数;它可能导致硬故障,因为 RTOS 执行 SVC 调用。
有关更多信息,请参阅 platform/critical.h。
主要的 Mbed OS 库
- 网络套接字 API - 线程安全: 公共呼叫受互斥锁保护。
- Nanostack - 不受保护: 通常,我们建议 Mbed 开发人员通过 Network Socket API 使用我们的 6LoWPAN 栈。如果您希望直接使用 Nanostack,您需要了解它如何使用线程。Nanostack 的核心运行在一个 tasklet 机制上,安排在一个底层的 OS 线程上。希望直接致电 Nanostack 的开发人员必须创建自己的 tasklet 并从那里进行 Nanostack 调用。因为只有一个 OS 线程服务于所有 tasklet,所以不需要在 tasklet 之间进一步同步。请参阅 6LoWPAN 文档。
mbed-tls
- 不受保护: 只要对其操作的对象进行了适当的保护,函数调用对任何线程都是安全的 - 请参阅 文档。- Mbed 客户端 - 线程安全: 公共呼叫受互斥锁保护。
- BLE - 不受保护: BLE 的预期用例是在一个线程上运行并将所有事件序列化到该线程。这类似于 Nanostack 使用线程的方式。我们提供了许多示例,展示了如何以线程安全的方式使用 BLE,包括 Eddystone Service。
编写线程安全代码时的其他注意事项
删除对象时,同步不会提供保护;删除对象时,也会删除保护它的互斥锁。这意味着删除对象时,必须确保使用它完成所有其他线程。
您需要使用相同的引脚同步对来自两个对象的硬件的访问。例如,如果在同一引脚上创建了两个 Serial 实例,则同时写入两个对象可能会导致不稳定。
移植时的注意事项
将新平台移植到 Mbed OS 5 几乎与 Mbed 2 中的相同。通常,在 C HAL 层下运行的驱动程序不需要同步机制,因为这已经在更高级别提供。唯一的例外是函数 port_read,port_write,gpio_read 和 gpio_write,它们应该使用特定于处理器的 set 和 clear 寄存器,而不是执行读 - 修改 - 写序列。有关更多信息,请参阅 示例。
进一步阅读
有关更多信息,请参阅 CMSIS-RTOS 教程。