mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
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:
commit
6382fc3d6b
@ -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}")
|
||||
|
73
components/pthread/include/semaphore.h
Normal file
73
components/pthread/include/semaphore.h
Normal 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
|
177
components/pthread/pthread_semaphore.c
Normal file
177
components/pthread/pthread_semaphore.c
Normal 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) { }
|
@ -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 "."
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user