pthread: facilitate dynamically allocated thread stacks

This change allows users to call pthread_create() with
the pthread_attr_t argument equal to NULL.

If Zephyr is configured with `CONFIG_DYNAMIC_THREAD`, then a
suitable thread stack will be allocated via
k_thread_stack_alloc(). The allocated thread stack is
automatically freed via k_thread_stack_free().

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

Signed-off-by: Christopher Friedt <chrisfriedt@gmail.com>
This commit is contained in:
Christopher Friedt 2020-10-22 22:41:58 -04:00 committed by Chris Friedt
commit 115efa2e35
3 changed files with 115 additions and 29 deletions

View file

@ -7,3 +7,24 @@ TYPE = PTHREAD
type = pthread_t
type-function = pthread_create
source "lib/posix/Kconfig.template.pooled_ipc_type"
if PTHREAD
config PTHREAD_RECYCLER_DELAY_MS
int "Delay for reclaiming dynamic pthread stacks (ms)"
default 100
help
Prior to a POSIX thread terminating via k_thread_abort(), scheduled
work is added to the system workqueue (SWQ) so that any resources
allocated by the thread (e.g. thread stack from a pool or the heap)
can be released back to the system. Because resources are also freed
on calls to pthread_create() there is no need to worry about resource
starvation.
This option sets the number of milliseconds by which to defer
scheduled work.
Note: this option should be considered temporary and will likely be
removed once a more synchronous solution is available.
endif

View file

@ -30,6 +30,9 @@ struct posix_thread {
/* List of keys that thread has called pthread_setspecific() on */
sys_slist_t key_list;
/* Dynamic stack */
k_thread_stack_t *dynamic_stack;
/* Exit status */
void *retval;

View file

@ -16,6 +16,12 @@
#include <zephyr/posix/pthread.h>
#include <zephyr/sys/slist.h>
#ifdef CONFIG_DYNAMIC_THREAD_STACK_SIZE
#define DYNAMIC_STACK_SIZE CONFIG_DYNAMIC_THREAD_STACK_SIZE
#else
#define DYNAMIC_STACK_SIZE 0
#endif
#define PTHREAD_INIT_FLAGS PTHREAD_CANCEL_ENABLE
#define PTHREAD_CANCELED ((void *) -1)
@ -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);
@ -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;
}
@ -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;
@ -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(CONFIG_PTHREAD_RECYCLER_DELAY_MS));
/* abort the underlying k_thread */
k_thread_abort(&t->thread);
}
@ -283,6 +300,45 @@ 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) {
(void)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.
*
@ -297,32 +353,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) {
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;
@ -332,12 +389,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 == NULL ? 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);
@ -348,11 +415,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,