diff --git a/components/pthread/Kconfig b/components/pthread/Kconfig index 349778b2cc..ed49dc5856 100644 --- a/components/pthread/Kconfig +++ b/components/pthread/Kconfig @@ -19,4 +19,31 @@ config PTHREAD_STACK_MIN 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 diff --git a/components/pthread/include/esp_pthread.h b/components/pthread/include/esp_pthread.h index 3ce3703dcc..76f45a32ab 100644 --- a/components/pthread/include/esp_pthread.h +++ b/components/pthread/include/esp_pthread.h @@ -14,6 +14,9 @@ #pragma once +#include "esp_err.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -24,11 +27,22 @@ extern "C" { /** 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 * diff --git a/components/pthread/pthread.c b/components/pthread/pthread.c index a158294a37..1baa035c9b 100644 --- a/components/pthread/pthread.c +++ b/components/pthread/pthread.c @@ -136,7 +136,6 @@ static void pthread_delete(esp_pthread_t *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) { @@ -168,6 +167,24 @@ esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p) 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; @@ -179,8 +196,13 @@ static void pthread_task_func(void *arg) 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); @@ -212,6 +234,8 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 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) { @@ -221,6 +245,25 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 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; } @@ -241,9 +284,15 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 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); diff --git a/docs/en/api-reference/system/esp_pthread.rst b/docs/en/api-reference/system/esp_pthread.rst index 7e43ce365e..ab30dc2411 100644 --- a/docs/en/api-reference/system/esp_pthread.rst +++ b/docs/en/api-reference/system/esp_pthread.rst @@ -8,6 +8,8 @@ This module offers Espressif specific extensions to the pthread library that can * 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: @@ -19,7 +21,7 @@ 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); @@ -50,7 +52,7 @@ The API can also be used for inheriting the settings across threads. For example 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); diff --git a/examples/system/cpp_pthread/CMakeLists.txt b/examples/system/cpp_pthread/CMakeLists.txt new file mode 100644 index 0000000000..06c83d88cd --- /dev/null +++ b/examples/system/cpp_pthread/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) diff --git a/examples/system/cpp_pthread/Makefile b/examples/system/cpp_pthread/Makefile new file mode 100644 index 0000000000..7001b522b6 --- /dev/null +++ b/examples/system/cpp_pthread/Makefile @@ -0,0 +1,9 @@ +# +# 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 + diff --git a/examples/system/cpp_pthread/README.md b/examples/system/cpp_pthread/README.md new file mode 100644 index 0000000000..4f925b7dbb --- /dev/null +++ b/examples/system/cpp_pthread/README.md @@ -0,0 +1,5 @@ +# 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. diff --git a/examples/system/cpp_pthread/main/CMakeLists.txt b/examples/system/cpp_pthread/main/CMakeLists.txt new file mode 100644 index 0000000000..f95c5997c7 --- /dev/null +++ b/examples/system/cpp_pthread/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "cpp_pthread.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/system/cpp_pthread/main/component.mk b/examples/system/cpp_pthread/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/system/cpp_pthread/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/system/cpp_pthread/main/cpp_pthread.cpp b/examples/system/cpp_pthread/main/cpp_pthread.cpp new file mode 100644 index 0000000000..59b0870d07 --- /dev/null +++ b/examples/system/cpp_pthread/main/cpp_pthread.cpp @@ -0,0 +1,112 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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