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:
parent
d7119b889f
commit
115efa2e35
3 changed files with 115 additions and 29 deletions
|
@ -7,3 +7,24 @@ TYPE = PTHREAD
|
||||||
type = pthread_t
|
type = pthread_t
|
||||||
type-function = pthread_create
|
type-function = pthread_create
|
||||||
source "lib/posix/Kconfig.template.pooled_ipc_type"
|
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
|
||||||
|
|
|
@ -30,6 +30,9 @@ struct posix_thread {
|
||||||
/* List of keys that thread has called pthread_setspecific() on */
|
/* List of keys that thread has called pthread_setspecific() on */
|
||||||
sys_slist_t key_list;
|
sys_slist_t key_list;
|
||||||
|
|
||||||
|
/* Dynamic stack */
|
||||||
|
k_thread_stack_t *dynamic_stack;
|
||||||
|
|
||||||
/* Exit status */
|
/* Exit status */
|
||||||
void *retval;
|
void *retval;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
#include <zephyr/posix/pthread.h>
|
#include <zephyr/posix/pthread.h>
|
||||||
#include <zephyr/sys/slist.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_INIT_FLAGS PTHREAD_CANCEL_ENABLE
|
||||||
#define PTHREAD_CANCELED ((void *) -1)
|
#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) &&
|
BUILD_ASSERT((PTHREAD_CANCEL_ENABLE == 0 || PTHREAD_CANCEL_DISABLE == 0) &&
|
||||||
(PTHREAD_CANCEL_ENABLE == 1 || PTHREAD_CANCEL_DISABLE == 1));
|
(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 ready_q = SYS_DLIST_STATIC_INIT(&ready_q);
|
||||||
static sys_dlist_t run_q = SYS_DLIST_STATIC_INIT(&run_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);
|
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)
|
static bool pthread_attr_is_valid(const struct pthread_attr *attr)
|
||||||
{
|
{
|
||||||
/*
|
/* auto-alloc thread stack */
|
||||||
* FIXME: Pthread attribute must be non-null and it provides stack
|
if (attr == NULL) {
|
||||||
* pointer and stack size. So even though POSIX 1003.1 spec accepts
|
return true;
|
||||||
* attrib as NULL but zephyr needs it initialized with valid stack.
|
}
|
||||||
*/
|
|
||||||
if (attr == NULL || attr->initialized == 0U || attr->stack == NULL ||
|
/* caller-provided thread stack */
|
||||||
attr->stacksize == 0) {
|
if (attr->initialized == 0U || attr->stack == NULL || attr->stacksize == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +241,13 @@ static bool pthread_attr_is_valid(const struct pthread_attr *attr)
|
||||||
return true;
|
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)
|
static void posix_thread_finalize(struct posix_thread *t, void *retval)
|
||||||
{
|
{
|
||||||
sys_snode_t *node_l;
|
sys_snode_t *node_l;
|
||||||
|
@ -259,6 +273,9 @@ static void posix_thread_finalize(struct posix_thread *t, void *retval)
|
||||||
t->retval = retval;
|
t->retval = retval;
|
||||||
k_spin_unlock(&pthread_pool_lock, key);
|
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 */
|
/* abort the underlying k_thread */
|
||||||
k_thread_abort(&t->thread);
|
k_thread_abort(&t->thread);
|
||||||
}
|
}
|
||||||
|
@ -283,6 +300,45 @@ static void zephyr_thread_wrapper(void *arg1, void *arg2, void *arg3)
|
||||||
CODE_UNREACHABLE;
|
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.
|
* @brief Create a new thread.
|
||||||
*
|
*
|
||||||
|
@ -297,32 +353,33 @@ int pthread_create(pthread_t *th, const pthread_attr_t *_attr, void *(*threadrou
|
||||||
int err;
|
int err;
|
||||||
k_spinlock_key_t key;
|
k_spinlock_key_t key;
|
||||||
pthread_barrier_t barrier;
|
pthread_barrier_t barrier;
|
||||||
struct posix_thread *safe_t;
|
|
||||||
struct posix_thread *t = NULL;
|
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)) {
|
if (!pthread_attr_is_valid(attr)) {
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = k_spin_lock(&pthread_pool_lock);
|
if (attr == NULL) {
|
||||||
if (!sys_dlist_is_empty(&ready_q)) {
|
attr = &attr_storage;
|
||||||
/* spawn thread 't' directly from ready_q */
|
attr->stacksize = DYNAMIC_STACK_SIZE;
|
||||||
t = CONTAINER_OF(sys_dlist_get(&ready_q), struct posix_thread, q_node);
|
attr->stack =
|
||||||
} else {
|
k_thread_stack_alloc(attr->stacksize, k_is_user_context() ? K_USER : 0);
|
||||||
SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&done_q, t, safe_t, q_node) {
|
if (attr->stack == NULL) {
|
||||||
if (t->detachstate == PTHREAD_CREATE_JOINABLE) {
|
return EAGAIN;
|
||||||
/* thread has not been joined yet */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* spawn thread 't' from done_q */
|
|
||||||
sys_dlist_remove(&t->q_node);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
__ASSERT_NO_MSG(attr != &attr_storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t != NULL) {
|
/* reclaim resources greedily */
|
||||||
|
posix_thread_recycle();
|
||||||
|
|
||||||
|
key = k_spin_lock(&pthread_pool_lock);
|
||||||
|
if (!sys_dlist_is_empty(&ready_q)) {
|
||||||
|
t = CONTAINER_OF(sys_dlist_get(&ready_q), struct posix_thread, q_node);
|
||||||
|
|
||||||
/* initialize thread state */
|
/* initialize thread state */
|
||||||
sys_dlist_append(&run_q, &t->q_node);
|
sys_dlist_append(&run_q, &t->q_node);
|
||||||
t->qid = POSIX_THREAD_RUN_Q;
|
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;
|
t->cancel_pending = false;
|
||||||
sys_slist_init(&t->key_list);
|
sys_slist_init(&t->key_list);
|
||||||
|
t->dynamic_stack = _attr == NULL ? attr->stack : NULL;
|
||||||
}
|
}
|
||||||
k_spin_unlock(&pthread_pool_lock, key);
|
k_spin_unlock(&pthread_pool_lock, key);
|
||||||
|
|
||||||
|
if (t == NULL) {
|
||||||
|
/* no threads are ready */
|
||||||
|
return EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_PTHREAD_CREATE_BARRIER)) {
|
if (IS_ENABLED(CONFIG_PTHREAD_CREATE_BARRIER)) {
|
||||||
err = pthread_barrier_init(&barrier, NULL, 2);
|
err = pthread_barrier_init(&barrier, NULL, 2);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
|
if (t->dynamic_stack != NULL) {
|
||||||
|
(void)k_thread_stack_free(attr->stack);
|
||||||
|
}
|
||||||
|
|
||||||
/* cannot allocate barrier. move thread back to ready_q */
|
/* cannot allocate barrier. move thread back to ready_q */
|
||||||
key = k_spin_lock(&pthread_pool_lock);
|
key = k_spin_lock(&pthread_pool_lock);
|
||||||
sys_dlist_remove(&t->q_node);
|
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 */
|
/* spawn the thread */
|
||||||
k_thread_create(&t->thread, attr->stack, attr->stacksize, zephyr_thread_wrapper,
|
k_thread_create(&t->thread, attr->stack, attr->stacksize, zephyr_thread_wrapper,
|
||||||
(void *)arg, threadroutine,
|
(void *)arg, threadroutine,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue