From e492bda28c27dc1a5ab75987f468323dd81234a1 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 27 Aug 2018 11:32:32 +0800 Subject: [PATCH 1/2] feat(pthread): Bring "pthread" from esp-idf Commit ID: df786128 --- components/pthread/Kconfig | 16 + components/pthread/component.mk | 13 + components/pthread/include/esp_pthread.h | 67 ++ components/pthread/pthread.c | 588 ++++++++++++++++++ components/pthread/pthread_cond_var.c | 200 ++++++ components/pthread/pthread_internal.h | 16 + components/pthread/pthread_local_storage.c | 257 ++++++++ components/pthread/test/component.mk | 9 + components/pthread/test/test_cxx_cond_var.cpp | 48 ++ .../pthread/test/test_cxx_std_future.cpp | 31 + components/pthread/test/test_pthread_cxx.cpp | 132 ++++ .../pthread/test/test_pthread_local_storage.c | 130 ++++ 12 files changed, 1507 insertions(+) create mode 100644 components/pthread/Kconfig create mode 100644 components/pthread/component.mk create mode 100644 components/pthread/include/esp_pthread.h create mode 100644 components/pthread/pthread.c create mode 100644 components/pthread/pthread_cond_var.c create mode 100644 components/pthread/pthread_internal.h create mode 100644 components/pthread/pthread_local_storage.c create mode 100644 components/pthread/test/component.mk create mode 100644 components/pthread/test/test_cxx_cond_var.cpp create mode 100644 components/pthread/test/test_cxx_std_future.cpp create mode 100644 components/pthread/test/test_pthread_cxx.cpp create mode 100644 components/pthread/test/test_pthread_local_storage.c diff --git a/components/pthread/Kconfig b/components/pthread/Kconfig new file mode 100644 index 00000000..3f16c6ff --- /dev/null +++ b/components/pthread/Kconfig @@ -0,0 +1,16 @@ +menu "PThreads" + +config ESP32_PTHREAD_TASK_PRIO_DEFAULT + int "Default task priority" + range 0 255 + default 5 + help + Priority used to create new tasks with default pthread parameters. + +config ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT + int "Default task stack size" + default 3072 + help + Stack size used to create new tasks with default pthread parameters. + +endmenu diff --git a/components/pthread/component.mk b/components/pthread/component.mk new file mode 100644 index 00000000..0dd23948 --- /dev/null +++ b/components/pthread/component.mk @@ -0,0 +1,13 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := . + +COMPONENT_ADD_INCLUDEDIRS := include + +COMPONENT_ADD_LDFLAGS := -lpthread + +ifdef CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK +COMPONENT_ADD_LDFLAGS += -Wl,--wrap=vPortCleanUpTCB +endif diff --git a/components/pthread/include/esp_pthread.h b/components/pthread/include/esp_pthread.h new file mode 100644 index 00000000..56849328 --- /dev/null +++ b/components/pthread/include/esp_pthread.h @@ -0,0 +1,67 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** 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 +} esp_pthread_cfg_t; + +/** + * @brief 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. + * + * @param cfg The pthread config parameters + * + * @return + * - ESP_OK if configuration was successfully set + * - ESP_ERR_NO_MEM if out of memory + */ +esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg); + +/** + * @brief Get current pthread creation configuration + * + * This will retrieve the current configuration that will be used for + * creating threads. + * + * @param p Pointer to the pthread config structure that will be + * updated with the currently configured parameters + * + * @return + * - ESP_OK if the configuration was available + * - ESP_ERR_NOT_FOUND if a configuration wasn't previously set + */ +esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p); + +#ifdef __cplusplus +} +#endif diff --git a/components/pthread/pthread.c b/components/pthread/pthread.c new file mode 100644 index 00000000..9d7822b8 --- /dev/null +++ b/components/pthread/pthread.c @@ -0,0 +1,588 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This module implements pthread API on top of FreeRTOS. API is implemented to the level allowing +// libstdcxx threading framework to operate correctly. So not all original pthread routines are supported. +// Moreover some implemened functions do not provide full functionality, e.g. pthread_create does not support +// thread's attributes customization (prio, stack size and so on). So if you are not satisfied with default +// behavior use native FreeRTOS API. +// +#include +#include +#include +#include "esp_err.h" +#include "esp_attr.h" +#include "rom/queue.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "pthread_internal.h" +#include "esp_pthread.h" + +#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include "esp_log.h" +const static char *TAG = "pthread"; + +/** task state */ +enum esp_pthread_task_state { + PTHREAD_TASK_STATE_RUN, + PTHREAD_TASK_STATE_EXIT +}; + +/** pthread thread FreeRTOS wrapper */ +typedef struct esp_pthread_entry { + SLIST_ENTRY(esp_pthread_entry) list_node; ///< Tasks list node struct. + TaskHandle_t handle; ///< FreeRTOS task handle + TaskHandle_t join_task; ///< Handle of the task waiting to join + enum esp_pthread_task_state state; ///< pthread task state + bool detached; ///< True if pthread is detached +} esp_pthread_t; + +/** pthread wrapper task arg */ +typedef struct { + void *(*func)(void *); ///< user task entry + void *arg; ///< user task argument + esp_pthread_cfg_t cfg; ///< pthread configuration +} esp_pthread_task_arg_t; + +/** pthread mutex FreeRTOS wrapper */ +typedef struct { + SemaphoreHandle_t sem; ///< Handle of the task waiting to join + int type; ///< Mutex type. Currently supported PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_RECURSIVE +} esp_pthread_mutex_t; + + +static SemaphoreHandle_t s_threads_mux = NULL; +static portMUX_TYPE s_mutex_init_lock = portMUX_INITIALIZER_UNLOCKED; +static SLIST_HEAD(esp_thread_list_head, esp_pthread_entry) s_threads_list + = SLIST_HEAD_INITIALIZER(s_threads_list); +static pthread_key_t s_pthread_cfg_key; + + +static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo); + +static void esp_pthread_cfg_key_destructor(void *value) +{ + free(value); +} + +esp_err_t esp_pthread_init(void) +{ + if (pthread_key_create(&s_pthread_cfg_key, esp_pthread_cfg_key_destructor) != 0) { + return ESP_ERR_NO_MEM; + } + s_threads_mux = xSemaphoreCreateMutex(); + if (s_threads_mux == NULL) { + pthread_key_delete(s_pthread_cfg_key); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +static void *pthread_list_find_item(void *(*item_check)(esp_pthread_t *, void *arg), void *check_arg) +{ + esp_pthread_t *it; + SLIST_FOREACH(it, &s_threads_list, list_node) { + void *val = item_check(it, check_arg); + if (val) { + return val; + } + } + return NULL; +} + +static void *pthread_get_handle_by_desc(esp_pthread_t *item, void *desc) +{ + if (item == desc) { + return item->handle; + } + return NULL; +} + +static void *pthread_get_desc_by_handle(esp_pthread_t *item, void *hnd) +{ + if (hnd == item->handle) { + return item; + } + return NULL; +} + +static inline TaskHandle_t pthread_find_handle(pthread_t thread) +{ + return pthread_list_find_item(pthread_get_handle_by_desc, (void *)thread); +} + +static esp_pthread_t *pthread_find(TaskHandle_t task_handle) +{ + return pthread_list_find_item(pthread_get_desc_by_handle, task_handle); +} + +static void pthread_delete(esp_pthread_t *pthread) +{ + SLIST_REMOVE(&s_threads_list, pthread, esp_pthread_entry, list_node); + free(pthread); +} + + +/* Call this function to configure pthread stacks in Pthreads */ +esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg) +{ + /* If a value is already set, update that value */ + esp_pthread_cfg_t *p = pthread_getspecific(s_pthread_cfg_key); + if (!p) { + p = malloc(sizeof(esp_pthread_cfg_t)); + if (!p) { + return ESP_ERR_NO_MEM; + } + } + *p = *cfg; + pthread_setspecific(s_pthread_cfg_key, p); + return 0; +} + +esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p) +{ + esp_pthread_cfg_t *cfg = pthread_getspecific(s_pthread_cfg_key); + if (cfg) { + *p = *cfg; + return ESP_OK; + } + memset(p, 0, sizeof(*p)); + return ESP_ERR_NOT_FOUND; +} + +static void pthread_task_func(void *arg) +{ + esp_pthread_task_arg_t *task_arg = (esp_pthread_task_arg_t *)arg; + + ESP_LOGV(TAG, "%s ENTER %p", __FUNCTION__, task_arg->func); + // wait for start + 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); + } + ESP_LOGV(TAG, "%s START %p", __FUNCTION__, task_arg->func); + task_arg->func(task_arg->arg); + ESP_LOGV(TAG, "%s END %p", __FUNCTION__, task_arg->func); + free(task_arg); + + /* preemptively clean up thread local storage, rather than + waiting for the idle task to clean up the thread */ + pthread_internal_local_storage_destructor_callback(); + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + esp_pthread_t *pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (!pthread) { + assert(false && "Failed to find pthread for current task!"); + } + if (pthread->detached) { + // auto-free for detached threads + pthread_delete(pthread); + } else { + // Remove from list, it indicates that task has exited + if (pthread->join_task) { + // notify join + xTaskNotify(pthread->join_task, 0, eNoAction); + } else { + pthread->state = PTHREAD_TASK_STATE_EXIT; + } + } + xSemaphoreGive(s_threads_mux); + + ESP_LOGD(TAG, "Task stk_wm = %d", uxTaskGetStackHighWaterMark(NULL)); + vTaskDelete(NULL); + + ESP_LOGV(TAG, "%s EXIT", __FUNCTION__); +} + +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) +{ + TaskHandle_t xHandle = NULL; + + ESP_LOGV(TAG, "%s", __FUNCTION__); + if (attr) { + ESP_LOGE(TAG, "%s: attrs not supported!", __FUNCTION__); + return ENOSYS; + } + esp_pthread_task_arg_t *task_arg = malloc(sizeof(esp_pthread_task_arg_t)); + if (task_arg == NULL) { + ESP_LOGE(TAG, "Failed to allocate task args!"); + return ENOMEM; + } + memset(task_arg, 0, sizeof(esp_pthread_task_arg_t)); + esp_pthread_t *pthread = malloc(sizeof(esp_pthread_t)); + if (pthread == NULL) { + ESP_LOGE(TAG, "Failed to allocate pthread data!"); + free(task_arg); + return ENOMEM; + } + uint32_t stack_size = CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT; + BaseType_t prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT; + esp_pthread_cfg_t *pthread_cfg = pthread_getspecific(s_pthread_cfg_key); + if (pthread_cfg) { + if (pthread_cfg->stack_size) { + stack_size = pthread_cfg->stack_size; + } + if (pthread_cfg->prio && pthread_cfg->prio < configMAX_PRIORITIES) { + prio = pthread_cfg->prio; + } + task_arg->cfg = *pthread_cfg; + } + memset(pthread, 0, sizeof(esp_pthread_t)); + task_arg->func = start_routine; + task_arg->arg = arg; + BaseType_t res = xTaskCreate(&pthread_task_func, "pthread", stack_size, + task_arg, prio, &xHandle); + if(res != pdPASS) { + ESP_LOGE(TAG, "Failed to create task!"); + free(pthread); + free(task_arg); + if (res == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) { + return ENOMEM; + } else { + return EAGAIN; + } + } + pthread->handle = xHandle; + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + SLIST_INSERT_HEAD(&s_threads_list, pthread, list_node); + xSemaphoreGive(s_threads_mux); + + // start task + xTaskNotify(xHandle, 0, eNoAction); + + *thread = (pthread_t)pthread; // pointer value fit into pthread_t (uint32_t) + + ESP_LOGV(TAG, "Created task %x", (uint32_t)xHandle); + + return 0; +} + +int pthread_join(pthread_t thread, void **retval) +{ + esp_pthread_t *pthread = (esp_pthread_t *)thread; + int ret = 0; + bool wait = false; + + ESP_LOGV(TAG, "%s %p", __FUNCTION__, pthread); + + // find task + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + TaskHandle_t handle = pthread_find_handle(thread); + if (!handle) { + // not found + ret = ESRCH; + } else if (pthread->join_task) { + // already have waiting task to join + ret = EINVAL; + } else if (handle == xTaskGetCurrentTaskHandle()) { + // join to self not allowed + ret = EDEADLK; + } else { + esp_pthread_t *cur_pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (cur_pthread && cur_pthread->join_task == handle) { + // join to each other not allowed + ret = EDEADLK; + } else { + if (pthread->state == PTHREAD_TASK_STATE_RUN) { + pthread->join_task = xTaskGetCurrentTaskHandle(); + wait = true; + } else { + pthread_delete(pthread); + } + } + } + xSemaphoreGive(s_threads_mux); + + if (ret == 0 && wait) { + xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + pthread_delete(pthread); + xSemaphoreGive(s_threads_mux); + } + + if (retval) { + *retval = 0; // no exit code in FreeRTOS + } + + ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret); + return ret; +} + +int pthread_detach(pthread_t thread) +{ + esp_pthread_t *pthread = (esp_pthread_t *)thread; + int ret = 0; + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + TaskHandle_t handle = pthread_find_handle(thread); + if (!handle) { + ret = ESRCH; + } else { + pthread->detached = true; + } + xSemaphoreGive(s_threads_mux); + ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret); + return ret; +} + +int pthread_cancel(pthread_t thread) +{ + ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__); + return ENOSYS; +} + +int sched_yield( void ) +{ + vTaskDelay(0); + return 0; +} + +pthread_t pthread_self(void) +{ + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + esp_pthread_t *pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (!pthread) { + assert(false && "Failed to find current thread ID!"); + } + xSemaphoreGive(s_threads_mux); + return (pthread_t)pthread; +} + +int pthread_equal(pthread_t t1, pthread_t t2) +{ + return t1 == t2 ? 1 : 0; +} + +/***************** ONCE ******************/ +int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) +{ + if (once_control == NULL || init_routine == NULL || !once_control->is_initialized) { + ESP_LOGE(TAG, "%s: Invalid args!", __FUNCTION__); + return EINVAL; + } + + uint32_t res = 1; +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(once_control)) { + uxPortCompareSetExtram((uint32_t *) &once_control->init_executed, 0, &res); + } else { +#endif + uxPortCompareSet((uint32_t *) &once_control->init_executed, 0, &res); +#if defined(CONFIG_SPIRAM_SUPPORT) + } +#endif + // Check if compare and set was successful + if (res == 0) { + ESP_LOGV(TAG, "%s: call init_routine %p", __FUNCTION__, once_control); + init_routine(); + } + + return 0; +} + +/***************** MUTEX ******************/ +static int mutexattr_check(const pthread_mutexattr_t *attr) +{ + if (attr->type < PTHREAD_MUTEX_NORMAL || attr->type > PTHREAD_MUTEX_RECURSIVE) { + return EINVAL; + } + return 0; +} + +int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) +{ + int type = PTHREAD_MUTEX_NORMAL; + + if (!mutex) { + return EINVAL; + } + + if (attr) { + if (!attr->is_initialized) { + return EINVAL; + } + int res = mutexattr_check(attr); + if (res) { + return res; + } + type = attr->type; + } + + esp_pthread_mutex_t *mux = (esp_pthread_mutex_t *)malloc(sizeof(esp_pthread_mutex_t)); + if (!mux) { + return ENOMEM; + } + mux->type = type; + + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + mux->sem = xSemaphoreCreateRecursiveMutex(); + } else { + mux->sem = xSemaphoreCreateMutex(); + } + if (!mux->sem) { + free(mux); + return EAGAIN; + } + + *mutex = (pthread_mutex_t)mux; // pointer value fit into pthread_mutex_t (uint32_t) + + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t *mutex) +{ + esp_pthread_mutex_t *mux; + + ESP_LOGV(TAG, "%s %p", __FUNCTION__, mutex); + + if (!mutex) { + return EINVAL; + } + mux = (esp_pthread_mutex_t *)*mutex; + + // check if mux is busy + int res = pthread_mutex_lock_internal(mux, 0); + if (res == EBUSY) { + return EBUSY; + } + + vSemaphoreDelete(mux->sem); + free(mux); + + return 0; +} + +static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo) +{ + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + if (xSemaphoreTakeRecursive(mux->sem, tmo) != pdTRUE) { + return EBUSY; + } + } else { + if (xSemaphoreTake(mux->sem, tmo) != pdTRUE) { + return EBUSY; + } + } + + return 0; +} + +static int pthread_mutex_init_if_static(pthread_mutex_t *mutex) { + int res = 0; + if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) { + portENTER_CRITICAL(&s_mutex_init_lock); + if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) { + res = pthread_mutex_init(mutex, NULL); + } + portEXIT_CRITICAL(&s_mutex_init_lock); + } + return res; +} + +int IRAM_ATTR pthread_mutex_lock(pthread_mutex_t *mutex) +{ + if (!mutex) { + return EINVAL; + } + int res = pthread_mutex_init_if_static(mutex); + if (res != 0) { + return res; + } + return pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, portMAX_DELAY); +} + +int IRAM_ATTR pthread_mutex_trylock(pthread_mutex_t *mutex) +{ + if (!mutex) { + return EINVAL; + } + int res = pthread_mutex_init_if_static(mutex); + if (res != 0) { + return res; + } + return pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, 0); +} + +int IRAM_ATTR pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + esp_pthread_mutex_t *mux; + + if (!mutex) { + return EINVAL; + } + mux = (esp_pthread_mutex_t *)*mutex; + + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + xSemaphoreGiveRecursive(mux->sem); + } else { + xSemaphoreGive(mux->sem); + } + return 0; +} + +int pthread_mutexattr_init(pthread_mutexattr_t *attr) +{ + if (!attr) { + return EINVAL; + } + attr->type = PTHREAD_MUTEX_NORMAL; + attr->is_initialized = 1; + return 0; +} + +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) +{ + if (!attr) { + return EINVAL; + } + attr->is_initialized = 0; + return 0; +} + +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) +{ + ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__); + return ENOSYS; +} + +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) +{ + if (!attr) { + return EINVAL; + } + pthread_mutexattr_t tmp_attr = {.type = type}; + int res = mutexattr_check(&tmp_attr); + if (!res) { + attr->type = type; + } + return res; +} diff --git a/components/pthread/pthread_cond_var.c b/components/pthread/pthread_cond_var.c new file mode 100644 index 00000000..39e23821 --- /dev/null +++ b/components/pthread/pthread_cond_var.c @@ -0,0 +1,200 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a simple implementation of pthread condition variables. In essence, +// the waiter creates its own semaphore to wait on and pushes it in the cond var +// specific list. Upon notify and broadcast, all the waiters for the given cond +// var are woken up. + +#include +#include +#include +#include "esp_err.h" +#include "esp_attr.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/list.h" + +#include +#include + +#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include "esp_log.h" +const static char *TAG = "esp_pthread"; + +typedef struct esp_pthread_cond_waiter { + SemaphoreHandle_t wait_sem; ///< task specific semaphore to wait on + TAILQ_ENTRY(esp_pthread_cond_waiter) link; ///< stash on the list of semaphores to be notified +} esp_pthread_cond_waiter_t; + +typedef struct esp_pthread_cond { + _lock_t lock; ///< lock that protects the list of semaphores + TAILQ_HEAD(, esp_pthread_cond_waiter) waiter_list; ///< head of the list of semaphores +} esp_pthread_cond_t; + +int pthread_cond_signal(pthread_cond_t *cv) +{ + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + esp_pthread_cond_waiter_t *entry; + entry = TAILQ_FIRST(&cond->waiter_list); + if (entry) { + xSemaphoreGive(entry->wait_sem); + } + _lock_release_recursive(&cond->lock); + + return 0; +} + +int pthread_cond_broadcast(pthread_cond_t *cv) +{ + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + esp_pthread_cond_waiter_t *entry; + TAILQ_FOREACH(entry, &cond->waiter_list, link) { + xSemaphoreGive(entry->wait_sem); + } + _lock_release_recursive(&cond->lock); + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mut) +{ + return pthread_cond_timedwait(cv, mut, NULL); +} + +int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mut, const struct timespec *to) +{ + int ret; + TickType_t timeout_ticks; + + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + if (to == NULL) { + timeout_ticks = portMAX_DELAY; + } else { + struct timeval abs_time, cur_time, diff_time; + long timeout_msec; + + gettimeofday(&cur_time, NULL); + + abs_time.tv_sec = to->tv_sec; + abs_time.tv_usec = to->tv_nsec / 1000; + + if (timercmp(&abs_time, &cur_time, <)) { + /* As per the pthread spec, if the time has already + * passed, no sleep is required. + */ + timeout_msec = 0; + } else { + timersub(&abs_time, &cur_time, &diff_time); + timeout_msec = (diff_time.tv_sec * 1000) + (diff_time.tv_usec / 1000); + } + + if (timeout_msec <= 0) { + return ETIMEDOUT; + } + + timeout_ticks = timeout_msec / portTICK_PERIOD_MS; + } + + esp_pthread_cond_waiter_t w; + w.wait_sem = xSemaphoreCreateCounting(1, 0); /* First get will block */ + + _lock_acquire_recursive(&cond->lock); + TAILQ_INSERT_TAIL(&cond->waiter_list, &w, link); + _lock_release_recursive(&cond->lock); + pthread_mutex_unlock(mut); + + if (xSemaphoreTake(w.wait_sem, timeout_ticks) == pdTRUE) { + ret = 0; + } else { + ret = ETIMEDOUT; + } + + _lock_acquire_recursive(&cond->lock); + TAILQ_REMOVE(&cond->waiter_list, &w, link); + _lock_release_recursive(&cond->lock); + vSemaphoreDelete(w.wait_sem); + + pthread_mutex_lock(mut); + return ret; +} + +int pthread_condattr_init(pthread_condattr_t *attr) +{ + ESP_LOGV(TAG, "%s not yet implemented (%p)", __FUNCTION__, attr); + return ENOSYS; +} + +int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *att) +{ + (void) att; /* Unused argument as of now */ + + if (cv == NULL) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) calloc(1, sizeof(esp_pthread_cond_t)); + if (cond == NULL) { + return ENOMEM; + } + + _lock_init_recursive(&cond->lock); + TAILQ_INIT(&cond->waiter_list); + + *cv = (pthread_cond_t) cond; + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cv) +{ + int ret = 0; + + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + if (!TAILQ_EMPTY(&cond->waiter_list)) { + ret = EBUSY; + } + _lock_release_recursive(&cond->lock); + + if (ret == 0) { + *cv = (pthread_cond_t) 0; + _lock_close_recursive(&cond->lock); + free(cond); + } + + return ret; +} diff --git a/components/pthread/pthread_internal.h b/components/pthread/pthread_internal.h new file mode 100644 index 00000000..6ed2fe45 --- /dev/null +++ b/components/pthread/pthread_internal.h @@ -0,0 +1,16 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +void pthread_internal_local_storage_destructor_callback(); diff --git a/components/pthread/pthread_local_storage.c b/components/pthread/pthread_local_storage.c new file mode 100644 index 00000000..f87f698d --- /dev/null +++ b/components/pthread/pthread_local_storage.c @@ -0,0 +1,257 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sys/lock.h" +#include "rom/queue.h" + +#include "pthread_internal.h" + +#define PTHREAD_TLS_INDEX 0 + +typedef void (*pthread_destructor_t)(void*); + +/* This is a very naive implementation of key-indexed thread local storage, using two linked lists + (one is a global list of registered keys, one per thread for thread local storage values). + + It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both), + but it should work for small amounts of data. +*/ +typedef struct key_entry_t_ { + pthread_key_t key; + pthread_destructor_t destructor; + SLIST_ENTRY(key_entry_t_) next; +} key_entry_t; + +// List of all keys created with pthread_key_create() +SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys); + +static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED; + +// List of all value entries associated with a thread via pthread_setspecific() +typedef struct value_entry_t_ { + pthread_key_t key; + void *value; + SLIST_ENTRY(value_entry_t_) next; +} value_entry_t; + +// Type for the head of the list, as saved as a FreeRTOS thread local storage pointer +SLIST_HEAD(values_list_t_, value_entry_t_); +typedef struct values_list_t_ values_list_t; + +int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor) +{ + key_entry_t *new_key = malloc(sizeof(key_entry_t)); + if (new_key == NULL) { + return ENOMEM; + } + + portENTER_CRITICAL(&s_keys_lock); + + const key_entry_t *head = SLIST_FIRST(&s_keys); + new_key->key = (head == NULL) ? 1 : (head->key + 1); + new_key->destructor = destructor; + *key = new_key->key; + + SLIST_INSERT_HEAD(&s_keys, new_key, next); + + portEXIT_CRITICAL(&s_keys_lock); + return 0; +} + +static key_entry_t *find_key(pthread_key_t key) +{ + portENTER_CRITICAL(&s_keys_lock); + key_entry_t *result = NULL;; + SLIST_FOREACH(result, &s_keys, next) { + if(result->key == key) { + break; + } + } + portEXIT_CRITICAL(&s_keys_lock); + return result; +} + +int pthread_key_delete(pthread_key_t key) +{ + + portENTER_CRITICAL(&s_keys_lock); + + /* Ideally, we would also walk all tasks' thread local storage value_list here + and delete any values associated with this key. We do not do this... + */ + + key_entry_t *entry = find_key(key); + if (entry != NULL) { + SLIST_REMOVE(&s_keys, entry, key_entry_t_, next); + free(entry); + } + + portEXIT_CRITICAL(&s_keys_lock); + + return 0; +} + +/* Clean up callback for deleted tasks. + + This is called from one of two places: + + If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends, + and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted. + + For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted. + + (The reason for calling it early for pthreads is to keep the timing consistent with "normal" pthreads, so after + pthread_join() the task's destructors have all been called even if the idle task hasn't run cleanup yet.) +*/ +static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls) +{ + values_list_t *tls = (values_list_t *)v_tls; + assert(tls != NULL); + + /* Walk the list, freeing all entries and calling destructors if they are registered */ + value_entry_t *entry = SLIST_FIRST(tls); + while(entry != NULL) { + // This is a little slow, walking the linked list of keys once per value, + // but assumes that the thread's value list will have less entries + // than the keys list + key_entry_t *key = find_key(entry->key); + if (key != NULL && key->destructor != NULL) { + key->destructor(entry->value); + } + value_entry_t *next_entry = SLIST_NEXT(entry, next); + free(entry); + entry = next_entry; + } + free(tls); +} + +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) +/* Called from FreeRTOS task delete hook */ +void pthread_local_storage_cleanup(TaskHandle_t task) +{ + void *tls = pvTaskGetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX); + if (tls != NULL) { + pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls); + vTaskSetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX, NULL); + } +} + +void __real_vPortCleanUpTCB(void *tcb); + +/* If static task cleanup hook is defined then its applications responsibility to define `vPortCleanUpTCB`. + Here we are wrapping it, so that we can do pthread specific TLS cleanup and then invoke application + real specific `vPortCleanUpTCB` */ +void __wrap_vPortCleanUpTCB(void *tcb) +{ + pthread_local_storage_cleanup(tcb); + __real_vPortCleanUpTCB(tcb); +} +#endif + +/* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */ +void pthread_internal_local_storage_destructor_callback() +{ + void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); + if (tls != NULL) { + pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls); + /* remove the thread-local-storage pointer to avoid the idle task cleanup + calling it again... + */ +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) + vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL); +#else + vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, + PTHREAD_TLS_INDEX, + NULL, + NULL); +#endif + } +} + +static value_entry_t *find_value(const values_list_t *list, pthread_key_t key) +{ + value_entry_t *result = NULL;; + SLIST_FOREACH(result, list, next) { + if(result->key == key) { + break; + } + } + return result; +} + +void *pthread_getspecific(pthread_key_t key) +{ + values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); + if (tls == NULL) { + return NULL; + } + + value_entry_t *entry = find_value(tls, key); + if(entry != NULL) { + return entry->value; + } + return NULL; +} + +int pthread_setspecific(pthread_key_t key, const void *value) +{ + key_entry_t *key_entry = find_key(key); + if (key_entry == NULL) { + return ENOENT; // this situation is undefined by pthreads standard + } + + values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); + if (tls == NULL) { + tls = calloc(1, sizeof(values_list_t)); + if (tls == NULL) { + return ENOMEM; + } +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) + vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls); +#else + vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, + PTHREAD_TLS_INDEX, + tls, + pthread_local_storage_thread_deleted_callback); +#endif + } + + value_entry_t *entry = find_value(tls, key); + if (entry != NULL) { + if (value != NULL) { + // cast on next line is necessary as pthreads API uses + // 'const void *' here but elsewhere uses 'void *' + entry->value = (void *) value; + } else { // value == NULL, remove the entry + SLIST_REMOVE(tls, entry, value_entry_t_, next); + free(entry); + } + } else if (value != NULL) { + entry = malloc(sizeof(value_entry_t)); + if (entry == NULL) { + return ENOMEM; + } + entry->key = key; + entry->value = (void *) value; // see note above about cast + SLIST_INSERT_HEAD(tls, entry, next); + } + + return 0; +} diff --git a/components/pthread/test/component.mk b/components/pthread/test/component.mk new file mode 100644 index 00000000..af9fbb83 --- /dev/null +++ b/components/pthread/test/component.mk @@ -0,0 +1,9 @@ +# +#Component Makefile +# + +COMPONENT_SRCDIRS := . + +#CXXFLAGS += -H + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/pthread/test/test_cxx_cond_var.cpp b/components/pthread/test/test_cxx_cond_var.cpp new file mode 100644 index 00000000..ccb411ae --- /dev/null +++ b/components/pthread/test/test_cxx_cond_var.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include "unity.h" + +#if __GTHREADS && __GTHREADS_CXX0X + +std::condition_variable cv; +std::mutex cv_m; +std::atomic i{0}; + +static void waits(int idx, int timeout_ms) +{ + std::unique_lock lk(cv_m); + auto now = std::chrono::system_clock::now(); + + if(cv.wait_until(lk, now + std::chrono::milliseconds(timeout_ms), [](){return i == 1;})) + std::cout << "Thread " << idx << " finished waiting. i == " << i << '\n'; + else + std::cout << "Thread " << idx << " timed out. i == " << i << '\n'; +} + +static void signals(int signal_ms) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(signal_ms)); + std::cout << "Notifying...\n"; + cv.notify_all(); + std::this_thread::sleep_for(std::chrono::milliseconds(signal_ms)); + i = 1; + std::cout << "Notifying again...\n"; + cv.notify_all(); +} + +TEST_CASE("C++ condition_variable", "[std::condition_variable]") +{ + i = 0; + std::thread t1(waits, 1, 100), t2(waits, 2, 800), t3(signals, 200); + + t1.join(); + t2.join(); + t3.join(); + + std::cout << "All threads joined\n"; +} +#endif diff --git a/components/pthread/test/test_cxx_std_future.cpp b/components/pthread/test/test_cxx_std_future.cpp new file mode 100644 index 00000000..bbf8d624 --- /dev/null +++ b/components/pthread/test/test_cxx_std_future.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include "unity.h" + +#if __GTHREADS && __GTHREADS_CXX0X +TEST_CASE("C++ future", "[std::future]") +{ + // future from a packaged_task + std::packaged_task task([]{ return 7; }); // wrap the function + std::future f1 = task.get_future(); // get a future + std::thread t(std::move(task)); // launch on a thread + + // future from an async() + std::future f2 = std::async(std::launch::async, []{ return 8; }); + + // future from a promise + std::promise p; + std::future f3 = p.get_future(); + std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach(); + + std::cout << "Waiting..." << std::flush; + f1.wait(); + f2.wait(); + f3.wait(); + std::cout << "Done!\nResults are: " + << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n'; + t.join(); +} +#endif + diff --git a/components/pthread/test/test_pthread_cxx.cpp b/components/pthread/test/test_pthread_cxx.cpp new file mode 100644 index 00000000..074b9ba9 --- /dev/null +++ b/components/pthread/test/test_pthread_cxx.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" + +#if __GTHREADS && __GTHREADS_CXX0X + +#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include "esp_log.h" +const static char *TAG = "pthread_test"; + +static std::shared_ptr global_sp; +static std::mutex mtx; +static std::recursive_mutex recur_mtx; + +static void thread_do_nothing() {} + +static void thread_main() +{ + int i = 0; + std::cout << "thread_main CXX " << std::hex << std::this_thread::get_id() << std::endl; + std::chrono::milliseconds dur = std::chrono::milliseconds(300); + + while (i < 3) { + int old_val, new_val; + + // mux test + mtx.lock(); + old_val = *global_sp; + std::this_thread::yield(); + (*global_sp)++; + std::this_thread::yield(); + new_val = *global_sp; + mtx.unlock(); + std::cout << "thread " << std::hex << std::this_thread::get_id() << ": " << i++ << " val= " << *global_sp << std::endl; + TEST_ASSERT_TRUE(new_val == old_val + 1); + + // sleep_for test + std::this_thread::sleep_for(dur); + + // recursive mux test + recur_mtx.lock(); + recur_mtx.lock(); + old_val = *global_sp; + std::this_thread::yield(); + (*global_sp)++; + std::this_thread::yield(); + new_val = *global_sp; + recur_mtx.unlock(); + recur_mtx.unlock(); + std::cout << "thread " << std::hex << std::this_thread::get_id() << ": " << i++ << " val= " << *global_sp << std::endl; + TEST_ASSERT_TRUE(new_val == old_val + 1); + + // sleep_until test + using std::chrono::system_clock; + std::time_t tt = system_clock::to_time_t(system_clock::now()); + struct std::tm *ptm = std::localtime(&tt); + ptm->tm_sec++; + std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm))); + } +} + +TEST_CASE("pthread C++", "[pthread]") +{ + global_sp.reset(new int(1)); + + std::thread t1(thread_do_nothing); + t1.join(); + + std::thread t2(thread_main); + std::cout << "Detach thread " << std::hex << t2.get_id() << std::endl; + t2.detach(); + TEST_ASSERT_FALSE(t2.joinable()); + + std::thread t3(thread_main); + std::thread t4(thread_main); + if (t3.joinable()) { + std::cout << "Join thread " << std::hex << t3.get_id() << std::endl; + t3.join(); + } + if (t4.joinable()) { + std::cout << "Join thread " << std::hex << t4.get_id() << std::endl; + t4.join(); + } + + global_sp.reset(); // avoid reported leak +} + +static void task_test_sandbox() +{ + std::stringstream ss; + + ESP_LOGI(TAG, "About to create a string stream"); + ESP_LOGI(TAG, "About to write to string stream"); + ss << "Hello World!"; + ESP_LOGI(TAG, "About to extract from stringstream"); + ESP_LOGI(TAG, "Text: %s", ss.str().c_str()); +} + +static void task_test_sandbox_c(void *arg) +{ + bool *running = (bool *)arg; + + // wrap thread func to ensure that all C++ stack objects are cleaned up by their destructors + task_test_sandbox(); + + ESP_LOGI(TAG, "Task stk_wm = %d", uxTaskGetStackHighWaterMark(NULL)); + if (running) { + *running = false; + vTaskDelete(NULL); + } +} + +TEST_CASE("pthread mix C/C++", "[pthread]") +{ + bool c_running = true; + + std::thread t1(task_test_sandbox); + xTaskCreatePinnedToCore((TaskFunction_t)&task_test_sandbox_c, "task_test_sandbox", 3072, &c_running, 5, NULL, 0); + while (c_running) { + vTaskDelay(1); + } + if (t1.joinable()) { + std::cout << "Join thread " << std::hex << t1.get_id() << std::endl; + t1.join(); + } +} + +#endif diff --git a/components/pthread/test/test_pthread_local_storage.c b/components/pthread/test/test_pthread_local_storage.c new file mode 100644 index 00000000..8be22ce5 --- /dev/null +++ b/components/pthread/test/test_pthread_local_storage.c @@ -0,0 +1,130 @@ +// Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +TEST_CASE("pthread local storage basics", "[pthread]") +{ + pthread_key_t key; + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, NULL)); + + TEST_ASSERT_NULL(pthread_getspecific(key)); + int val = 3; + + printf("Setting to %p...\n", &val); + TEST_ASSERT_EQUAL(0, pthread_setspecific(key, &val)); + + printf("Reading back...\n"); + TEST_ASSERT_EQUAL_PTR(&val, pthread_getspecific(key)); + + printf("Setting to NULL...\n"); + TEST_ASSERT_EQUAL(0, pthread_setspecific(key, NULL)); + + printf("Reading back...\n"); + TEST_ASSERT_NULL(pthread_getspecific(key)); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + +TEST_CASE("pthread local storage unique keys", "[pthread]") +{ + const int NUM_KEYS = 10; + pthread_key_t keys[NUM_KEYS]; + + for (int i = 0; i < NUM_KEYS; i++) { + TEST_ASSERT_EQUAL(0, pthread_key_create(&keys[i], NULL)); + printf("New key %d = %d\n", i, keys[i]); + } + + for (int i = 0; i < NUM_KEYS; i++) { + for (int j = 0; j < NUM_KEYS; j++) { + if (i != j) { + TEST_ASSERT_NOT_EQUAL(keys[i], keys[j]); + } + } + } + + for (int i = 0; i < NUM_KEYS; i++) { + TEST_ASSERT_EQUAL(0, pthread_key_delete(keys[i])); + } +} + +static void test_pthread_destructor(void *); +static void *expected_destructor_ptr; +static void *actual_destructor_ptr; +static void *thread_test_pthread_destructor(void *); + +TEST_CASE("pthread local storage destructor", "[pthread]") +{ + pthread_t thread; + pthread_key_t key = -1; + + expected_destructor_ptr = NULL; + actual_destructor_ptr = NULL; + + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor)); + + TEST_ASSERT_EQUAL(0, pthread_create(&thread, NULL, thread_test_pthread_destructor, (void *)key)); + TEST_ASSERT_EQUAL(0, pthread_join(thread, NULL)); + + printf("Joined...\n"); + TEST_ASSERT_NOT_NULL(expected_destructor_ptr); + TEST_ASSERT_NOT_NULL(actual_destructor_ptr); + TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + +static void task_test_pthread_destructor(void *v_key); + +TEST_CASE("pthread local storage destructor in FreeRTOS task", "[pthread]") +{ + // Same as previous test case, but doesn't use pthread APIs therefore must wait + // for the idle task to call the destructor + pthread_key_t key = -1; + + expected_destructor_ptr = NULL; + actual_destructor_ptr = NULL; + + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor)); + + xTaskCreate(task_test_pthread_destructor, + "ptdest", 8192, (void *)key, UNITY_FREERTOS_PRIORITY+1, + NULL); + + // Above task has higher priority to us, so should run immediately + // but we need to wait for the idle task cleanup to run + vTaskDelay(20); + + TEST_ASSERT_NOT_NULL(expected_destructor_ptr); + TEST_ASSERT_NOT_NULL(actual_destructor_ptr); + TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + + + +static void *thread_test_pthread_destructor(void *v_key) +{ + printf("Local storage thread running...\n"); + pthread_key_t key = (pthread_key_t) v_key; + expected_destructor_ptr = &key; // address of stack variable in the task... + pthread_setspecific(key, expected_destructor_ptr); + printf("Local storage thread done.\n"); + return NULL; +} + +static void test_pthread_destructor(void *value) +{ + printf("Destructor called...\n"); + actual_destructor_ptr = value; +} + +static void task_test_pthread_destructor(void *v_key) +{ + /* call the pthread main routine, then delete ourselves... */ + thread_test_pthread_destructor(v_key); + vTaskDelete(NULL); +} From 1fc474b8c9585f6e1835a371ca6c1f4557bf0419 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 27 Aug 2018 11:54:55 +0800 Subject: [PATCH 2/2] feat(pthread): Modify for ESP8266 --- components/esp8266/source/startup.c | 5 + .../esp8266/include/freertos/FreeRTOSConfig.h | 4 + components/newlib/Makefile.projbuild | 5 +- components/newlib/newlib/include/sys/time.h | 2 +- components/newlib/newlib/port/syscall.c | 2 +- components/pthread/Kconfig | 6 + components/pthread/component.mk | 4 +- components/pthread/{ => src}/pthread.c | 28 +++- .../pthread/{ => src}/pthread_cond_var.c | 9 +- .../pthread/{ => src}/pthread_internal.h | 0 .../pthread/{ => src}/pthread_local_storage.c | 19 ++- components/pthread/test/test_cxx_cond_var.cpp | 48 ------- .../pthread/test/test_cxx_std_future.cpp | 31 ---- components/pthread/test/test_pthread_cxx.cpp | 132 ------------------ .../pthread/test/test_pthread_local_storage.c | 1 + 15 files changed, 75 insertions(+), 221 deletions(-) rename components/pthread/{ => src}/pthread.c (96%) rename components/pthread/{ => src}/pthread_cond_var.c (97%) rename components/pthread/{ => src}/pthread_internal.h (100%) rename components/pthread/{ => src}/pthread_local_storage.c (96%) delete mode 100644 components/pthread/test/test_cxx_cond_var.cpp delete mode 100644 components/pthread/test/test_cxx_std_future.cpp delete mode 100644 components/pthread/test/test_pthread_cxx.cpp diff --git a/components/esp8266/source/startup.c b/components/esp8266/source/startup.c index 36ceddb6..cf0c7d9d 100644 --- a/components/esp8266/source/startup.c +++ b/components/esp8266/source/startup.c @@ -39,6 +39,7 @@ extern int base_gpio_init(void); extern int watchdog_init(void); extern int wifi_timer_init(void); extern int wifi_nvs_init(void); +extern esp_err_t esp_pthread_init(void); static void user_init_entry(void *param) { @@ -67,6 +68,10 @@ static void user_init_entry(void *param) esp_task_wdt_init(); #endif +#ifdef CONFIG_ENABLE_PTHREAD + assert(esp_pthread_init() == 0); +#endif + app_main(); wifi_task_delete(NULL); diff --git a/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h b/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h index 2d728385..78d22eeb 100644 --- a/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h @@ -152,7 +152,11 @@ NVIC value of 255. */ #define configUSE_NEWLIB_REENTRANT 1 #endif +#ifdef CONFIG_ENABLE_PTHREAD +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 2 +#else #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1 +#endif #define configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS 1 /* add this to dump task stack information */ diff --git a/components/newlib/Makefile.projbuild b/components/newlib/Makefile.projbuild index 733676d0..90b99fcb 100644 --- a/components/newlib/Makefile.projbuild +++ b/components/newlib/Makefile.projbuild @@ -1 +1,4 @@ -CFLAGS += -D_CLOCKS_PER_SEC_=CONFIG_FREERTOS_HZ +CFLAGS += -D_CLOCKS_PER_SEC_=CONFIG_FREERTOS_HZ -D_POSIX_THREADS=1 -D_UNIX98_THREAD_MUTEX_ATTRIBUTES=1 +ifdef CONFIG_ENABLE_PTHREAD +CFLAGS += -D_POSIX_THREADS=1 -D_UNIX98_THREAD_MUTEX_AT +endif diff --git a/components/newlib/newlib/include/sys/time.h b/components/newlib/newlib/include/sys/time.h index be16497f..8e3ef808 100644 --- a/components/newlib/newlib/include/sys/time.h +++ b/components/newlib/newlib/include/sys/time.h @@ -20,7 +20,7 @@ struct timeval { }; /* BSD time macros used by RTEMS code */ -#if defined (__rtems__) || defined (__CYGWIN__) +#if defined (__rtems__) || defined (__CYGWIN__) || defined(__XTENSA__) /* Convenience macros for operations on timevals. NOTE: `timercmp' does not work for >= or <=. */ diff --git a/components/newlib/newlib/port/syscall.c b/components/newlib/newlib/port/syscall.c index f289b1df..a7fe205d 100644 --- a/components/newlib/newlib/port/syscall.c +++ b/components/newlib/newlib/port/syscall.c @@ -32,7 +32,7 @@ _ssize_t _read_r(struct _reent *r, int fd, void *buf, size_t len) return 0; } -_ssize_t _write_r(struct _reent *r, int fd, void *buf, size_t len) +_ssize_t _write_r(struct _reent *r, int fd, const void *buf, size_t len) { int i; const char *cbuf = buf; diff --git a/components/pthread/Kconfig b/components/pthread/Kconfig index 3f16c6ff..6c6d8fa1 100644 --- a/components/pthread/Kconfig +++ b/components/pthread/Kconfig @@ -1,5 +1,11 @@ menu "PThreads" +config ENABLE_PTHREAD + bool "Enable pthread" + default n + help + Enable this option and then pthread is to be used. + config ESP32_PTHREAD_TASK_PRIO_DEFAULT int "Default task priority" range 0 255 diff --git a/components/pthread/component.mk b/components/pthread/component.mk index 0dd23948..7528b1da 100644 --- a/components/pthread/component.mk +++ b/components/pthread/component.mk @@ -2,7 +2,9 @@ # Component Makefile # -COMPONENT_SRCDIRS := . +ifdef CONFIG_ENABLE_PTHREAD +COMPONENT_SRCDIRS := src +endif COMPONENT_ADD_INCLUDEDIRS := include diff --git a/components/pthread/pthread.c b/components/pthread/src/pthread.c similarity index 96% rename from components/pthread/pthread.c rename to components/pthread/src/pthread.c index 9d7822b8..c9fec073 100644 --- a/components/pthread/pthread.c +++ b/components/pthread/src/pthread.c @@ -21,9 +21,11 @@ #include #include #include +#include #include "esp_err.h" #include "esp_attr.h" -#include "rom/queue.h" +#include "sys/queue.h" +#include "sys/types.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" @@ -31,6 +33,16 @@ #include "pthread_internal.h" #include "esp_pthread.h" +#ifdef CONFIG_ENABLE_PTHREAD + +#if portNUM_PROCESSORS == 1 +#undef portENTER_CRITICAL +#undef portEXIT_CRITICAL + +#define portENTER_CRITICAL(l) vPortEnterCritical() +#define portEXIT_CRITICAL(l) vPortExitCritical() +#endif + #define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL #include "esp_log.h" const static char *TAG = "pthread"; @@ -65,7 +77,9 @@ typedef struct { static SemaphoreHandle_t s_threads_mux = NULL; +#if portNUM_PROCESSORS > 1 static portMUX_TYPE s_mutex_init_lock = portMUX_INITIALIZER_UNLOCKED; +#endif static SLIST_HEAD(esp_thread_list_head, esp_pthread_entry) s_threads_list = SLIST_HEAD_INITIALIZER(s_threads_list); static pthread_key_t s_pthread_cfg_key; @@ -73,6 +87,14 @@ static pthread_key_t s_pthread_cfg_key; static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo); +static inline void uxPortCompareSet(volatile uint32_t *addr, uint32_t compare, uint32_t *set) +{ + portENTER_CRITICAL(&s_mutex_init_lock); + *addr = compare; + *set = 0; + portEXIT_CRITICAL(&s_mutex_init_lock); +} + static void esp_pthread_cfg_key_destructor(void *value) { free(value); @@ -205,7 +227,7 @@ static void pthread_task_func(void *arg) } xSemaphoreGive(s_threads_mux); - ESP_LOGD(TAG, "Task stk_wm = %d", uxTaskGetStackHighWaterMark(NULL)); + ESP_LOGD(TAG, "Task stk_wm = %lu", uxTaskGetStackHighWaterMark(NULL)); vTaskDelete(NULL); ESP_LOGV(TAG, "%s EXIT", __FUNCTION__); @@ -586,3 +608,5 @@ int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) } return res; } + +#endif diff --git a/components/pthread/pthread_cond_var.c b/components/pthread/src/pthread_cond_var.c similarity index 97% rename from components/pthread/pthread_cond_var.c rename to components/pthread/src/pthread_cond_var.c index 39e23821..98f53fda 100644 --- a/components/pthread/pthread_cond_var.c +++ b/components/pthread/src/pthread_cond_var.c @@ -20,16 +20,19 @@ #include #include #include +#include #include "esp_err.h" #include "esp_attr.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" -#include "freertos/list.h" +#include "freertos/private/list.h" -#include +#include #include +#ifdef CONFIG_ENABLE_PTHREAD + #define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL #include "esp_log.h" const static char *TAG = "esp_pthread"; @@ -198,3 +201,5 @@ int pthread_cond_destroy(pthread_cond_t *cv) return ret; } + +#endif diff --git a/components/pthread/pthread_internal.h b/components/pthread/src/pthread_internal.h similarity index 100% rename from components/pthread/pthread_internal.h rename to components/pthread/src/pthread_internal.h diff --git a/components/pthread/pthread_local_storage.c b/components/pthread/src/pthread_local_storage.c similarity index 96% rename from components/pthread/pthread_local_storage.c rename to components/pthread/src/pthread_local_storage.c index f87f698d..dd10d50e 100644 --- a/components/pthread/pthread_local_storage.c +++ b/components/pthread/src/pthread_local_storage.c @@ -14,16 +14,27 @@ #include #include #include +#include #include "esp_err.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "sys/lock.h" -#include "rom/queue.h" +#include "sys/queue.h" #include "pthread_internal.h" -#define PTHREAD_TLS_INDEX 0 +#ifdef CONFIG_ENABLE_PTHREAD + +#define PTHREAD_TLS_INDEX 1 + +#if portNUM_PROCESSORS == 1 +#undef portENTER_CRITICAL +#undef portEXIT_CRITICAL + +#define portENTER_CRITICAL(l) vPortEnterCritical() +#define portEXIT_CRITICAL(l) vPortExitCritical() +#endif typedef void (*pthread_destructor_t)(void*); @@ -42,7 +53,9 @@ typedef struct key_entry_t_ { // List of all keys created with pthread_key_create() SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys); +#if portNUM_PROCESSORS > 1 static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED; +#endif // List of all value entries associated with a thread via pthread_setspecific() typedef struct value_entry_t_ { @@ -255,3 +268,5 @@ int pthread_setspecific(pthread_key_t key, const void *value) return 0; } + +#endif diff --git a/components/pthread/test/test_cxx_cond_var.cpp b/components/pthread/test/test_cxx_cond_var.cpp deleted file mode 100644 index ccb411ae..00000000 --- a/components/pthread/test/test_cxx_cond_var.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "unity.h" - -#if __GTHREADS && __GTHREADS_CXX0X - -std::condition_variable cv; -std::mutex cv_m; -std::atomic i{0}; - -static void waits(int idx, int timeout_ms) -{ - std::unique_lock lk(cv_m); - auto now = std::chrono::system_clock::now(); - - if(cv.wait_until(lk, now + std::chrono::milliseconds(timeout_ms), [](){return i == 1;})) - std::cout << "Thread " << idx << " finished waiting. i == " << i << '\n'; - else - std::cout << "Thread " << idx << " timed out. i == " << i << '\n'; -} - -static void signals(int signal_ms) -{ - std::this_thread::sleep_for(std::chrono::milliseconds(signal_ms)); - std::cout << "Notifying...\n"; - cv.notify_all(); - std::this_thread::sleep_for(std::chrono::milliseconds(signal_ms)); - i = 1; - std::cout << "Notifying again...\n"; - cv.notify_all(); -} - -TEST_CASE("C++ condition_variable", "[std::condition_variable]") -{ - i = 0; - std::thread t1(waits, 1, 100), t2(waits, 2, 800), t3(signals, 200); - - t1.join(); - t2.join(); - t3.join(); - - std::cout << "All threads joined\n"; -} -#endif diff --git a/components/pthread/test/test_cxx_std_future.cpp b/components/pthread/test/test_cxx_std_future.cpp deleted file mode 100644 index bbf8d624..00000000 --- a/components/pthread/test/test_cxx_std_future.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include -#include "unity.h" - -#if __GTHREADS && __GTHREADS_CXX0X -TEST_CASE("C++ future", "[std::future]") -{ - // future from a packaged_task - std::packaged_task task([]{ return 7; }); // wrap the function - std::future f1 = task.get_future(); // get a future - std::thread t(std::move(task)); // launch on a thread - - // future from an async() - std::future f2 = std::async(std::launch::async, []{ return 8; }); - - // future from a promise - std::promise p; - std::future f3 = p.get_future(); - std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach(); - - std::cout << "Waiting..." << std::flush; - f1.wait(); - f2.wait(); - f3.wait(); - std::cout << "Done!\nResults are: " - << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n'; - t.join(); -} -#endif - diff --git a/components/pthread/test/test_pthread_cxx.cpp b/components/pthread/test/test_pthread_cxx.cpp deleted file mode 100644 index 074b9ba9..00000000 --- a/components/pthread/test/test_pthread_cxx.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" - -#if __GTHREADS && __GTHREADS_CXX0X - -#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL -#include "esp_log.h" -const static char *TAG = "pthread_test"; - -static std::shared_ptr global_sp; -static std::mutex mtx; -static std::recursive_mutex recur_mtx; - -static void thread_do_nothing() {} - -static void thread_main() -{ - int i = 0; - std::cout << "thread_main CXX " << std::hex << std::this_thread::get_id() << std::endl; - std::chrono::milliseconds dur = std::chrono::milliseconds(300); - - while (i < 3) { - int old_val, new_val; - - // mux test - mtx.lock(); - old_val = *global_sp; - std::this_thread::yield(); - (*global_sp)++; - std::this_thread::yield(); - new_val = *global_sp; - mtx.unlock(); - std::cout << "thread " << std::hex << std::this_thread::get_id() << ": " << i++ << " val= " << *global_sp << std::endl; - TEST_ASSERT_TRUE(new_val == old_val + 1); - - // sleep_for test - std::this_thread::sleep_for(dur); - - // recursive mux test - recur_mtx.lock(); - recur_mtx.lock(); - old_val = *global_sp; - std::this_thread::yield(); - (*global_sp)++; - std::this_thread::yield(); - new_val = *global_sp; - recur_mtx.unlock(); - recur_mtx.unlock(); - std::cout << "thread " << std::hex << std::this_thread::get_id() << ": " << i++ << " val= " << *global_sp << std::endl; - TEST_ASSERT_TRUE(new_val == old_val + 1); - - // sleep_until test - using std::chrono::system_clock; - std::time_t tt = system_clock::to_time_t(system_clock::now()); - struct std::tm *ptm = std::localtime(&tt); - ptm->tm_sec++; - std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm))); - } -} - -TEST_CASE("pthread C++", "[pthread]") -{ - global_sp.reset(new int(1)); - - std::thread t1(thread_do_nothing); - t1.join(); - - std::thread t2(thread_main); - std::cout << "Detach thread " << std::hex << t2.get_id() << std::endl; - t2.detach(); - TEST_ASSERT_FALSE(t2.joinable()); - - std::thread t3(thread_main); - std::thread t4(thread_main); - if (t3.joinable()) { - std::cout << "Join thread " << std::hex << t3.get_id() << std::endl; - t3.join(); - } - if (t4.joinable()) { - std::cout << "Join thread " << std::hex << t4.get_id() << std::endl; - t4.join(); - } - - global_sp.reset(); // avoid reported leak -} - -static void task_test_sandbox() -{ - std::stringstream ss; - - ESP_LOGI(TAG, "About to create a string stream"); - ESP_LOGI(TAG, "About to write to string stream"); - ss << "Hello World!"; - ESP_LOGI(TAG, "About to extract from stringstream"); - ESP_LOGI(TAG, "Text: %s", ss.str().c_str()); -} - -static void task_test_sandbox_c(void *arg) -{ - bool *running = (bool *)arg; - - // wrap thread func to ensure that all C++ stack objects are cleaned up by their destructors - task_test_sandbox(); - - ESP_LOGI(TAG, "Task stk_wm = %d", uxTaskGetStackHighWaterMark(NULL)); - if (running) { - *running = false; - vTaskDelete(NULL); - } -} - -TEST_CASE("pthread mix C/C++", "[pthread]") -{ - bool c_running = true; - - std::thread t1(task_test_sandbox); - xTaskCreatePinnedToCore((TaskFunction_t)&task_test_sandbox_c, "task_test_sandbox", 3072, &c_running, 5, NULL, 0); - while (c_running) { - vTaskDelay(1); - } - if (t1.joinable()) { - std::cout << "Join thread " << std::hex << t1.get_id() << std::endl; - t1.join(); - } -} - -#endif diff --git a/components/pthread/test/test_pthread_local_storage.c b/components/pthread/test/test_pthread_local_storage.c index 8be22ce5..0b7fe96a 100644 --- a/components/pthread/test/test_pthread_local_storage.c +++ b/components/pthread/test/test_pthread_local_storage.c @@ -1,5 +1,6 @@ // Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific #include +#include #include "unity.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h"