Merge branch 'feature/update_pthread_from_esp_idf' into 'master'

pthread: update pthread from esp-idf

See merge request sdk/ESP8266_RTOS_SDK!1072
This commit is contained in:
Dong Heng
2019-09-10 17:15:27 +08:00
19 changed files with 1051 additions and 181 deletions

View File

@ -103,6 +103,8 @@ to exclude the API function. */
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xSemaphoreGetMutexHolder 1
/* This is the raw value as per the Cortex-M3 NVIC. Values can be 255
(lowest) to 0 (1?) (highest). */
#define configKERNEL_INTERRUPT_PRIORITY 255

View File

@ -0,0 +1,40 @@
// 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.
#ifndef __ESP_PLATFORM_PTHREAD_H__
#define __ESP_PLATFORM_PTHREAD_H__
#include <sys/types.h>
#include <sys/time.h>
#include <sys/features.h>
// Remove this when GCC 5.2.0 is no longer supported
#ifndef _POSIX_TIMEOUTS
#define _POSIX_TIMEOUTS // For pthread_mutex_timedlock
#endif
#include_next <pthread.h>
#ifdef __cplusplus
extern "C" {
#endif
int pthread_condattr_getclock(const pthread_condattr_t * attr, clockid_t * clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
#ifdef __cplusplus
}
#endif
#endif // __ESP_PLATFORM_PTHREAD_H__

View File

@ -0,0 +1,35 @@
// 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.
#ifndef _ESP_TIME_H
#define _ESP_TIME_H
#ifdef __cplusplus
extern "C" {
#endif
#include_next <time.h>
#define _POSIX_TIMERS 1
#define CLOCK_MONOTONIC (clockid_t)4
#define CLOCK_BOOTTIME (clockid_t)4
int clock_settime(clockid_t clock_id, const struct timespec *tp);
int clock_gettime(clockid_t clock_id, struct timespec *tp);
int clock_getres(clockid_t clock_id, struct timespec *res);
#ifdef __cplusplus
}
#endif
#endif /* _ESP_TIME_H */

View File

@ -16,6 +16,7 @@
#include <reent.h>
#include <sys/times.h>
#include <sys/time.h>
#include <sys/errno.h>
#include "esp_system.h"
#include "esp_timer.h"
@ -106,3 +107,38 @@ unsigned int sleep(unsigned int seconds)
return 0;
}
int clock_gettime (clockid_t clock_id, struct timespec *tp)
{
#if defined( WITH_FRC ) || defined( WITH_RTC )
if (tp == NULL) {
errno = EINVAL;
return -1;
}
struct timeval tv;
uint64_t monotonic_time_us = 0;
switch (clock_id) {
case CLOCK_REALTIME:
_gettimeofday_r(NULL, &tv, NULL);
tp->tv_sec = tv.tv_sec;
tp->tv_nsec = tv.tv_usec * 1000L;
break;
case CLOCK_MONOTONIC:
#if defined( WITH_FRC )
monotonic_time_us = (uint64_t) esp_timer_get_time();
#elif defined( WITH_RTC )
monotonic_time_us = get_rtc_time_us();
#endif // WITH_FRC
tp->tv_sec = monotonic_time_us / 1000000LL;
tp->tv_nsec = (monotonic_time_us % 1000000LL) * 1000L;
break;
default:
errno = EINVAL;
return -1;
}
return 0;
#else
errno = ENOSYS;
return -1;
#endif
}

View File

@ -1,11 +1,23 @@
if(CONFIG_ENABLE_PTHREAD)
set(COMPONENT_SRCDIRS "src")
set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_REQUIRES)
idf_component_register(SRCS "pthread.c"
"pthread_cond_var.c"
"pthread_local_storage.c"
INCLUDE_DIRS include
PRIV_REQUIRES newlib)
if(GCC_NOT_5_2_0)
set(extra_link_flags "-u pthread_include_pthread_impl")
list(APPEND extra_link_flags "-u pthread_include_pthread_cond_impl")
list(APPEND extra_link_flags "-u pthread_include_pthread_local_storage_impl")
endif()
if(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
target_link_libraries(${COMPONENT_LIB} "-Wl,--wrap=vPortCleanUpTCB")
endif()
if(extra_link_flags)
target_link_libraries(${COMPONENT_LIB} INTERFACE "${extra_link_flags}")
endif()
else()
register_component()
if(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK)
target_link_libraries(pthread "-Wl,--wrap=vPortCleanUpTCB")
endif()
endif()

View File

@ -1,22 +1,37 @@
menu "PThreads"
config ENABLE_PTHREAD
bool "Enable pthread"
default n
help
Enable this option and then pthread is to be used.
config ENABLE_PTHREAD
bool "Enable Pthread"
help
Enable this option and then pthread is to be used.
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 PTHREAD_TASK_PRIO_DEFAULT
int "Default task priority"
range 0 255
default 5
depends on ENABLE_PTHREAD
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.
config PTHREAD_TASK_STACK_SIZE_DEFAULT
int "Default task stack size"
default 3072
depends on ENABLE_PTHREAD
help
Stack size used to create new tasks with default pthread parameters.
config PTHREAD_STACK_MIN
int "Minimum allowed pthread stack size"
default 768
depends on ENABLE_PTHREAD
help
Minimum allowed pthread stack size set in attributes passed to pthread_create
config PTHREAD_TASK_NAME_DEFAULT
string "Default name of pthreads"
default "pthread"
depends on ENABLE_PTHREAD
help
The default name of pthreads.
endmenu

View File

@ -3,13 +3,23 @@
#
ifdef CONFIG_ENABLE_PTHREAD
COMPONENT_SRCDIRS := src
endif
COMPONENT_SRCDIRS := .
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_ADD_LDFLAGS := -lpthread
ifdef CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK
ifdef CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP
COMPONENT_ADD_LDFLAGS += -Wl,--wrap=vPortCleanUpTCB
endif
ifeq ($(GCC_NOT_5_2_0), 1)
# Forces the linker to include pthread implementation from this component,
# instead of the weak implementations provided by libgcc.
COMPONENT_ADD_LDFLAGS += -u pthread_include_pthread_impl
COMPONENT_ADD_LDFLAGS += -u pthread_include_pthread_cond_impl
COMPONENT_ADD_LDFLAGS += -u pthread_include_pthread_local_storage_impl
endif # GCC_NOT_5_2_0
else
COMPONENT_SRCDIRS :=
endif

View File

@ -14,19 +14,35 @@
#pragma once
#include "esp_err.h"
#include <freertos/FreeRTOSConfig.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN CONFIG_PTHREAD_STACK_MIN
#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(void);
/**
* @brief Configure parameters for creating pthread
*
@ -39,11 +55,15 @@ typedef struct {
* then the same configuration is also inherited in the thread
* subtree.
*
* @note Passing non-NULL attributes to pthread_create() will override
* the stack_size parameter set using this API
*
* @param cfg The pthread config parameters
*
* @return
* - ESP_OK if configuration was successfully set
* - ESP_ERR_NO_MEM if out of memory
* - ESP_ERR_INVALID_ARG if stack_size is less than PTHREAD_STACK_MIN
*/
esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg);

View File

@ -1,4 +1,4 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
// 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.
@ -14,18 +14,15 @@
//
// 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 <time.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include "esp_err.h"
#include "esp_attr.h"
#include "sys/queue.h"
#include "sys/types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
@ -33,16 +30,6 @@
#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";
@ -60,6 +47,8 @@ typedef struct esp_pthread_entry {
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
void *retval; ///< Value supplied to calling thread during join
void *task_arg; ///< Task arguments
} esp_pthread_t;
/** pthread wrapper task arg */
@ -77,9 +66,6 @@ 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;
@ -87,14 +73,6 @@ static pthread_key_t s_pthread_cfg_key;
static int 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);
@ -107,7 +85,7 @@ esp_err_t esp_pthread_init(void)
}
s_threads_mux = xSemaphoreCreateMutex();
if (s_threads_mux == NULL) {
pthread_key_delete(s_pthread_cfg_key);
pthread_key_delete(s_pthread_cfg_key);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
@ -157,17 +135,20 @@ 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)
{
if (cfg->stack_size < PTHREAD_STACK_MIN) {
return ESP_ERR_INVALID_ARG;
}
/* 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 = malloc(sizeof(esp_pthread_cfg_t));
if (!p) {
return ESP_ERR_NO_MEM;
}
}
*p = *cfg;
pthread_setspecific(s_pthread_cfg_key, p);
@ -178,57 +159,55 @@ 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;
*p = *cfg;
return ESP_OK;
}
memset(p, 0, sizeof(*p));
return ESP_ERR_NOT_FOUND;
}
static int get_default_pthread_core(void)
{
return 0;
}
esp_pthread_cfg_t esp_pthread_get_default_config(void)
{
esp_pthread_cfg_t cfg = {
.stack_size = CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT,
.prio = CONFIG_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;
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);
/* 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);
task_arg->func(task_arg->arg);
rval = 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 = %lu", uxTaskGetStackHighWaterMark(NULL));
vTaskDelete(NULL);
pthread_exit(rval);
ESP_LOGV(TAG, "%s EXIT", __FUNCTION__);
}
@ -239,40 +218,79 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
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));
esp_pthread_task_arg_t *task_arg = calloc(1, 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));
esp_pthread_t *pthread = calloc(1, 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;
uint32_t stack_size = CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT;
BaseType_t prio = CONFIG_PTHREAD_TASK_PRIO_DEFAULT;
const char *task_name = CONFIG_PTHREAD_TASK_NAME_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;
if (pthread_cfg->stack_size) {
stack_size = pthread_cfg->stack_size;
}
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_PTHREAD_TASK_NAME_DEFAULT;
} else {
task_name = pthread_cfg->thread_name;
}
task_arg->cfg = *pthread_cfg;
}
memset(pthread, 0, sizeof(esp_pthread_t));
if (attr) {
/* Overwrite attributes */
stack_size = attr->stacksize;
switch (attr->detachstate) {
case PTHREAD_CREATE_DETACHED:
pthread->detached = true;
break;
case PTHREAD_CREATE_JOINABLE:
default:
pthread->detached = false;
}
}
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) {
pthread->task_arg = task_arg;
BaseType_t res = xTaskCreatePinnedToCore(&pthread_task_func,
task_name,
// stack_size is in bytes. This transformation ensures that the units are
// transformed to the units used in FreeRTOS.
// Note: float division of ceil(m / n) ==
// integer division of (m + n - 1) / n
(stack_size + sizeof(StackType_t) - 1) / sizeof(StackType_t),
task_arg,
prio,
&xHandle,
0);
if (res != pdPASS) {
ESP_LOGE(TAG, "Failed to create task!");
free(pthread);
free(task_arg);
@ -305,6 +323,7 @@ int pthread_join(pthread_t thread, void **retval)
esp_pthread_t *pthread = (esp_pthread_t *)thread;
int ret = 0;
bool wait = false;
void *child_task_retval = 0;
ESP_LOGV(TAG, "%s %p", __FUNCTION__, pthread);
@ -316,6 +335,9 @@ int pthread_join(pthread_t thread, void **retval)
if (!handle) {
// not found
ret = ESRCH;
} else if (pthread->detached) {
// Thread is detached
ret = EDEADLK;
} else if (pthread->join_task) {
// already have waiting task to join
ret = EINVAL;
@ -332,23 +354,28 @@ int pthread_join(pthread_t thread, void **retval)
pthread->join_task = xTaskGetCurrentTaskHandle();
wait = true;
} else {
child_task_retval = pthread->retval;
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!");
if (ret == 0) {
if (wait) {
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) {
assert(false && "Failed to lock threads list!");
}
child_task_retval = pthread->retval;
pthread_delete(pthread);
xSemaphoreGive(s_threads_mux);
}
pthread_delete(pthread);
xSemaphoreGive(s_threads_mux);
vTaskDelete(handle);
}
if (retval) {
*retval = 0; // no exit code in FreeRTOS
*retval = child_task_retval;
}
ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret);
@ -366,14 +393,71 @@ int pthread_detach(pthread_t thread)
TaskHandle_t handle = pthread_find_handle(thread);
if (!handle) {
ret = ESRCH;
} else {
} else if (pthread->detached) {
// already detached
ret = EINVAL;
} else if (pthread->join_task) {
// already have waiting task to join
ret = EINVAL;
} else if (pthread->state == PTHREAD_TASK_STATE_RUN) {
// pthread still running
pthread->detached = true;
} else {
// pthread already stopped
pthread_delete(pthread);
vTaskDelete(handle);
}
xSemaphoreGive(s_threads_mux);
ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret);
return ret;
}
void pthread_exit(void *value_ptr)
{
bool detached = false;
/* 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->task_arg) {
free(pthread->task_arg);
}
if (pthread->detached) {
// auto-free for detached threads
pthread_delete(pthread);
detached = true;
} else {
// Set return value
pthread->retval = value_ptr;
// 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 = %ld", uxTaskGetStackHighWaterMark(NULL));
if (detached) {
vTaskDelete(NULL);
} else {
vTaskSuspend(NULL);
}
// Should never be reached
abort();
}
int pthread_cancel(pthread_t thread)
{
ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__);
@ -412,21 +496,7 @@ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
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();
}
once_control->init_executed = 1;
return 0;
}
@ -434,7 +504,9 @@ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
/***************** MUTEX ******************/
static int mutexattr_check(const pthread_mutexattr_t *attr)
{
if (attr->type < PTHREAD_MUTEX_NORMAL || attr->type > PTHREAD_MUTEX_RECURSIVE) {
if (attr->type != PTHREAD_MUTEX_NORMAL &&
attr->type != PTHREAD_MUTEX_RECURSIVE &&
attr->type != PTHREAD_MUTEX_ERRORCHECK) {
return EINVAL;
}
return 0;
@ -490,6 +562,9 @@ int pthread_mutex_destroy(pthread_mutex_t *mutex)
return EINVAL;
}
mux = (esp_pthread_mutex_t *)*mutex;
if (!mux) {
return EINVAL;
}
// check if mux is busy
int res = pthread_mutex_lock_internal(mux, 0);
@ -505,6 +580,15 @@ int pthread_mutex_destroy(pthread_mutex_t *mutex)
static int pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo)
{
if (!mux) {
return EINVAL;
}
if ((mux->type == PTHREAD_MUTEX_ERRORCHECK) &&
(xSemaphoreGetMutexHolder(mux->sem) == xTaskGetCurrentTaskHandle())) {
return EDEADLK;
}
if (mux->type == PTHREAD_MUTEX_RECURSIVE) {
if (xSemaphoreTakeRecursive(mux->sem, tmo) != pdTRUE) {
return EBUSY;
@ -518,14 +602,15 @@ static int pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo)
return 0;
}
static int pthread_mutex_init_if_static(pthread_mutex_t *mutex) {
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);
portENTER_CRITICAL();
if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) {
res = pthread_mutex_init(mutex, NULL);
}
portEXIT_CRITICAL(&s_mutex_init_lock);
portEXIT_CRITICAL();
}
return res;
}
@ -542,6 +627,28 @@ int pthread_mutex_lock(pthread_mutex_t *mutex)
return pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, portMAX_DELAY);
}
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *timeout)
{
if (!mutex) {
return EINVAL;
}
int res = pthread_mutex_init_if_static(mutex);
if (res != 0) {
return res;
}
struct timespec currtime;
clock_gettime(CLOCK_REALTIME, &currtime);
TickType_t tmo = ((timeout->tv_sec - currtime.tv_sec)*1000 +
(timeout->tv_nsec - currtime.tv_nsec)/1000000)/portTICK_PERIOD_MS;
res = pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, tmo);
if (res == EBUSY) {
return ETIMEDOUT;
}
return res;
}
int pthread_mutex_trylock(pthread_mutex_t *mutex)
{
if (!mutex) {
@ -562,11 +669,24 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex)
return EINVAL;
}
mux = (esp_pthread_mutex_t *)*mutex;
if (!mux) {
return EINVAL;
}
if (((mux->type == PTHREAD_MUTEX_RECURSIVE) ||
(mux->type == PTHREAD_MUTEX_ERRORCHECK)) &&
(xSemaphoreGetMutexHolder(mux->sem) != xTaskGetCurrentTaskHandle())) {
return EPERM;
}
int ret;
if (mux->type == PTHREAD_MUTEX_RECURSIVE) {
xSemaphoreGiveRecursive(mux->sem);
ret = xSemaphoreGiveRecursive(mux->sem);
} else {
xSemaphoreGive(mux->sem);
ret = xSemaphoreGive(mux->sem);
}
if (ret != pdTRUE) {
assert(false && "Failed to unlock mutex!");
}
return 0;
}
@ -592,8 +712,11 @@ int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)
{
ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__);
return ENOSYS;
if (!attr) {
return EINVAL;
}
*type = attr->type;
return 0;
}
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
@ -609,4 +732,75 @@ int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
return res;
}
#endif
/***************** ATTRIBUTES ******************/
int pthread_attr_init(pthread_attr_t *attr)
{
if (attr) {
/* Nothing to allocate. Set everything to default */
attr->stacksize = CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT;
attr->detachstate = PTHREAD_CREATE_JOINABLE;
return 0;
}
return EINVAL;
}
int pthread_attr_destroy(pthread_attr_t *attr)
{
if (attr) {
/* Nothing to deallocate. Reset everything to default */
attr->stacksize = CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT;
attr->detachstate = PTHREAD_CREATE_JOINABLE;
return 0;
}
return EINVAL;
}
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
{
if (attr) {
*stacksize = attr->stacksize;
return 0;
}
return EINVAL;
}
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
{
if (attr && !(stacksize < PTHREAD_STACK_MIN)) {
attr->stacksize = stacksize;
return 0;
}
return EINVAL;
}
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
{
if (attr) {
*detachstate = attr->detachstate;
return 0;
}
return EINVAL;
}
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
{
if (attr) {
switch (detachstate) {
case PTHREAD_CREATE_DETACHED:
attr->detachstate = PTHREAD_CREATE_DETACHED;
break;
case PTHREAD_CREATE_JOINABLE:
attr->detachstate = PTHREAD_CREATE_JOINABLE;
break;
default:
return EINVAL;
}
return 0;
}
return EINVAL;
}
/* Hook function to force linking this file */
void pthread_include_pthread_impl(void)
{
}

View File

@ -20,19 +20,15 @@
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include "esp_err.h"
#include "esp_attr.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/private/list.h"
#include <sys/queue.h>
#include <sys/time.h>
#ifdef CONFIG_ENABLE_PTHREAD
#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include "esp_log.h"
const static char *TAG = "esp_pthread";
@ -202,4 +198,7 @@ int pthread_cond_destroy(pthread_cond_t *cv)
return ret;
}
#endif
/* Hook function to force linking this file */
void pthread_include_pthread_cond_var_impl(void)
{
}

View File

@ -13,4 +13,4 @@
// limitations under the License.
#pragma once
void pthread_internal_local_storage_destructor_callback();
void pthread_internal_local_storage_destructor_callback(void);

View File

@ -14,7 +14,6 @@
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
@ -24,18 +23,8 @@
#include "pthread_internal.h"
#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*);
/* This is a very naive implementation of key-indexed thread local storage, using two linked lists
@ -53,10 +42,6 @@ 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_ {
pthread_key_t key;
@ -75,7 +60,7 @@ int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
return ENOMEM;
}
portENTER_CRITICAL(&s_keys_lock);
portENTER_CRITICAL();
const key_entry_t *head = SLIST_FIRST(&s_keys);
new_key->key = (head == NULL) ? 1 : (head->key + 1);
@ -84,27 +69,27 @@ int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
SLIST_INSERT_HEAD(&s_keys, new_key, next);
portEXIT_CRITICAL(&s_keys_lock);
portEXIT_CRITICAL();
return 0;
}
static key_entry_t *find_key(pthread_key_t key)
{
portENTER_CRITICAL(&s_keys_lock);
portENTER_CRITICAL();
key_entry_t *result = NULL;;
SLIST_FOREACH(result, &s_keys, next) {
if(result->key == key) {
break;
}
}
portEXIT_CRITICAL(&s_keys_lock);
portEXIT_CRITICAL();
return result;
}
int pthread_key_delete(pthread_key_t key)
{
portENTER_CRITICAL(&s_keys_lock);
portENTER_CRITICAL();
/* 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...
@ -116,7 +101,7 @@ int pthread_key_delete(pthread_key_t key)
free(entry);
}
portEXIT_CRITICAL(&s_keys_lock);
portEXIT_CRITICAL();
return 0;
}
@ -155,7 +140,7 @@ static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls
free(tls);
}
#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK)
#if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
/* Called from FreeRTOS task delete hook */
void pthread_local_storage_cleanup(TaskHandle_t task)
{
@ -179,7 +164,7 @@ void __wrap_vPortCleanUpTCB(void *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 pthread_internal_local_storage_destructor_callback(void)
{
void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
if (tls != NULL) {
@ -187,7 +172,7 @@ void pthread_internal_local_storage_destructor_callback()
/* remove the thread-local-storage pointer to avoid the idle task cleanup
calling it again...
*/
#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK)
#if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL);
#else
vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
@ -236,7 +221,7 @@ int pthread_setspecific(pthread_key_t key, const void *value)
if (tls == NULL) {
return ENOMEM;
}
#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK)
#if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls);
#else
vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
@ -269,4 +254,7 @@ int pthread_setspecific(pthread_key_t key, const void *value)
return 0;
}
#endif
/* Hook function to force linking this file */
void pthread_include_pthread_local_storage_impl(void)
{
}

View File

@ -0,0 +1,11 @@
# sdkconfig replacement configurations for deprecated options formatted as
# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT CONFIG_PTHREAD_TASK_PRIO_DEFAULT
CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT
CONFIG_ESP32_PTHREAD_STACK_MIN CONFIG_PTHREAD_STACK_MIN
CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT CONFIG_PTHREAD_TASK_CORE_DEFAULT
CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY
CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 CONFIG_PTHREAD_DEFAULT_CORE_0
CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 CONFIG_PTHREAD_DEFAULT_CORE_1
CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT CONFIG_PTHREAD_TASK_NAME_DEFAULT

View File

@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES unity test_utils pthread)

View File

@ -0,0 +1,48 @@
#include <iostream>
#include <thread>
#include <condition_variable>
#include <chrono>
#include <mutex>
#include <atomic>
#include "unity.h"
#if __GTHREADS && __GTHREADS_CXX0X
std::condition_variable cv;
std::mutex cv_m;
std::atomic<int> i{0};
static void waits(int idx, int timeout_ms)
{
std::unique_lock<std::mutex> 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

View File

@ -0,0 +1,31 @@
#include <iostream>
#include <future>
#include <thread>
#include "unity.h"
#if __GTHREADS && __GTHREADS_CXX0X
TEST_CASE("C++ future", "[std::future]")
{
// future from a packaged_task
std::packaged_task<int()> task([]{ return 7; }); // wrap the function
std::future<int> f1 = task.get_future(); // get a future
std::thread t(std::move(task)); // launch on a thread
// future from an async()
std::future<int> f2 = std::async(std::launch::async, []{ return 8; });
// future from a promise
std::promise<int> p;
std::future<int> 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

View File

@ -0,0 +1,294 @@
#include <errno.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_pthread.h"
#include <pthread.h>
#include "unity.h"
static void *compute_square(void *arg)
{
int *num = (int *) arg;
*num = (*num) * (*num);
pthread_exit((void *) num);
return NULL;
}
TEST_CASE("pthread create join", "[pthread]")
{
int res = 0;
volatile int num = 7;
volatile bool attr_init = false;
void *thread_rval = NULL;
pthread_t new_thread = (pthread_t)NULL;
pthread_attr_t attr;
if (TEST_PROTECT()) {
res = pthread_attr_init(&attr);
TEST_ASSERT_EQUAL_INT(0, res);
attr_init = true;
res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_create(&new_thread, &attr, compute_square, (void *) &num);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_join(new_thread, &thread_rval);
TEST_ASSERT_EQUAL_INT(EDEADLK, res);
vTaskDelay(100 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL_INT(49, num);
res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_create(&new_thread, &attr, compute_square, (void *) &num);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_join(new_thread, &thread_rval);
TEST_ASSERT_EQUAL_INT(0, res);
TEST_ASSERT_EQUAL_INT(2401, num);
TEST_ASSERT_EQUAL_PTR(&num, thread_rval);
}
if (attr_init) {
pthread_attr_destroy(&attr);
}
}
static void *waiting_thread(void *arg)
{
TaskHandle_t *task_handle = (TaskHandle_t *)arg;
TaskHandle_t parent_task = *task_handle;
*task_handle = xTaskGetCurrentTaskHandle();
xTaskNotify(parent_task, 0, eNoAction);
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
return NULL;
}
TEST_CASE("pthread detach", "[pthread]")
{
int res = 0;
pthread_t new_thread = (pthread_t)NULL;
TaskHandle_t task_handle = NULL;
const int task_count = uxTaskGetNumberOfTasks();
bool detach_works = false;
if (TEST_PROTECT()) {
task_handle = xTaskGetCurrentTaskHandle();
res = pthread_create(&new_thread, NULL, waiting_thread, (void *)&task_handle);
TEST_ASSERT_EQUAL_INT(0, res);
res = xTaskNotifyWait(0, 0, NULL, 100 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL_INT(pdTRUE, res);
xTaskNotify(task_handle, 0, eNoAction);
vTaskDelay(100 / portTICK_PERIOD_MS);
res = pthread_detach(new_thread);
TEST_ASSERT_EQUAL_INT(0, res);
res = uxTaskGetNumberOfTasks();
TEST_ASSERT_EQUAL_INT(task_count, res);
detach_works = true;
}
if (!detach_works) {
vTaskDelete(task_handle);
} else {
detach_works = false;
}
if (TEST_PROTECT()) {
task_handle = xTaskGetCurrentTaskHandle();
res = pthread_create(&new_thread, NULL, waiting_thread, (void *)&task_handle);
TEST_ASSERT_EQUAL_INT(0, res);
res = xTaskNotifyWait(0, 0, NULL, 100 / portTICK_PERIOD_MS);
TEST_ASSERT_EQUAL_INT(pdTRUE, res);
res = pthread_detach(new_thread);
TEST_ASSERT_EQUAL_INT(0, res);
xTaskNotify(task_handle, 0, eNoAction);
vTaskDelay(100 / portTICK_PERIOD_MS);
res = uxTaskGetNumberOfTasks();
TEST_ASSERT_EQUAL_INT(task_count, res);
detach_works = true;
}
if (!detach_works) {
vTaskDelete(task_handle);
}
}
TEST_CASE("pthread attr init destroy", "[pthread]")
{
int res = 0;
size_t stack_size_1 = 0, stack_size_2 = 0;
volatile bool attr_init = pdFALSE;
pthread_attr_t attr;
if (TEST_PROTECT()) {
res = pthread_attr_init(&attr);
TEST_ASSERT_EQUAL_INT(0, res);
attr_init = true;
res = pthread_attr_getstacksize(&attr, &stack_size_1);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_attr_setstacksize(&attr, stack_size_1);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_attr_getstacksize(&attr, &stack_size_2);
TEST_ASSERT_EQUAL_INT(0, res);
TEST_ASSERT_EQUAL_INT(stack_size_2, stack_size_1);
stack_size_1 = PTHREAD_STACK_MIN - 1;
res = pthread_attr_setstacksize(&attr, stack_size_1);
TEST_ASSERT_EQUAL_INT(EINVAL, res);
}
if (attr_init) {
TEST_ASSERT_EQUAL_INT(0, pthread_attr_destroy(&attr));
}
}
static void *unlock_mutex(void *arg)
{
pthread_mutex_t *mutex = (pthread_mutex_t *) arg;
intptr_t res = (intptr_t) pthread_mutex_unlock(mutex);
pthread_exit((void *) res);
return NULL;
}
static void test_mutex_lock_unlock(int mutex_type)
{
int res = 0;
int set_type = -1;
volatile bool attr_created = false;
volatile bool mutex_created = false;
volatile intptr_t thread_rval = 0;
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_t new_thread;
if (TEST_PROTECT()) {
res = pthread_mutexattr_init(&attr);
TEST_ASSERT_EQUAL_INT(0, res);
attr_created = true;
res = pthread_mutexattr_settype(&attr, mutex_type);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_mutexattr_gettype(&attr, &set_type);
TEST_ASSERT_EQUAL_INT(0, res);
TEST_ASSERT_EQUAL_INT(mutex_type, set_type);
res = pthread_mutex_init(&mutex, &attr);
TEST_ASSERT_EQUAL_INT(0, res);
mutex_created = true;
res = pthread_mutex_lock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_mutex_lock(&mutex);
if(mutex_type == PTHREAD_MUTEX_ERRORCHECK) {
TEST_ASSERT_EQUAL_INT(EDEADLK, res);
} else {
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_mutex_unlock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
}
pthread_create(&new_thread, NULL, unlock_mutex, &mutex);
pthread_join(new_thread, (void **) &thread_rval);
TEST_ASSERT_EQUAL_INT(EPERM, (int) thread_rval);
res = pthread_mutex_unlock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
}
if (attr_created) {
pthread_mutexattr_destroy(&attr);
}
if (mutex_created) {
pthread_mutex_destroy(&mutex);
}
}
TEST_CASE("pthread mutex lock unlock", "[pthread]")
{
int res = 0;
/* Present behavior of mutex initializer is unlike what is
* defined in Posix standard, ie. calling pthread_mutex_lock
* on such a mutex would internally cause dynamic allocation.
* Therefore pthread_mutex_destroy needs to be called in
* order to avoid memory leak. */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
res = pthread_mutex_lock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_mutex_unlock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
/* This deviates from the Posix standard static mutex behavior.
* This needs to be removed in the future when standard mutex
* initializer is supported */
pthread_mutex_destroy(&mutex);
test_mutex_lock_unlock(PTHREAD_MUTEX_ERRORCHECK);
test_mutex_lock_unlock(PTHREAD_MUTEX_RECURSIVE);
}
static void timespec_add_nano(struct timespec * out, struct timespec * in, long val)
{
out->tv_nsec = val + in->tv_nsec;
if (out->tv_nsec < (in->tv_nsec)) {
out->tv_sec += 1;
}
}
TEST_CASE("pthread mutex trylock timedlock", "[pthread]")
{
int res = 0;
volatile bool mutex_created = false;
pthread_mutex_t mutex;
struct timespec abs_timeout;
if (TEST_PROTECT()) {
res = pthread_mutex_init(&mutex, NULL);
TEST_ASSERT_EQUAL_INT(0, res);
mutex_created = true;
res = pthread_mutex_trylock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
res = pthread_mutex_trylock(&mutex);
TEST_ASSERT_EQUAL_INT(EBUSY, res);
clock_gettime(CLOCK_REALTIME, &abs_timeout);
timespec_add_nano(&abs_timeout, &abs_timeout, 100000000LL);
res = pthread_mutex_timedlock(&mutex, &abs_timeout);
TEST_ASSERT_EQUAL_INT(ETIMEDOUT, res);
res = pthread_mutex_unlock(&mutex);
TEST_ASSERT_EQUAL_INT(0, res);
}
if (mutex_created) {
pthread_mutex_destroy(&mutex);
}
}

View File

@ -0,0 +1,132 @@
#include <iostream>
#include <sstream>
#include <thread>
#include <mutex>
#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<int> 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

View File

@ -1,9 +1,9 @@
// Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific
#include <pthread.h>
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "test_utils.h"
TEST_CASE("pthread local storage basics", "[pthread]")
{