help
Minimum allowed pthread stack size set in attributes passed to pthread_create
+choice ESP32_PTHREAD_TASK_CORE_DEFAULT
+ bool "Default pthread core affinity"
+ default ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY
+ depends on !FREERTOS_UNICORE
+ help
+ The default core to which pthreads are pinned.
+
+config ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY
+ bool "No affinity"
+config ESP32_DEFAULT_PTHREAD_CORE_0
+ bool "Core 0"
+config ESP32_DEFAULT_PTHREAD_CORE_1
+ bool "Core 1"
+endchoice
+
+config ESP32_PTHREAD_TASK_CORE_DEFAULT
+ int
+ default -1 if ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY || FREERTOS_UNICORE
+ default 0 if ESP32_DEFAULT_PTHREAD_CORE_0
+ default 1 if ESP32_DEFAULT_PTHREAD_CORE_1
+
+config ESP32_PTHREAD_TASK_NAME_DEFAULT
+ string "Default name of pthreads"
+ default "pthread"
+ help
+ The default name of pthreads.
+
endmenu
#pragma once
+#include "esp_err.h"
+#include <freertos/FreeRTOSConfig.h>
+
#ifdef __cplusplus
extern "C" {
#endif
/** pthread configuration structure that influences pthread creation */
typedef struct {
- size_t stack_size; ///< the stack size of the pthread
- size_t prio; ///< the thread's priority
- bool inherit_cfg; ///< inherit this configuration further
+ 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.
} esp_pthread_cfg_t;
+/**
+ * @brief Creates a default pthread configuration based
+ * on the values set via menuconfig.
+ *
+ * @return
+ * A default configuration structure.
+ */
+esp_pthread_cfg_t esp_pthread_get_default_config();
+
/**
* @brief Configure parameters for creating pthread
*
free(pthread);
}
-
/* Call this function to configure pthread stacks in Pthreads */
esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg)
{
return ESP_ERR_NOT_FOUND;
}
+static int get_default_pthread_core()
+{
+ return CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT == -1 ? tskNO_AFFINITY : CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT;
+}
+
+esp_pthread_cfg_t esp_pthread_get_default_config()
+{
+ esp_pthread_cfg_t cfg = {
+ .stack_size = CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT,
+ .prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT,
+ .inherit_cfg = false,
+ .thread_name = NULL,
+ .pin_to_core = get_default_pthread_core()
+ };
+
+ return cfg;
+}
+
static void pthread_task_func(void *arg)
{
void *rval = NULL;
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
if (task_arg->cfg.inherit_cfg) {
- /* If inherit option is set, then do a set_cfg() ourselves for future forks */
- esp_pthread_set_cfg(&task_arg->cfg);
+ /* If inherit option is set, then do a set_cfg() ourselves for future forks,
+ but first set thread_name to NULL to enable inheritance of the name too.
+ (This also to prevents dangling pointers to name of tasks that might
+ possibly have been deleted when we use the configuration).*/
+ esp_pthread_cfg_t *cfg = &task_arg->cfg;
+ cfg->thread_name = NULL;
+ esp_pthread_set_cfg(cfg);
}
ESP_LOGV(TAG, "%s START %p", __FUNCTION__, task_arg->func);
rval = task_arg->func(task_arg->arg);
uint32_t stack_size = CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT;
BaseType_t prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT;
+ BaseType_t core_id = get_default_pthread_core();
+ const char *task_name = CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT;
esp_pthread_cfg_t *pthread_cfg = pthread_getspecific(s_pthread_cfg_key);
if (pthread_cfg) {
if (pthread_cfg->prio && pthread_cfg->prio < configMAX_PRIORITIES) {
prio = pthread_cfg->prio;
}
+
+ if (pthread_cfg->inherit_cfg) {
+ if (pthread_cfg->thread_name == NULL) {
+ // Inherit task name from current task.
+ task_name = pcTaskGetTaskName(NULL);
+ } else {
+ // Inheriting, but new task name.
+ task_name = pthread_cfg->thread_name;
+ }
+ } else if (pthread_cfg->thread_name == NULL) {
+ task_name = CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT;
+ } else {
+ task_name = pthread_cfg->thread_name;
+ }
+
+ if (pthread_cfg->pin_to_core >= 0 && pthread_cfg->pin_to_core < portNUM_PROCESSORS) {
+ core_id = pthread_cfg->pin_to_core;
+ }
+
task_arg->cfg = *pthread_cfg;
}
task_arg->func = start_routine;
task_arg->arg = arg;
pthread->task_arg = task_arg;
- BaseType_t res = xTaskCreate(&pthread_task_func, "pthread", stack_size,
- task_arg, prio, &xHandle);
- if(res != pdPASS) {
+ BaseType_t res = xTaskCreatePinnedToCore(&pthread_task_func,
+ task_name,
+ stack_size,
+ task_arg,
+ prio,
+ &xHandle,
+ core_id);
+
+ if (res != pdPASS) {
ESP_LOGE(TAG, "Failed to create task!");
free(pthread);
free(task_arg);
* Stack size of the pthreads
* Priority of the created pthreads
* Inheriting this configuration across threads
+ * Thread name
+ * Core affinity / core pinning.
Example to tune the stack size of the pthread:
{
pthread_t t1;
- esp_pthread_cfg_t cfg;
+ esp_pthread_cfg_t cfg = esp_create_default_pthread_config();
cfg.stack_size = (4 * 1024);
esp_pthread_set_cfg(&cfg);
pthread_t t1;
- esp_pthread_cfg_t cfg;
+ esp_pthread_cfg_t cfg = esp_create_default_pthread_config();
cfg.stack_size = (4 * 1024);
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
--- /dev/null
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(cpp_pthread)
--- /dev/null
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := cpp_pthread
+
+include $(IDF_PATH)/make/project.mk
+
--- /dev/null
+# pthread examples
+
+This example shows how to use the pthread API to create std::threads with different stack sizes, names, priorities and pinned to certain cores.
+
+This example is in C++, contrary to the the normal standard of pure C.
--- /dev/null
+set(COMPONENT_SRCS "cpp_pthread.cpp")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()
--- /dev/null
+#
+# Main Makefile. This is basically the same as a component makefile.
+#
--- /dev/null
+/* pthread/std::thread example
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <iostream>
+#include <thread>
+#include <chrono>
+#include <memory>
+#include <string>
+#include <sstream>
+#include <esp_pthread.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+
+using namespace std::chrono;
+
+const auto sleep_time = seconds
+{
+ 5
+};
+
+void print_thread_info(const char *extra = nullptr)
+{
+ std::stringstream ss;
+ if (extra) {
+ ss << extra;
+ }
+ ss << "Core id: " << xPortGetCoreID()
+ << ", prio: " << uxTaskPriorityGet(nullptr)
+ << ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes.";
+ ESP_LOGI(pcTaskGetTaskName(nullptr), "%s", ss.str().c_str());
+}
+
+void thread_func_inherited()
+{
+ while (true) {
+ print_thread_info("This is the INHERITING thread with the same parameters as our parent, including name. ");
+ std::this_thread::sleep_for(sleep_time);
+ }
+}
+
+void spawn_another_thread()
+{
+ // Create a new thread, it will inherit our configuration
+ std::thread inherits(thread_func_inherited);
+
+ while (true) {
+ print_thread_info();
+ std::this_thread::sleep_for(sleep_time);
+ }
+}
+
+void thread_func_any_core()
+{
+ while (true) {
+ print_thread_info("This thread (with the default name) may run on any core.");
+ std::this_thread::sleep_for(sleep_time);
+ }
+}
+
+void thread_func()
+{
+ while (true) {
+ print_thread_info();
+ std::this_thread::sleep_for(sleep_time);
+ }
+}
+
+esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int prio)
+{
+ auto cfg = esp_pthread_get_default_config();
+ cfg.thread_name = name;
+ cfg.pin_to_core = core_id;
+ cfg.stack_size = stack;
+ cfg.prio = prio;
+ return cfg;
+}
+
+extern "C" void app_main()
+{
+ // Create a thread using deafult values that can run on any core
+ auto cfg = esp_pthread_get_default_config();
+ esp_pthread_set_cfg(&cfg);
+ std::thread any_core(thread_func_any_core);
+
+ // Create a thread on core 0 that spawns another thread, they will both have the same name etc.
+ cfg = create_config("Thread 1", 0, 3 * 1024, 5);
+ cfg.inherit_cfg = true;
+ esp_pthread_set_cfg(&cfg);
+ std::thread thread_1(spawn_another_thread);
+
+ // Create a thread on core 1.
+ cfg = create_config("Thread 2", 1, 3 * 1024, 5);
+ esp_pthread_set_cfg(&cfg);
+ std::thread thread_2(thread_func);
+
+ // Let the main task do something too
+ while (true) {
+ std::stringstream ss;
+ ss << "core id: " << xPortGetCoreID()
+ << ", prio: " << uxTaskPriorityGet(nullptr)
+ << ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes.";
+ ESP_LOGI(pcTaskGetTaskName(nullptr), "%s", ss.str().c_str());
+ std::this_thread::sleep_for(sleep_time);
+ }
+}
\ No newline at end of file