From d0da8c38079971533e8e5e60d78cff0ae5c2910d Mon Sep 17 00:00:00 2001 From: Christopher Friedt Date: Thu, 22 Oct 2020 22:41:58 -0400 Subject: [PATCH] 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 --- lib/posix/Kconfig.pthread | 21 +++++++ lib/posix/posix_internal.h | 3 + lib/posix/pthread.c | 118 ++++++++++++++++++++++++++++--------- 3 files changed, 114 insertions(+), 28 deletions(-) diff --git a/lib/posix/Kconfig.pthread b/lib/posix/Kconfig.pthread index 9b2541c6631bf7..388a30c5fa40d7 100644 --- a/lib/posix/Kconfig.pthread +++ b/lib/posix/Kconfig.pthread @@ -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 diff --git a/lib/posix/posix_internal.h b/lib/posix/posix_internal.h index e3c2b2817125e4..39c64c53331c15 100644 --- a/lib/posix/posix_internal.h +++ b/lib/posix/posix_internal.h @@ -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; diff --git a/lib/posix/pthread.c b/lib/posix/pthread.c index 519d6809124818..a6505d4f9d33be 100644 --- a/lib/posix/pthread.c +++ b/lib/posix/pthread.c @@ -16,6 +16,12 @@ #include #include +#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,