From 45c8b8ba10ab1fd2a3b3438ef85f143e7c5d30a0 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Tue, 28 Feb 2023 19:11:12 +0800 Subject: [PATCH] pthread: Implemented POSIX unnamed semaphore --- components/pthread/CMakeLists.txt | 4 +- components/pthread/include/semaphore.h | 73 +++ components/pthread/pthread_semaphore.c | 177 ++++++++ .../pthread_unity_tests/main/CMakeLists.txt | 3 +- .../pthread_unity_tests/main/test_app_main.c | 6 +- .../main/test_pthread_semaphore.c | 417 ++++++++++++++++++ docs/en/api-reference/system/pthread.rst | 25 +- 7 files changed, 699 insertions(+), 6 deletions(-) create mode 100644 components/pthread/include/semaphore.h create mode 100644 components/pthread/pthread_semaphore.c create mode 100644 components/pthread/test_apps/pthread_unity_tests/main/test_pthread_semaphore.c diff --git a/components/pthread/CMakeLists.txt b/components/pthread/CMakeLists.txt index 66c700dabe..7b3daaab5a 100644 --- a/components/pthread/CMakeLists.txt +++ b/components/pthread/CMakeLists.txt @@ -11,7 +11,8 @@ endif() set(sources "pthread.c" "pthread_cond_var.c" "pthread_local_storage.c" - "pthread_rwlock.c") + "pthread_rwlock.c" + "pthread_semaphore.c") idf_component_register(SRCS ${sources} INCLUDE_DIRS include) @@ -22,6 +23,7 @@ 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") list(APPEND extra_link_flags "-u pthread_include_pthread_rwlock_impl") +list(APPEND extra_link_flags "-u pthread_include_pthread_semaphore_impl") if(extra_link_flags) target_link_libraries(${COMPONENT_LIB} INTERFACE "${extra_link_flags}") diff --git a/components/pthread/include/semaphore.h b/components/pthread/include/semaphore.h new file mode 100644 index 0000000000..5a7ef56b97 --- /dev/null +++ b/components/pthread/include/semaphore.h @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int sem_t; + +/** + * This is the maximum value to which any POSIX semaphore can count on ESP chips. + */ +#define SEM_VALUE_MAX 0x7FFF + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + * + * Must NOT be called if threads are still blocked on semaphore! + */ +int sem_destroy(sem_t *sem); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + * + * Note that on ESP chips, pshared is ignored. Semaphores can always be shared between FreeRTOS tasks. + */ +int sem_init(sem_t *sem, int pshared, unsigned value); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + * + * Note that, unlike specified in POSIX, this implementation returns -1 and sets errno to + * EAGAIN if the semaphore can not be unlocked (posted) due to its value being SEM_VALUE_MAX. + */ +int sem_post(sem_t *sem); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + * + * Note the following three deviations/issues originating from the underlying FreeRTOS implementation: + * * The time value passed by abstime will be rounded up to the next FreeRTOS tick. + * * The actual timeout will happen after the tick the time was rounded to + * and before the following tick. + * * It is possible, though unlikely, that the task is preempted directly after the timeout calculation, + * delaying timeout of the following blocking operating system call by the duration of the preemption. + */ +int sem_timedwait(sem_t * restrict semaphore, const struct timespec *restrict abstime); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + */ +int sem_trywait(sem_t *sem); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + */ +int sem_wait(sem_t *sem); + +/** + * This is a POSIX function, please refer to the POSIX specification for a detailed description. + */ +int sem_getvalue(sem_t *restrict sem, int *restrict sval); + +#ifdef __cplusplus +} +#endif diff --git a/components/pthread/pthread_semaphore.c b/components/pthread/pthread_semaphore.c new file mode 100644 index 0000000000..ca030db65e --- /dev/null +++ b/components/pthread/pthread_semaphore.c @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "semaphore.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define BLN 1000000000 +#define MIO 1000000 + +// sem_t is used to store SemaphoreHandle_t, so make sure there is enough space +static_assert(sizeof(sem_t) == sizeof(SemaphoreHandle_t)); + +int sem_destroy(sem_t * semaphore) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + vSemaphoreDelete(freertos_semaphore); + return 0; +} + +int sem_init(sem_t * semaphore, int pshared, unsigned value) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + if (value > SEM_VALUE_MAX) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore; + freertos_semaphore = xSemaphoreCreateCounting(SEM_VALUE_MAX, value); + + if (freertos_semaphore == NULL) { + errno = ENOSPC; + return -1; + } + + *semaphore = (sem_t) freertos_semaphore; + return 0; +} + +int sem_post(sem_t * semaphore) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + BaseType_t ret = xSemaphoreGive(freertos_semaphore); + + if (ret == pdFALSE) { + errno = EAGAIN; + return -1; + } + + return 0; +} + +int sem_timedwait(sem_t * restrict semaphore, const struct timespec *restrict abstime) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + if (abstime == NULL || abstime->tv_nsec >= (1 * BLN) || abstime->tv_nsec < 0) { + errno = EINVAL; + return -1; + } + + TickType_t timeout_ticks; + struct timespec cur_time; + clock_gettime(CLOCK_REALTIME, &cur_time); + + if (timespeccmp(abstime, &cur_time, <)) { + timeout_ticks = 0; + } else { + struct timespec diff_time; + timespecsub(abstime, &cur_time, &diff_time); + + long timeout_msec; + // Round up timeout nanoseconds to the next millisecond + timeout_msec = (diff_time.tv_sec * 1000) + + ((diff_time.tv_nsec + (1 * MIO) - 1) / (1 * MIO)); + + // Round up milliseconds to the next tick + timeout_ticks = (timeout_msec + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; + + /* We have to add 1 more tick of delay + + The reason for this is that vTaskDelay(1) will sleep until the start of the next tick, + which can be any amount of time up to one tick period. So if we don't add one more tick, + we're likely to timeout a small time (< 1 tick period) before the requested timeout. + If we add 1 tick then we will timeout a small time (< 1 tick period) after the + requested timeout. + */ + timeout_ticks += 1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + BaseType_t sem_take_result; + sem_take_result = xSemaphoreTake(freertos_semaphore, timeout_ticks); + if (sem_take_result == pdFALSE) { + errno = ETIMEDOUT; + return -1; + } + + return 0; +} + +int sem_trywait(sem_t * semaphore) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + + BaseType_t ret = xSemaphoreTake(freertos_semaphore, 0); + + if (ret == pdFALSE) { + errno = EAGAIN; + return -1; + } + + return 0; +} + +int sem_wait(sem_t * semaphore) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + + // Only returns failure if block time expires, but we block indefinitely, hence not return code check + xSemaphoreTake(freertos_semaphore, portMAX_DELAY); + return 0; +} + +int sem_getvalue(sem_t *restrict semaphore, int *restrict sval) +{ + if (semaphore == NULL) { + errno = EINVAL; + return -1; + } + + if (sval == NULL) { + errno = EINVAL; + return -1; + } + + SemaphoreHandle_t freertos_semaphore = (SemaphoreHandle_t) *semaphore; + *sval = uxSemaphoreGetCount(freertos_semaphore); + return 0; +} + +/* Hook function to force linking this file */ +void pthread_include_pthread_semaphore_impl(void) { } diff --git a/components/pthread/test_apps/pthread_unity_tests/main/CMakeLists.txt b/components/pthread/test_apps/pthread_unity_tests/main/CMakeLists.txt index 80ad2a8722..f7a98a39ae 100644 --- a/components/pthread/test_apps/pthread_unity_tests/main/CMakeLists.txt +++ b/components/pthread/test_apps/pthread_unity_tests/main/CMakeLists.txt @@ -3,7 +3,8 @@ set(sources "test_app_main.c" "test_pthread_cond_var.c" "test_pthread_local_storage.c" "test_pthread_cxx.cpp" - "test_pthread_rwlock.c") + "test_pthread_rwlock.c" + "test_pthread_semaphore.c") idf_component_register(SRCS ${sources} INCLUDE_DIRS "." diff --git a/components/pthread/test_apps/pthread_unity_tests/main/test_app_main.c b/components/pthread/test_apps/pthread_unity_tests/main/test_app_main.c index 5ef64d0c35..2f34948bea 100644 --- a/components/pthread/test_apps/pthread_unity_tests/main/test_app_main.c +++ b/components/pthread/test_apps/pthread_unity_tests/main/test_app_main.c @@ -1,9 +1,10 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unity.h" @@ -28,10 +29,9 @@ void setUp(void) { before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + errno = 0; } - - void tearDown(void) { // Add a short delay of 100ms to allow the idle task to free an remaining memory diff --git a/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_semaphore.c b/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_semaphore.c new file mode 100644 index 0000000000..a4bb84b09e --- /dev/null +++ b/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_semaphore.c @@ -0,0 +1,417 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include + +#define SHARED 0 + +#include +#include +#include +#include +#include + +#include "unity.h" + +// This test is actually not mentioned in the standard, but it's good IDF practice +TEST_CASE("sem_init nullptr", "[semaphore]") +{ + TEST_ASSERT_EQUAL_INT(-1, sem_init(NULL, SHARED, 0)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +TEST_CASE("sem_init semaphore value exceeded", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(-1, sem_init(&semaphore, SHARED, SEM_VALUE_MAX + 1)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +// This test is actually not mentioned in the standard, but it's good IDF practice +TEST_CASE("sem_destroy nullptr", "[semaphore]") +{ + TEST_ASSERT_EQUAL_INT(-1, sem_destroy(NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +TEST_CASE("sem_init and destroy work correctly", "[semaphore]") +{ + sem_t semaphore; + memset(&semaphore, 0, sizeof(semaphore)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + TEST_ASSERT(semaphore); + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_wait nulltpr", "[semaphore]") +{ + TEST_ASSERT_EQUAL_INT(-1, sem_wait(NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +TEST_CASE("sem_post nulltpr", "[semaphore]") +{ + TEST_ASSERT_EQUAL_INT(-1, sem_post(NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +TEST_CASE("lock and unlock semaphore", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + + TEST_ASSERT_EQUAL_INT(0, sem_wait(&semaphore)); + TEST_ASSERT_EQUAL_INT(0, sem_post(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_post posts up to SEM_VALUE_MAX times", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, SEM_VALUE_MAX - 1)); + + TEST_ASSERT_EQUAL_INT(0, sem_post(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +// This test is actually not mentioned in the standard +TEST_CASE("sem_post fails on semaphore with SEM_VALUE_MAX", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, SEM_VALUE_MAX)); + + TEST_ASSERT_EQUAL_INT(-1, sem_post(&semaphore)); + TEST_ASSERT_EQUAL_INT(EAGAIN, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_wait waits on semaphore with SEM_VALUE_MAX", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, SEM_VALUE_MAX)); + + TEST_ASSERT_EQUAL_INT(0, sem_wait(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_trywait nulltpr", "[semaphore]") +{ + TEST_ASSERT_EQUAL_INT(-1, sem_post(NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +TEST_CASE("sem_trywait on semaphore initialized in locked state", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + + TEST_ASSERT_EQUAL_INT(-1, sem_trywait(&semaphore)); + TEST_ASSERT_EQUAL_INT(EAGAIN, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_trywait on locked semaphore", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + TEST_ASSERT_EQUAL_INT(0, sem_wait(&semaphore)); + + TEST_ASSERT_EQUAL_INT(-1, sem_trywait(&semaphore)); + TEST_ASSERT_EQUAL_INT(EAGAIN, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_post(&semaphore)); + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait fails with semaphore null", "[semaphore]") +{ + struct timespec abstime = { .tv_sec = 0, .tv_nsec = 0 }; + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(NULL, &abstime)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +// This test is actually not mentioned in the standard, but it's good IDF practice +TEST_CASE("sem_timedwait fails when abstime null, locked semaphore", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +// This test is actually not necessary but OK according to the standard, it simplifies implementation +TEST_CASE("sem_timedwait fails when abstime null, unlocked semaphore", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait fails with tv_nsec >= 1 bln, locked semaphore", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + abstime.tv_sec = abstime.tv_sec + 1; // set the time to the future + abstime.tv_nsec = 1000000000; // make tv_nsec invalid + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +// This test is actually not mentioned but OK according to the standard, it simplifies implementation +TEST_CASE("sem_timedwait fails with tv_nsec >= 1 bln, unlocked semaphore", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + abstime.tv_sec = abstime.tv_sec + 1; // set the time to the future + abstime.tv_nsec = 1000000000; // make tv_nsec invalid + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait fails with tv_nsec < 0, locked semaphore", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + abstime.tv_sec = abstime.tv_sec + 2; // set the time to the future + abstime.tv_nsec = -1; // make tv_nsec invalid + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +// This test is actually not mentioned but OK according to the standard, it simplifies implementation +TEST_CASE("sem_timedwait fails with tv_nsec < 0, unlocked semaphore", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + abstime.tv_sec = abstime.tv_sec + 2; // set the time to the future + abstime.tv_nsec = -1; // make tv_nsec invalid + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait lock locked semaphore with tv_nsec 0", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + if (abstime.tv_nsec > 900000000) { // ~ ten ticks if tick rate is 100Hz, reduce test time to max ~1.1s + abstime.tv_sec = abstime.tv_sec + 2; + } else { + abstime.tv_sec = abstime.tv_sec + 1; + } + abstime.tv_nsec = 0; + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait lock locked semaphore with tv_nsec 999999999", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + if (abstime.tv_nsec > 900000000) { // ~ ten ticks if tick rate is 100Hz, reduce test time to max ~1.1s + abstime.tv_sec = abstime.tv_sec + 1; + } + abstime.tv_nsec = 999999999; + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +// POSIX explicitly requires this +TEST_CASE("sem_timedwait still locks unlocked semaphore even if abstime expired ", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + abstime.tv_sec = abstime.tv_sec - 1; + + TEST_ASSERT_EQUAL_INT(0, sem_timedwait(&semaphore, &abstime)); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait too old time", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + abstime.tv_sec = abstime.tv_sec - 1; + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait lock unlocked semaphore", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + abstime.tv_sec = abstime.tv_sec + 1; + + TEST_ASSERT_EQUAL_INT(0, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_post(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_timedwait wait on locked semaphore (timeout)", "[semaphore]") +{ + sem_t semaphore; + struct timespec abstime; + TEST_ASSERT_EQUAL_INT(0, clock_gettime(CLOCK_REALTIME, &abstime)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + abstime.tv_nsec = abstime.tv_nsec + 20000000; + if (abstime.tv_nsec >= 1000000000) { + abstime.tv_sec = abstime.tv_sec + 1; + abstime.tv_sec = abstime.tv_nsec % 1000000000; + } + + TEST_ASSERT_EQUAL_INT(-1, sem_timedwait(&semaphore, &abstime)); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_getvalue semaphore NULL", "[semaphore]") +{ + int semaphore_value; + TEST_ASSERT_EQUAL_INT(-1, sem_getvalue(NULL, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); +} + +// This test is actually not mentioned in the standard, but it's good IDF practice +TEST_CASE("sem_getvalue value ptr NULL", "[semaphore]") +{ + sem_t semaphore; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + + TEST_ASSERT_EQUAL_INT(-1, sem_getvalue(&semaphore, NULL)); + TEST_ASSERT_EQUAL_INT(EINVAL, errno); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_getvalue locked semaphore", "[semaphore]") +{ + sem_t semaphore; + int semaphore_value; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 0)); + + TEST_ASSERT_EQUAL_INT(0, sem_getvalue(&semaphore, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(0, semaphore_value); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_getvalue unlocked semaphore", "[semaphore]") +{ + sem_t semaphore; + int semaphore_value; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 1)); + + TEST_ASSERT_EQUAL_INT(0, sem_getvalue(&semaphore, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(1, semaphore_value); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +TEST_CASE("sem_getvalue changes after waiting and posting", "[semaphore]") +{ + sem_t semaphore; + int semaphore_value; + TEST_ASSERT_EQUAL_INT(0, sem_init(&semaphore, SHARED, 2)); + + TEST_ASSERT_EQUAL_INT(0, sem_getvalue(&semaphore, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(2, semaphore_value); + + TEST_ASSERT_EQUAL_INT(0, sem_wait(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_getvalue(&semaphore, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(1, semaphore_value); + + TEST_ASSERT_EQUAL_INT(0, sem_post(&semaphore)); + + TEST_ASSERT_EQUAL_INT(0, sem_getvalue(&semaphore, &semaphore_value)); + TEST_ASSERT_EQUAL_INT(2, semaphore_value); + + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&semaphore)); +} + +static bool finished_wait; +static sem_t g_thread_waiter; +static sem_t g_synchronizer; +static void *do_something(void *arg) +{ + + TEST_ASSERT_EQUAL_INT(0, sem_wait(&g_thread_waiter)); + finished_wait = true; + TEST_ASSERT_EQUAL_INT(0, sem_post(&g_synchronizer)); + return NULL; +} + +TEST_CASE("thread is waiting on semaphore", "[semaphore]") +{ + pthread_t thr; + finished_wait = false; + + TEST_ASSERT_EQUAL_INT(0, sem_init(&g_thread_waiter, SHARED, 0)); + TEST_ASSERT_EQUAL_INT(0, sem_init(&g_synchronizer, SHARED, 0)); + TEST_ASSERT_EQUAL_INT(0, pthread_create(&thr, NULL, do_something, NULL)); + + TEST_ASSERT_FALSE(finished_wait); + TEST_ASSERT_EQUAL_INT(0, sem_post(&g_thread_waiter)); + TEST_ASSERT_EQUAL_INT(0, sem_wait(&g_synchronizer)); + TEST_ASSERT_TRUE(finished_wait); + + pthread_join(thr, NULL); + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&g_synchronizer)); + TEST_ASSERT_EQUAL_INT(0, sem_destroy(&g_thread_waiter)); +} diff --git a/docs/en/api-reference/system/pthread.rst b/docs/en/api-reference/system/pthread.rst index 1e642d0e86..535b6c8542 100644 --- a/docs/en/api-reference/system/pthread.rst +++ b/docs/en/api-reference/system/pthread.rst @@ -98,6 +98,30 @@ Static initializer constant ``PTHREAD_COND_INITIALIZER`` is supported. .. note:: These functions can be called from tasks created using either pthread or FreeRTOS APIs +Semaphores +^^^^^^^^^^ + +In IDF, POSIX *unnamed* semaphores are implemented. The accessible API is described below. It implements `semaphores as specified in the POSIX standard `_, unless specified otherwise. + +* `sem_init() `_ +* `sem_destroy() `_ + + - ``pshared`` is ignored. Semaphores can always be shared between FreeRTOS tasks. + +* `sem_post() `_ + + - If the semaphore has a value of ``SEM_VALUE_MAX`` already, -1 is returned and ``errno`` is set to ``EAGAIN``. + +* `sem_wait() `_ +* `sem_trywait() `_ +* `sem_timedwait() `_ + + - The time value passed by abstime will be rounded up to the next FreeRTOS tick. + - The actual timeout will happen after the tick the time was rounded to and before the following tick. + - It is possible, though unlikely, that the task is preempted directly after the timeout calculation, delaying the timeout of the following blocking operating system call by the duration of the preemption. + +* `sem_getvalue() `_ + Read/Write Locks ^^^^^^^^^^^^^^^^^^^ @@ -160,4 +184,3 @@ API Reference ------------- .. include-build-file:: inc/esp_pthread.inc -