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)。
新线程的 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
pthread
component. To declare that your component depends onpthread
, 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