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:
parent
25b0083ee9
commit
abb21d48d9
5 changed files with 449 additions and 3 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue