POSIX 支持(包括 POSIX 线程支持)
概述
ESP-IDF 基于 FreeRTOS,但提供了一系列与 POSIX 兼容的 API,以便轻松移植第三方代码,例如支持 pthread 常用的 pthread API。
在 ESP-IDF 中,pthread 对 FreeRTOS 中的等效功能进行了包装。pthread API 所需的运行内存或性能开销很低,但 pthread 或 FreeRTOS 的可用功能并非都可以通过 ESP-IDF 的 pthread 支持来实现。
添加标准 pthread.h 头文件后可以在 ESP-IDF 中使用 pthread,该头文件已包含在工具链 libc 中。还有另一个专用于 ESP-IDF 的头文件 esp_pthread.h,其中提供了一些额外的非 POSIX API,以便通过 pthread 使用一些 ESP-IDF 功能。
除了 POSIX 线程,ESP-IDF 还支持 POSIX 消息队列。
C++ 标准库中的 std::thread、std::mutex、std::condition_variable 等功能也是通过 pthread 和其他 POSIX API(利用 GCC libstdc++)实现的。因此,本文档提到的限制条件也同样适用于 C++ 标准库中的等效功能。
如果希望 ESP-IDF 支持某个尚未实现的 API,请 在 GitHub 上发起功能请求 并提供详细信息。
RTOS 集成
与许多使用 pthread 的操作系统不同,ESP-IDF 是一个实时操作系统,具有实时调度程序。这意味着只有当一个更高优先级的任务准备就绪、线程在 OS 同步结构(如 mutex)上发生阻塞、或者线程调用 sleep、vTaskDelay()、usleep 等函数时,线程才会停止运行。
备注
如果调用 C 标准库或 C++ sleep 函数,例如在 unistd.h 中定义的 usleep,那么只有当睡眠时间超过 一个 FreeRTOS 滴答周期 时,任务才会阻塞并让出内核。如果时间较短,线程将处于忙等待状态,不会让步给另一个 RTOS 任务。
备注
POSIX 的 errno 由 ESP-IDF 中的 newlib 提供。因此,配置项 configUSE_POSIX_ERRNO 并未被使用,应该保持禁用状态。
默认情况下,所有 pthread 具有相同的 RTOS 优先级,但可以通过调用 ESP-IDF 提供的扩展 API 对此优先级进行更改。
标准功能
ESP-IDF 中实现了以下标准 API。
请参考 标准 pthread 文档 或 pthread.h,了解每个函数标准参数和行为的详细信息。下文还列出了 pthread API 与标准 API 相比的差异或局限性。
线程 API
- pthread_create()
- attr参数仅支持设置堆栈大小和分离状态,其他属性字段将被忽略。
- 与 FreeRTOS 任务函数不同, - start_routine函数允许返回。如果函数返回,分离类型的线程会被自动删除。而默认的可连接类型线程将被挂起,直到调用- pthread_join()。
 
 
- pthread_join()
- pthread_detach()
- pthread_exit()
- sched_yield()
- pthread_self()
- 如果从不是 pthread 的 FreeRTOS 任务中调用此函数,断言会失败。 
 
 
- pthread_equal()
线程属性
- pthread_attr_init()
- pthread_attr_destroy()
- 此函数不需要释放任何资源,而是将 - attr结构体重置为默认值。其实现与- pthread_attr_init()相同。
 
 
- pthread_attr_getstacksize()/- pthread_attr_setstacksize()
- pthread_attr_getdetachstate()/- pthread_attr_setdetachstate()
Once
- pthread_once()
支持静态初始化常量 PTHREAD_ONCE_INIT。
备注
在使用 pthread 或 FreeRTOS API 创建的任务中都可以调用此函数。
互斥锁
POSIX 互斥锁被实现为 FreeRTOS 互斥信号量(普通类型用于“快速”或“错误检查”互斥锁,递归类型用于“递归”互斥锁),因此与使用 xSemaphoreCreateMutex() 创建的互斥锁具有相同的优先级继承行为。
- pthread_mutex_init()
- pthread_mutex_destroy()
- pthread_mutex_lock()
- pthread_mutex_timedlock()
- pthread_mutex_trylock()
- pthread_mutex_unlock()
- pthread_mutexattr_init()
- pthread_mutexattr_destroy()
- pthread_mutexattr_gettype()/- pthread_mutexattr_settype()
支持静态初始化常量 PTHREAD_MUTEX_INITIALIZER,但不支持其他互斥锁类型的非标准静态初始化常量。
备注
在使用 pthread 或 FreeRTOS API 创建的任务中都可以调用这些函数。
条件变量
- pthread_cond_init()
- attr参数未实现,将被忽略。
 
 
- pthread_cond_destroy()
- pthread_cond_signal()
- pthread_cond_broadcast()
- pthread_cond_wait()
- pthread_cond_timedwait()
支持静态初始化常量 PTHREAD_COND_INITIALIZER。
- pthread_cond_timedwait()超时的分辨率为 RTOS 滴答周期(参见 CONFIG_FREERTOS_HZ)。在请求超时后,超时最多会延迟一个滴答周期。
备注
在使用 pthread 或 FreeRTOS API 创建的任务中都可以调用这些函数。
信号量
ESP-IDF 中实现了 POSIX 未命名信号量,下文介绍了可访问的 API。除非另有说明,否则 ESP-IDF 中未命名信号量的实现遵循 POSIX 标准规定的信号量。
- 
- pshared被忽略。信号量始终可以在 FreeRTOS 任务之间共享。
 
- 
- 如果信号量的值已经是 - SEM_VALUE_MAX,则返回- -1,并将- errno设置为- EAGAIN。
 
- 
- 通过 abstime 传递的时间值将被向上舍入到下一个 FreeRTOS 时钟滴答。 
- 超时实际发生在被舍入到的滴答之后,下一个滴答之前。 
- 在计算超时后,任务有可能被立即抢占(可能性较小),从而延迟下一个阻塞操作系统调用的超时,延迟的时间等于抢占的持续时间。 
 
读/写锁
ESP-IDF 中实现了 POSIX 读写锁规范的以下 API 函数:
- 
- attr参数未实现,将被忽略。
 
支持静态初始化器常量 PTHREAD_RWLOCK_INITIALIZER。
备注
在 pthread 或 FreeRTOS API 创建的任务中都可以调用此函数。
线程特定数据
- pthread_key_create()
- 支持 - destr_function参数。如果线程函数正常退出并调用- pthread_exit(),此参数就会被调用,或者在使用 FreeRTOS 函数- vTaskDelete()直接删除了底层任务时被调用。
 
 
- pthread_key_delete()
- pthread_setspecific()/- pthread_getspecific()
备注
在 pthread 或 FreeRTOS API 创建的任务中都可以调用此函数。当从 FreeRTOS API 创建的任务中调用这些函数时,必须先启用 CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS 配置选项,以确保在删除任务之前清理线程数据。
备注
ESP-IDF 中还有其他的线程本地存储选项,包括性能更高的选项。参见 线程局部存储。
消息队列
消息队列的实现基于 FreeRTOS-Plus-POSIX 项目,ESP-IDF 的文件系统不提供消息队列,不支持消息优先级。
以下 POSIX 消息队列规范中的 API 函数已被实现:
- 
- 除了要符合 POSIX 规范,name参数还有以下额外限制:
- 必须以斜杠开头。 
- 长度不得超过 255 + 2 个字符(包括开头的斜杠,除去终止的空字符)。但 - name的内存是在内部动态分配的,所以名称越短,消耗的内存越少。
 
 
- 除了要符合 POSIX 规范,
- mode参数未实现且被忽略。
- 支持的 - oflags:- O_RDWR、- O_CREAT、- O_EXCL和- O_NONBLOCK。
 
- 
- 不支持消息优先级,因此 - msg_prio未被使用。
 
- 
- 不支持消息优先级,因此 - msg_prio未被使用。
 
- 
- 不支持消息优先级,因此 - msg_prio无效。
 
- 
- 不支持消息优先级,因此 - msg_prio无效。
 
尚未实现 mq_notify() 和 mq_setattr()。
构建
要使用 POSIX 消息队列 API,请在组件的 CMakeLists.txt 文件中添加 rt 作为依赖项。
备注
请注意,如果曾在其他 FreeRTOS 项目中使用过 FreeRTOS-Plus-POSIX,则 IDF 中的包含路径是 POSIX 风格的。因此,应用程序应直接包含 mqueue.h,而不是使用子目录来包含 FreeRTOS_POSIX/mqueue.h。
未实现 API
pthread.h 头文件是一个标准头文件,包含了在 ESP-IDF 中未实现的额外 API 和功能,包括:
- 若调用 - pthread_cancel(),则返回- ENOSYS。
- 若调用 - pthread_condattr_init(),则返回- ENOSYS。
- 若调用 mq_notify(),则返回 - ENOSYS。
- 若调用 mq_setattr(),则返回 - ENOSYS。
其他未列出的 pthread 函数未在 ESP-IDF 中实现,如果从 ESP-IDF 应用程序中直接引用,将产生编译器错误或链接器错误。
ESP-IDF 扩展
在 esp_pthreads.h 头文件中定义的 API esp_pthread_set_cfg() 提供了自定义扩展,能够对后续 pthread_create() 的调用行为进行控制。目前提供以下配置:
- 如果调用 - pthread_create()时未指定默认堆栈大小,可设置新线程的默认堆栈大小(覆盖 CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT)。
- 堆栈内存属性决定用于分配 pthread 堆栈的内存类型。该字段使用 ESP-IDF 堆属性标志,这一标志在 heap/include/esp_heap_caps.h 文件中定义。为了确保分配的内存能够通过 8 位地址访问 (MALLOC_CAP_8BIT),用户必须设置相应的标志,此外也可添加其他自定义标志。用户应当确保选择了正确的堆栈内存属性。了解内存位置的更多信息,请参考 内存属性 文档。 
- 新线程的 RTOS 优先级(覆盖 CONFIG_PTHREAD_TASK_PRIO_DEFAULT)。 
- 新线程的内核亲和性/内核固定(覆盖 CONFIG_PTHREAD_TASK_CORE_DEFAULT)。 
- 新线程的 FreeRTOS 任务名称(覆盖 CONFIG_PTHREAD_TASK_NAME_DEFAULT) 
此配置的作用范围是调用线程或 FreeRTOS 任务,这意味着 esp_pthread_set_cfg() 可以在不同的线程或任务中独立调用。如果在当前配置中设置了 inherit_cfg 标志,那么当一个线程递归调用 pthread_create() 时,任何新创建的线程都会继承该线程的配置,否则新线程将采用默认配置。
应用示例
- system/pthread 演示了如何使用 pthread API 创建线程。 
- cxx/pthread 演示了如何通过线程使用 C++ 标准库函数。 
API 参考
Header File
- This header file can be included with: - #include "esp_pthread.h" 
- This header file is a part of the API provided by the - pthreadcomponent. To declare that your component depends on- pthread, add the following to your CMakeLists.txt:- REQUIRES pthread - or - PRIV_REQUIRES pthread 
Functions
- 
esp_pthread_cfg_t esp_pthread_get_default_config(void)
- Creates a default pthread configuration based on the values set via menuconfig. - 返回:
- A default configuration structure. 
 
- 
esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg)
- Configure parameters for creating pthread. - This API allows you to configure how the subsequent pthread_create() call will behave. This call can be used to setup configuration parameters like stack size, priority, configuration inheritance etc. - If the 'inherit' flag in the configuration structure is enabled, then the same configuration is also inherited in the thread subtree. - 备注 - If cfg->stack_alloc_caps is 0, it is automatically set to valid default stack memory capabilities. If cfg->stack_alloc_caps is non-zero, the developer is responsible for its correctenss. This function only checks that the capabilities are MALLOC_CAP_8BIT, the rest is unchecked. - 备注 - Passing non-NULL attributes to pthread_create() will override the stack_size parameter set using this API - 参数:
- cfg -- The pthread config parameters 
- 返回:
- ESP_OK if configuration was successfully set 
- ESP_ERR_NO_MEM if out of memory 
- ESP_ERR_INVALID_ARG if cfg is NULL 
- ESP_ERR_INVALID_ARG if stack_size is less than PTHREAD_STACK_MIN 
- ESP_ERR_INVALID_ARG if stack_alloc_caps does not include MALLOC_CAP_8BIT 
 
 
- 
esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p)
- Get current pthread creation configuration. - This will retrieve the current configuration that will be used for creating threads. - 参数:
- p -- Pointer to the pthread config structure that will be updated with the currently configured parameters 
- 返回:
- ESP_OK if the configuration was available 
- ESP_ERR_INVALID_ARG if p is NULL 
- ESP_ERR_NOT_FOUND if a configuration wasn't previously set 
 
 
Structures
- 
struct esp_pthread_cfg_t
- pthread configuration structure that influences pthread creation - Public Members - 
size_t stack_size
- The stack size of the pthread 
 - 
size_t prio
- The thread's priority 
 - 
bool inherit_cfg
- Inherit this configuration further 
 - 
const char *thread_name
- The thread name. 
 - 
int pin_to_core
- The core id to pin the thread to. Has the same value range as xCoreId argument of xTaskCreatePinnedToCore. 
 - 
uint32_t stack_alloc_caps
- A bit mask of memory capabilities (MALLOC_CAPS*) to use when allocating the stack. The memory must be 8 bit accessible (MALLOC_CAP_8BIT). The developer is responsible for the correctenss of - stack_alloc_caps.
 
- 
size_t stack_size
Macros
- 
PTHREAD_STACK_MIN