posix: semaphore: implement sem_open(), sem_unlink() & sem_close()

Implements `sem_open()`, `sem_unlink()` & `sem_close()`
functions and added tests for them.

Updated existing tests and POSIX docs.

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
This commit is contained in:
Yong Cong Sin 2023-12-26 18:15:58 +08:00 committed by Chris Friedt
commit abb21d48d9
5 changed files with 449 additions and 3 deletions

View file

@ -13,6 +13,8 @@
extern "C" {
#endif
#define SEM_FAILED ((sem_t *) 0)
int sem_destroy(sem_t *semaphore);
int sem_getvalue(sem_t *ZRESTRICT semaphore, int *ZRESTRICT value);
int sem_init(sem_t *semaphore, int pshared, unsigned int value);
@ -20,6 +22,9 @@ int sem_post(sem_t *semaphore);
int sem_timedwait(sem_t *ZRESTRICT semaphore, struct timespec *ZRESTRICT abstime);
int sem_trywait(sem_t *semaphore);
int sem_wait(sem_t *semaphore);
sem_t *sem_open(const char *name, int oflags, ...);
int sem_unlink(const char *name);
int sem_close(sem_t *sem);
#ifdef __cplusplus
}

View file

@ -8,3 +8,11 @@ config SEM_VALUE_MAX
range 1 32767
help
Maximum semaphore count in POSIX compliant Application.
config SEM_NAMELEN_MAX
int "Maximum name length"
default 16
range 2 255
help
Maximum length of name for a named semaphore.
The max value of 255 corresponds to {NAME_MAX}.

View file

@ -1,11 +1,75 @@
/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2023 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/posix/fcntl.h>
#include <zephyr/posix/pthread.h>
#include <zephyr/posix/semaphore.h>
struct nsem_obj {
sys_snode_t snode;
sem_t sem;
unsigned int ref_count;
char *name;
};
/* Initialize the list */
static sys_slist_t nsem_list = SYS_SLIST_STATIC_INIT(&nsem_list);
static K_MUTEX_DEFINE(nsem_mutex);
static inline void nsem_list_lock(void)
{
k_mutex_lock(&nsem_mutex, K_FOREVER);
}
static inline void nsem_list_unlock(void)
{
k_mutex_unlock(&nsem_mutex);
}
static struct nsem_obj *nsem_find(const char *name)
{
struct nsem_obj *nsem;
SYS_SLIST_FOR_EACH_CONTAINER(&nsem_list, nsem, snode) {
if ((nsem->name != NULL) && (strcmp(nsem->name, name) == 0)) {
return nsem;
}
}
return NULL;
}
/* Clean up a named semaphore object completely (incl its `name` buffer) */
static void nsem_cleanup(struct nsem_obj *nsem)
{
if (nsem != NULL) {
if (nsem->name != NULL) {
k_free(nsem->name);
nsem->name = NULL;
}
k_free(nsem);
nsem = NULL;
}
}
/* Remove a named semaphore if it isn't unsed */
static void nsem_remove_if_unused(struct nsem_obj *nsem)
{
if (nsem->ref_count == 0) {
sys_slist_find_and_remove(&nsem_list, (sys_snode_t *) nsem);
/* Free nsem */
nsem_cleanup(nsem);
}
}
/**
* @brief Destroy semaphore.
@ -148,3 +212,181 @@ int sem_wait(sem_t *semaphore)
(void)k_sem_take(semaphore, K_FOREVER);
return 0;
}
sem_t *sem_open(const char *name, int oflags, ...)
{
va_list va;
mode_t mode;
unsigned int value;
struct nsem_obj *nsem = NULL;
size_t namelen;
va_start(va, oflags);
BUILD_ASSERT(sizeof(mode_t) <= sizeof(int));
mode = va_arg(va, int);
value = va_arg(va, unsigned int);
va_end(va);
if (value > CONFIG_SEM_VALUE_MAX) {
errno = EINVAL;
return (sem_t *)SEM_FAILED;
}
if (name == NULL) {
errno = EINVAL;
return (sem_t *)SEM_FAILED;
}
namelen = strlen(name);
if ((namelen + 1) > CONFIG_SEM_NAMELEN_MAX) {
errno = ENAMETOOLONG;
return (sem_t *)SEM_FAILED;
}
/* Lock before checking to make sure that the call is atomic */
nsem_list_lock();
/* Check if the named semaphore exists */
nsem = nsem_find(name);
if (nsem != NULL) { /* Named semaphore exists */
if (((oflags & O_CREAT) != 0) && ((oflags & O_EXCL) != 0)) {
errno = EEXIST;
goto error_unlock;
}
__ASSERT_NO_MSG(nsem->ref_count != UINT_MAX);
nsem->ref_count++;
goto unlock;
}
/* Named sempahore doesn't exist, try to create new one */
if ((oflags & O_CREAT) == 0) {
errno = ENOENT;
goto error_unlock;
}
nsem = k_calloc(1, sizeof(struct nsem_obj));
if (nsem == NULL) {
errno = ENOSPC;
goto error_unlock;
}
/* goto `cleanup_error_unlock` past this point to avoid memory leak */
nsem->name = k_calloc(namelen + 1, sizeof(uint8_t));
if (nsem->name == NULL) {
errno = ENOSPC;
goto cleanup_error_unlock;
}
strcpy(nsem->name, name);
nsem->ref_count = 1;
(void)k_sem_init(&nsem->sem, value, CONFIG_SEM_VALUE_MAX);
sys_slist_append(&nsem_list, (sys_snode_t *)&(nsem->snode));
goto unlock;
cleanup_error_unlock:
nsem_cleanup(nsem);
error_unlock:
nsem = NULL;
unlock:
nsem_list_unlock();
return nsem == NULL ? SEM_FAILED : &nsem->sem;
}
int sem_unlink(const char *name)
{
int ret = 0;
struct nsem_obj *nsem;
if (name == NULL) {
errno = EINVAL;
return -1;
}
if ((strlen(name) + 1) > CONFIG_SEM_NAMELEN_MAX) {
errno = ENAMETOOLONG;
return -1;
}
nsem_list_lock();
/* Check if queue already exists */
nsem = nsem_find(name);
if (nsem == NULL) {
ret = -1;
errno = ENOENT;
goto unlock;
}
k_free(nsem->name);
nsem->name = NULL;
nsem_remove_if_unused(nsem);
unlock:
nsem_list_unlock();
return ret;
}
int sem_close(sem_t *sem)
{
struct nsem_obj *nsem = CONTAINER_OF(sem, struct nsem_obj, sem);
if (sem == NULL) {
errno = EINVAL;
return -1;
}
__ASSERT_NO_MSG(nsem != NULL);
nsem_list_lock();
__ASSERT_NO_MSG(nsem->ref_count != 0);
nsem->ref_count--;
/* remove sem if marked for unlink */
if (nsem->name == NULL) {
nsem_remove_if_unused(nsem);
}
nsem_list_unlock();
return 0;
}
#ifdef CONFIG_ZTEST
/* Used by ztest to get the ref count of a named semaphore */
unsigned int nsem_get_ref_count(sem_t *sem)
{
struct nsem_obj *nsem = CONTAINER_OF(sem, struct nsem_obj, sem);
unsigned int ref_count;
__ASSERT_NO_MSG(sem != NULL);
__ASSERT_NO_MSG(nsem != NULL);
nsem_list_lock();
ref_count = nsem->ref_count;
nsem_list_unlock();
return ref_count;
}
/* Used by ztest to get the length of the named semaphore */
size_t nsem_get_list_len(void)
{
size_t len;
nsem_list_lock();
len = sys_slist_len(&nsem_list);
nsem_list_unlock();
return len;
}
#endif

View file

@ -1,10 +1,12 @@
/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2023 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
@ -94,3 +96,192 @@ ZTEST(posix_apis, test_semaphore)
/* TESTPOINT: Wait and acquire semaphore till thread2 gives */
zassert_equal(sem_wait(&sema), 0, "sem_wait failed");
}
unsigned int nsem_get_ref_count(sem_t *sem);
size_t nsem_get_list_len(void);
#define N_LOOPS 999
static void *nsem_open_func(void *p)
{
const char *name = (char *)p;
for (int i = 0; i < N_LOOPS; i++) {
zassert_not_null(sem_open(name, 0, 0, 0), "%s is NULL", name);
k_msleep(1);
}
/* Unlink after finished opening */
zassert_ok(sem_unlink(name));
return NULL;
}
static void *nsem_close_func(void *p)
{
sem_t *sem = (sem_t *)p;
/* Make sure that we have enough ref_count's initially */
k_msleep(N_LOOPS >> 1);
for (int i = 0; i < N_LOOPS; i++) {
zassert_ok(sem_close(sem));
k_msleep(1);
}
/* Close the last `sem` */
zassert_ok(sem_close(sem));
return NULL;
}
ZTEST(posix_apis, test_named_semaphore)
{
pthread_t thread1, thread2;
sem_t *sem1, *sem2, *different_sem1;
/* If `name` is invalid */
sem1 = sem_open(NULL, 0, 0, 0);
zassert_equal(errno, EINVAL);
zassert_equal_ptr(sem1, SEM_FAILED);
zassert_equal(nsem_get_list_len(), 0);
/* Attempt to open a named sem that doesn't exist */
sem1 = sem_open("sem1", 0, 0, 0);
zassert_equal(errno, ENOENT);
zassert_equal_ptr(sem1, SEM_FAILED);
zassert_equal(nsem_get_list_len(), 0);
/* Name exceeds CONFIG_SEM_NAMELEN_MAX */
char name_too_long[CONFIG_SEM_NAMELEN_MAX + 2];
for (size_t i = 0; i < sizeof(name_too_long) - 1; i++) {
name_too_long[i] = 'a';
}
name_too_long[sizeof(name_too_long) - 1] = '\0';
sem1 = sem_open(name_too_long, 0, 0, 0);
zassert_equal(errno, ENAMETOOLONG, "\"%s\" should be longer than %d", name_too_long,
CONFIG_SEM_NAMELEN_MAX);
zassert_equal_ptr(sem1, SEM_FAILED);
zassert_equal(nsem_get_list_len(), 0);
/* `value` greater than CONFIG_SEM_VALUE_MAX */
sem1 = sem_open("sem1", O_CREAT, 0, (CONFIG_SEM_VALUE_MAX + 1));
zassert_equal(errno, EINVAL);
zassert_equal_ptr(sem1, SEM_FAILED);
zassert_equal(nsem_get_list_len(), 0);
/* Open named sem */
sem1 = sem_open("sem1", O_CREAT, 0, 0);
zassert_equal(nsem_get_ref_count(sem1), 1);
zassert_equal(nsem_get_list_len(), 1);
sem2 = sem_open("sem2", O_CREAT, 0, 0);
zassert_equal(nsem_get_ref_count(sem2), 1);
zassert_equal(nsem_get_list_len(), 2);
/* Open created named sem repeatedly */
for (size_t i = 1; i < N_LOOPS; i++) {
sem_t *new_sem1, *new_sem2;
/* oflags are ignored (except when both O_CREAT & O_EXCL are set) */
new_sem1 = sem_open("sem1", i % 2 == 0 ? O_CREAT : 0, 0, 0);
zassert_not_null(new_sem1);
zassert_equal_ptr(new_sem1, sem1); /* Should point to the same sem */
new_sem2 = sem_open("sem2", i % 2 == 0 ? O_CREAT : 0, 0, 0);
zassert_not_null(new_sem2);
zassert_equal_ptr(new_sem2, sem2);
/* ref_count should increment */
zassert_equal(nsem_get_ref_count(sem1), i + 1);
zassert_equal(nsem_get_ref_count(sem2), i + 1);
/* Should reuse the same named sem instead of creating another one */
zassert_equal(nsem_get_list_len(), 2);
}
/* O_CREAT and O_EXCL are set and the named semaphore already exists */
zassert_equal_ptr((sem_open("sem1", O_CREAT | O_EXCL, 0, 0)), SEM_FAILED);
zassert_equal(errno, EEXIST);
zassert_equal(nsem_get_list_len(), 2);
zassert_equal(sem_close(NULL), -1);
zassert_equal(errno, EINVAL);
zassert_equal(nsem_get_list_len(), 2);
/* Close sem */
for (size_t i = 0;
/* close until one left, required by the test later */
i < (N_LOOPS - 1); i++) {
zassert_ok(sem_close(sem1));
zassert_not_null(sem1);
zassert_equal(nsem_get_ref_count(sem1), N_LOOPS - (i + 1));
zassert_ok(sem_close(sem2));
zassert_not_null(sem2);
zassert_equal(nsem_get_ref_count(sem2), N_LOOPS - (i + 1));
zassert_equal(nsem_get_list_len(), 2);
}
/* If `name` is invalid */
zassert_equal(sem_unlink(NULL), -1);
zassert_equal(errno, EINVAL);
zassert_equal(nsem_get_list_len(), 2);
/* Attempt to unlink a named sem that doesn't exist */
zassert_equal(sem_unlink("sem3"), -1);
zassert_equal(errno, ENOENT);
zassert_equal(nsem_get_list_len(), 2);
/* Name exceeds CONFIG_SEM_NAMELEN_MAX */
char long_sem_name[CONFIG_SEM_NAMELEN_MAX + 2];
for (int i = 0; i < CONFIG_SEM_NAMELEN_MAX + 1; i++) {
long_sem_name[i] = 'a';
}
long_sem_name[CONFIG_SEM_NAMELEN_MAX + 1] = '\0';
zassert_equal(sem_unlink(long_sem_name), -1);
zassert_equal(errno, ENAMETOOLONG);
zassert_equal(nsem_get_list_len(), 2);
/* Unlink sem1 when it is still being used */
zassert_equal(nsem_get_ref_count(sem1), 1);
zassert_ok(sem_unlink("sem1"));
/* sem won't be destroyed */
zassert_equal(nsem_get_list_len(), 2);
/* Create another sem with the name of an unlinked sem */
different_sem1 = sem_open("sem1", O_CREAT, 0, 0);
zassert_not_null(different_sem1);
/* The created sem will be a different instance */
zassert(different_sem1 != sem1, "");
zassert_equal(nsem_get_list_len(), 3);
/* Destruction of sem1 will be postponed until all references to the semaphore have been
* destroyed by calls to sem_close()
*/
zassert_ok(sem_close(sem1));
zassert_equal(nsem_get_list_len(), 2);
/* Closing a linked sem won't destroy the sem */
zassert_ok(sem_close(sem2));
zassert_equal(nsem_get_ref_count(sem2), 0);
zassert_equal(nsem_get_list_len(), 2);
/* Instead the sem will be destroyed upon call to sem_unlink() */
zassert_ok(sem_unlink("sem2"));
zassert_equal(nsem_get_list_len(), 1);
/* What we have left open here is `different_sem` as "sem1", which has 1 ref_count */
zassert_equal(nsem_get_ref_count(different_sem1), 1);
/* Stress test: open & close "sem1" repeatedly */
zassert_ok(pthread_create(&thread1, NULL, nsem_open_func, "sem1"));
zassert_ok(pthread_create(&thread2, NULL, nsem_close_func, different_sem1));
/* Make sure the threads are terminated */
zassert_ok(pthread_join(thread1, NULL));
zassert_ok(pthread_join(thread2, NULL));
/* All named semaphores should be destroyed here */
zassert_equal(nsem_get_list_len(), 0);
}

View file

@ -22,15 +22,15 @@ ZTEST(posix_headers, test_semaphore_h)
/* zassert_not_equal(SEM_FAILED, (sem_t *)42); */ /* not implemented */
if (IS_ENABLED(CONFIG_POSIX_API)) {
/* zassert_not_null(sem_close); */ /* not implemented */
zassert_not_null(sem_close);
zassert_not_null(sem_destroy);
zassert_not_null(sem_getvalue);
zassert_not_null(sem_init);
/* zassert_not_null(sem_open); */ /* not implemented */
zassert_not_null(sem_open);
zassert_not_null(sem_post);
zassert_not_null(sem_timedwait);
zassert_not_null(sem_trywait);
/* zassert_not_null(sem_unlink); */ /* not implemented */
zassert_not_null(sem_unlink);
zassert_not_null(sem_wait);
}
}