Skip to content

Commit

Permalink
pthread: facilitate dynamically allocated thread stacks
Browse files Browse the repository at this point in the history
This change allows users to call pthread_create(3) with
the pthread_attr_t argument equal to NULL, or with the
pthread_attr_t argument specifying a NULL stack but a
custom stack size.

If either of the above to requirements are met, then
a stack will be heap-allocated internally and
freed again after the thread has terminated.

This makes the Zephyr implementation of pthread_create(3)
more compliant with the normative spec.

Fixes #25973

Signed-off-by: Christopher Friedt <chrisfriedt@gmail.com>
  • Loading branch information
cfriedt committed Jul 14, 2023
1 parent 1323b1a commit 15b9e8a
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 35 deletions.
15 changes: 8 additions & 7 deletions lib/posix/posix_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ struct posix_thread {
/* Exit status */
void *retval;

/* Pthread cancellation */
uint8_t cancel_state;
bool cancel_pending;

/* Detach state */
uint8_t detachstate;
/* Dynamic stack */
k_thread_stack_t *dynamic_stack;

/* Queue ID (internal-only) */
uint8_t qid;
uint8_t qid: 2;
/* Pthread cancellation */
bool cancel_state: 1;
bool cancel_pending: 1;
/* Detach state */
bool detachstate: 1;
};

typedef struct pthread_key_obj {
Expand Down
123 changes: 95 additions & 28 deletions lib/posix/pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
#include <zephyr/posix/pthread.h>
#include <zephyr/sys/slist.h>

#ifdef CONFIG_PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
#define DYNAMIC_STACK_SIZE CONFIG_PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
#else
#define DYNAMIC_STACK_SIZE 0
#endif

#define PTHREAD_INIT_FLAGS PTHREAD_CANCEL_ENABLE
#define PTHREAD_CANCELED ((void *) -1)

Expand All @@ -34,6 +40,7 @@ BUILD_ASSERT((PTHREAD_CREATE_DETACHED == 0 || PTHREAD_CREATE_JOINABLE == 0) &&
BUILD_ASSERT((PTHREAD_CANCEL_ENABLE == 0 || PTHREAD_CANCEL_DISABLE == 0) &&
(PTHREAD_CANCEL_ENABLE == 1 || PTHREAD_CANCEL_DISABLE == 1));

static void posix_thread_recycle(void);
static sys_dlist_t ready_q = SYS_DLIST_STATIC_INIT(&ready_q);
static sys_dlist_t run_q = SYS_DLIST_STATIC_INIT(&run_q);
static sys_dlist_t done_q = SYS_DLIST_STATIC_INIT(&done_q);
Expand Down Expand Up @@ -205,13 +212,13 @@ int pthread_attr_setstack(pthread_attr_t *_attr, void *stackaddr, size_t stacksi

static bool pthread_attr_is_valid(const struct pthread_attr *attr)
{
/*
* FIXME: Pthread attribute must be non-null and it provides stack
* pointer and stack size. So even though POSIX 1003.1 spec accepts
* attrib as NULL but zephyr needs it initialized with valid stack.
*/
if (attr == NULL || attr->initialized == 0U || attr->stack == NULL ||
attr->stacksize == 0) {
/* auto-alloc thread stack */
if (attr == NULL) {
return true;
}

/* caller-provided thread stack */
if (attr->initialized == 0U || attr->stack == NULL || attr->stacksize == 0) {
return false;
}

Expand All @@ -234,6 +241,13 @@ static bool pthread_attr_is_valid(const struct pthread_attr *attr)
return true;
}

static void posix_thread_recycle_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
posix_thread_recycle();
}
static K_WORK_DELAYABLE_DEFINE(posix_thread_recycle_work, posix_thread_recycle_work_handler);

static void posix_thread_finalize(struct posix_thread *t, void *retval)
{
sys_snode_t *node_l;
Expand All @@ -259,6 +273,9 @@ static void posix_thread_finalize(struct posix_thread *t, void *retval)
t->retval = retval;
k_spin_unlock(&pthread_pool_lock, key);

/* trigger recycle work */
(void)k_work_schedule(&posix_thread_recycle_work, K_MSEC(100));

/* abort the underlying k_thread */
k_thread_abort(&t->thread);
}
Expand All @@ -283,6 +300,50 @@ static void zephyr_thread_wrapper(void *arg1, void *arg2, void *arg3)
CODE_UNREACHABLE;
}

static void posix_thread_recycle(void)
{
k_spinlock_key_t key;
struct posix_thread *t;
struct posix_thread *safe_t;
sys_dlist_t recyclables = SYS_DLIST_STATIC_INIT(&recyclables);

key = k_spin_lock(&pthread_pool_lock);
SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&done_q, t, safe_t, q_node) {
if (t->detachstate == PTHREAD_CREATE_JOINABLE) {
/* thread has not been joined yet */
continue;
}

sys_dlist_remove(&t->q_node);
sys_dlist_append(&recyclables, &t->q_node);
}
k_spin_unlock(&pthread_pool_lock, key);

if (sys_dlist_is_empty(&recyclables)) {
return;
}

if (IS_ENABLED(CONFIG_DYNAMIC_THREAD)) {
SYS_DLIST_FOR_EACH_CONTAINER(&recyclables, t, q_node) {
if (t->dynamic_stack != NULL) {
if (k_is_user_context()) {
(void)k_thread_stack_free(t->dynamic_stack);
} else {
(void)z_impl_k_thread_stack_free(t->dynamic_stack);
}

t->dynamic_stack = NULL;
}
}
}

key = k_spin_lock(&pthread_pool_lock);
while (!sys_dlist_is_empty(&recyclables)) {
sys_dlist_append(&ready_q, sys_dlist_get(&recyclables));
}
k_spin_unlock(&pthread_pool_lock, key);
}

/**
* @brief Create a new thread.
*
Expand All @@ -297,32 +358,33 @@ int pthread_create(pthread_t *th, const pthread_attr_t *_attr, void *(*threadrou
int err;
k_spinlock_key_t key;
pthread_barrier_t barrier;
struct posix_thread *safe_t;
struct posix_thread *t = NULL;
const struct pthread_attr *attr = (const struct pthread_attr *)_attr;
struct pthread_attr attr_storage = init_pthread_attrs;
struct pthread_attr *attr = (struct pthread_attr *)_attr;

if (!pthread_attr_is_valid(attr)) {
return EINVAL;
}

if (attr == NULL && IS_ENABLED(CONFIG_DYNAMIC_THREAD)) {
attr = &attr_storage;
attr->stacksize = DYNAMIC_STACK_SIZE;
attr->stack =
k_thread_stack_alloc(attr->stacksize, k_is_user_context() ? K_USER : 0);
if (attr->stack == NULL) {
return EAGAIN;
}
} else {
__ASSERT_NO_MSG(attr != &attr_storage);
}

/* reclaim resources greedily */
posix_thread_recycle();

key = k_spin_lock(&pthread_pool_lock);
if (!sys_dlist_is_empty(&ready_q)) {
/* spawn thread 't' directly from ready_q */
t = CONTAINER_OF(sys_dlist_get(&ready_q), struct posix_thread, q_node);
} else {
SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&done_q, t, safe_t, q_node) {
if (t->detachstate == PTHREAD_CREATE_JOINABLE) {
/* thread has not been joined yet */
continue;
}

/* spawn thread 't' from done_q */
sys_dlist_remove(&t->q_node);
break;
}
}

if (t != NULL) {
/* initialize thread state */
sys_dlist_append(&run_q, &t->q_node);
t->qid = POSIX_THREAD_RUN_Q;
Expand All @@ -332,12 +394,22 @@ int pthread_create(pthread_t *th, const pthread_attr_t *_attr, void *(*threadrou
}
t->cancel_pending = false;
sys_slist_init(&t->key_list);
t->dynamic_stack = attr == &attr_storage ? attr->stack : NULL;
}
k_spin_unlock(&pthread_pool_lock, key);

if (t == NULL) {
/* no threads are ready */
return EAGAIN;
}

if (IS_ENABLED(CONFIG_PTHREAD_CREATE_BARRIER)) {
err = pthread_barrier_init(&barrier, NULL, 2);
if (err != 0) {
if (t->dynamic_stack != NULL) {
(void)k_thread_stack_free(attr->stack);
}

/* cannot allocate barrier. move thread back to ready_q */
key = k_spin_lock(&pthread_pool_lock);
sys_dlist_remove(&t->q_node);
Expand All @@ -348,11 +420,6 @@ int pthread_create(pthread_t *th, const pthread_attr_t *_attr, void *(*threadrou
}
}

if (t == NULL) {
/* no threads are ready */
return EAGAIN;
}

/* spawn the thread */
k_thread_create(&t->thread, attr->stack, attr->stacksize, zephyr_thread_wrapper,
(void *)arg, threadroutine,
Expand Down

0 comments on commit 15b9e8a

Please sign in to comment.