Merge branch 'feature/pthread_semaphore' into 'master'

pthread: POSIX unnamed semaphore

Closes IDF-6936

See merge request espressif/esp-idf!22585
This commit is contained in:
Jakob Hasse 2023-03-22 19:03:12 +08:00
commit 6382fc3d6b
7 changed files with 699 additions and 6 deletions

View File

@ -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}")

View File

@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <time.h>
#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

View File

@ -0,0 +1,177 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <assert.h>
#include <sys/time.h>
#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) { }

View File

@ -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 "."

View File

@ -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 <errno.h>
#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

View File

@ -0,0 +1,417 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <errno.h>
#define SHARED 0
#include <limits.h>
#include <string.h>
#include <semaphore.h>
#include <errno.h>
#include <pthread.h>
#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));
}

View File

@ -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 <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html>`_, unless specified otherwise.
* `sem_init() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_init.html>`_
* `sem_destroy() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_destroy.html>`_
- ``pshared`` is ignored. Semaphores can always be shared between FreeRTOS tasks.
* `sem_post() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_post.html>`_
- If the semaphore has a value of ``SEM_VALUE_MAX`` already, -1 is returned and ``errno`` is set to ``EAGAIN``.
* `sem_wait() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_wait.html>`_
* `sem_trywait() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_trywait.html>`_
* `sem_timedwait() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_timedwait.html>`_
- 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() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_getvalue.html>`_
Read/Write Locks
^^^^^^^^^^^^^^^^^^^
@ -160,4 +184,3 @@ API Reference
-------------
.. include-build-file:: inc/esp_pthread.inc