Skip to content

pthread: facilitate dynamically allocated thread stacks #31603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ extern void k_thread_foreach_unlocked(
*/
#define K_INHERIT_PERMS (BIT(3))

/**
* @brief dynamically allocated stack
*
* This flag indicates that a thread stack has been heap-allocated with
* @ref k_malloc.
*/
#define K_STACK_ON_HEAP (BIT(4))

#ifdef CONFIG_X86
/* x86 Bitmask definitions for threads user options */

Expand Down
1 change: 1 addition & 0 deletions include/posix/pthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ static inline int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
return 0;
}

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
Expand Down
24 changes: 24 additions & 0 deletions lib/posix/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ config SEM_VALUE_MAX
help
Maximum semaphore count in POSIX compliant Application.

config PTHREAD_DYNAMIC_STACKS
bool "Support for dynamic stacks"
select THREAD_STACK_INFO
default n
help
POSIX 1003.1 allows a NULL pthread_attr_t* to be passed to
pthread_create(3). However, Zephyr has traditionally required
that the caller statically allocate a stack and pass it in via the
pthread_attr_t*. With this option selected, NULL will be permitted
and a suitable stack will be automatically allocated and assigned,
inheriting permissions from the calling thread.

if PTHREAD_DYNAMIC_STACK
config PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
int "Default size for a dynamic pthread stack (in bytes)"
default 1024
help
This value is used for the default size of dynamically-allocated
stacks. However, users may still specify the size of
dynamically-allocated stacks via pthread_attr_setstacksize(3)
prior to calling pthread_create(3).

endif # PTHREAD_DYNAMIC_STACK

endif # PTHREAD_IPC

config POSIX_CLOCK
Expand Down
108 changes: 92 additions & 16 deletions lib/posix/pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@

#include <kernel.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/atomic.h>
#include <ksched.h>
#include <wait_q.h>
#include <posix/pthread.h>
#include <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 Down Expand Up @@ -115,7 +122,10 @@ int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,
static void zephyr_thread_wrapper(void *arg1, void *arg2, void *arg3)
{
void * (*fun_ptr)(void *) = arg3;

struct _thread_stack_info *stack_info
= &k_current_get()->stack_info;
stack_info->delta = (size_t)
((uint8_t *)stack_info->start - (uint8_t *)arg2);
fun_ptr(arg1);
pthread_exit(NULL);
}
Expand All @@ -135,17 +145,43 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr,
uint32_t pthread_num;
pthread_condattr_t cond_attr;
struct posix_thread *thread;
pthread_attr_t dynamic_attr;
k_thread_stack_t *dynamic_stack = NULL;
/* a non-const pthread_attr_t* that we can modify, if needed */
pthread_attr_t *mattr = (pthread_attr_t *)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)) {
if (mattr != NULL && mattr->initialized == 0) {
return EINVAL;
}

if (mattr == NULL || mattr->stack == NULL) {
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
/*
* We dynamically allocate space when either
* 1) attr == NULL -> use DYNAMIC_STACK_SIZE, or
* 2) attr != NULL && attr->stack == NULL
* -> allocate attr->stacksize
*/
if (mattr == NULL) {
(void) pthread_attr_init(&dynamic_attr);
dynamic_attr.stacksize = DYNAMIC_STACK_SIZE;
mattr = &dynamic_attr;
}

dynamic_stack = k_aligned_alloc(ARCH_STACK_PTR_ALIGN,
Z_KERNEL_STACK_SIZE_ADJUST(mattr->stacksize));
if (dynamic_stack == NULL) {
return EAGAIN;
}

__ASSERT_NO_MSG(dynamic_stack != NULL);
mattr->stack = dynamic_stack;
mattr->flags |= K_STACK_ON_HEAP;
} else {
return EINVAL;
}
}

pthread_mutex_lock(&pthread_pool_lock);
for (pthread_num = 0;
pthread_num < CONFIG_MAX_PTHREAD_COUNT; pthread_num++) {
Expand All @@ -158,10 +194,14 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr,
pthread_mutex_unlock(&pthread_pool_lock);

if (pthread_num >= CONFIG_MAX_PTHREAD_COUNT) {
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)
&& dynamic_stack != NULL) {
k_free(dynamic_stack);
}
return EAGAIN;
}

prio = posix_to_zephyr_priority(attr->priority, attr->schedpolicy);
prio = posix_to_zephyr_priority(mattr->priority, mattr->schedpolicy);

thread = &posix_thread_pool[pthread_num];
/*
Expand All @@ -172,25 +212,25 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr,
(void)pthread_mutex_init(&thread->cancel_lock, NULL);

pthread_mutex_lock(&thread->cancel_lock);
thread->cancel_state = (1 << _PTHREAD_CANCEL_POS) & attr->flags;
thread->cancel_state = (1 << _PTHREAD_CANCEL_POS) & mattr->flags;
thread->cancel_pending = 0;
pthread_mutex_unlock(&thread->cancel_lock);

pthread_mutex_lock(&thread->state_lock);
thread->state = attr->detachstate;
thread->state = mattr->detachstate;
pthread_mutex_unlock(&thread->state_lock);

pthread_cond_init(&thread->state_cond, &cond_attr);
sys_slist_init(&thread->key_list);

*newthread = (pthread_t) k_thread_create(&thread->thread, attr->stack,
attr->stacksize,
*newthread = (pthread_t) k_thread_create(&thread->thread, mattr->stack,
mattr->stacksize,
(k_thread_entry_t)
zephyr_thread_wrapper,
(void *)arg, NULL,
(void *)arg, dynamic_stack,
threadroutine, prio,
(~K_ESSENTIAL & attr->flags),
K_MSEC(attr->delayedstart));
(~K_ESSENTIAL & mattr->flags),
K_MSEC(mattr->delayedstart));
return 0;
}

Expand Down Expand Up @@ -347,6 +387,23 @@ int pthread_once(pthread_once_t *once, void (*init_func)(void))
return 0;
}

#ifdef CONFIG_PTHREAD_DYNAMIC_STACK
static void zephyr_pthread_stack_reclaim(struct k_thread *thread)
{
uint8_t *p = (uint8_t *)thread->stack_info.start;

p -= thread->stack_info.delta;
memset((void *)thread->stack_info.start, 0,
thread->stack_info.size);
k_free(p);
}
#else
static inline void zephyr_pthread_stack_reclaim(struct k_thread *thread)
{
ARG_UNUSED(thread);
}
#endif

/**
* @brief Terminate calling thread.
*
Expand Down Expand Up @@ -385,6 +442,10 @@ void pthread_exit(void *retval)
}
}

if ((self->thread.base.user_options & K_STACK_ON_HEAP) != 0) {
self->thread.fn_abort = zephyr_pthread_stack_reclaim;
}

pthread_mutex_unlock(&self->state_lock);
k_thread_abort((k_tid_t)self);
}
Expand Down Expand Up @@ -534,6 +595,21 @@ int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)
return 0;
}

/**
* @brief Set stack size attribute in thread attributes object.
*
* See IEEE 1003.1
*/
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
{
if ((attr == NULL) || (attr->initialized == 0U)) {
return EINVAL;
}

attr->stacksize = stacksize;
return 0;
}

/**
* @brief Get stack size attribute in thread attributes object.
*
Expand Down
2 changes: 2 additions & 0 deletions tests/posix/common/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extern void test_posix_pthread_execution(void);
extern void test_posix_pthread_termination(void);
extern void test_posix_multiple_threads_single_key(void);
extern void test_posix_single_thread_multiple_keys(void);
extern void test_posix_thread_attr_stacksize(void);
extern void test_nanosleep_NULL_NULL(void);
extern void test_nanosleep_NULL_notNULL(void);
extern void test_nanosleep_notNULL_NULL(void);
Expand All @@ -42,6 +43,7 @@ void test_main(void)
ztest_unit_test(test_posix_pthread_termination),
ztest_unit_test(test_posix_multiple_threads_single_key),
ztest_unit_test(test_posix_single_thread_multiple_keys),
ztest_unit_test(test_posix_thread_attr_stacksize),
ztest_unit_test(test_posix_clock),
ztest_unit_test(test_posix_semaphore),
ztest_unit_test(test_posix_normal_mutex),
Expand Down
65 changes: 53 additions & 12 deletions tests/posix/common/src/pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ void *thread_top_term(void *p1)
}

if (id >= 2) {
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
zassert_false(pthread_detach(self), "failed to set detach state");
}
ret = pthread_detach(self);
if (id == 2) {
zassert_equal(ret, EINVAL, "re-detached thread!");
Expand Down Expand Up @@ -301,18 +304,20 @@ void test_posix_pthread_execution(void)
ret = pthread_setname_np(NULL, thr_name);
zassert_equal(ret, ESRCH, "uninitialized setname!");

/* TESTPOINT: Try creating thread before attr init */
ret = pthread_create(&newthread[0], &attr[0],
thread_top_exec, NULL);
zassert_equal(ret, EINVAL, "thread created before attr init!");
if (!IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
/* TESTPOINT: Try creating thread before attr init */
ret = pthread_create(&newthread[0], &attr[0],
thread_top_exec, NULL);
zassert_equal(ret, EINVAL, "thread created before attr init!");
}

for (i = 0; i < N_THR_E; i++) {
ret = pthread_attr_init(&attr[i]);
if (ret != 0) {
zassert_false(pthread_attr_destroy(&attr[i]),
"Unable to destroy pthread object attrib");
"Unable to destroy pthread object attrib");
zassert_false(pthread_attr_init(&attr[i]),
"Unable to create pthread object attrib");
"Unable to create pthread object attrib");
}

/* TESTPOINTS: Retrieve set stack attributes and compare */
Expand All @@ -333,11 +338,16 @@ void test_posix_pthread_execution(void)
pthread_attr_setschedparam(&attr[i], &schedparam);
pthread_attr_getschedparam(&attr[i], &getschedparam);
zassert_equal(schedparam.sched_priority,
getschedparam.sched_priority,
"scheduling priorities do not match!");
getschedparam.sched_priority,
"scheduling priorities do not match!");

ret = pthread_create(&newthread[i], &attr[i], thread_top_exec,
INT_TO_POINTER(i));
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
ret = pthread_create(&newthread[i], NULL, thread_top_exec,
INT_TO_POINTER(i));
} else {
ret = pthread_create(&newthread[i], &attr[i], thread_top_exec,
INT_TO_POINTER(i));
}

/* TESTPOINT: Check if thread is created successfully */
zassert_false(ret, "Number of threads exceed max limit");
Expand Down Expand Up @@ -429,8 +439,13 @@ void test_posix_pthread_termination(void)
schedparam.sched_priority = 2;
pthread_attr_setschedparam(&attr[i], &schedparam);
pthread_attr_setstack(&attr[i], &stack_t[i][0], STACKS);
ret = pthread_create(&newthread[i], &attr[i], thread_top_term,
INT_TO_POINTER(i));
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
ret = pthread_create(&newthread[i], NULL, thread_top_term,
INT_TO_POINTER(i));
} else {
ret = pthread_create(&newthread[i], &attr[i], thread_top_term,
INT_TO_POINTER(i));
}

zassert_false(ret, "Not enough space to create new thread");
}
Expand Down Expand Up @@ -464,3 +479,29 @@ void test_posix_pthread_termination(void)
ret = pthread_getschedparam(newthread[N_THR_T/2], &policy, &schedparam);
zassert_equal(ret, ESRCH, "got attr from terminated thread!");
}

#ifdef CONFIG_PTHREAD_DYNAMIC_STACK
static void *fun(void *arg)
{
*((uint32_t *)arg) = 0xB105F00D;
return NULL;
}

void test_posix_thread_attr_stacksize(void)
{
uint32_t x = 0;
pthread_attr_t attr;
pthread_t th;

/* TESTPOINT: specify a custom stack size via pthread_attr_t */
zassert_equal(0, pthread_attr_init(&attr), "");
zassert_equal(0, pthread_attr_setstacksize(&attr, 256), "");
zassert_equal(0, pthread_create(&th, &attr, fun, &x), "");
zassert_equal(0, pthread_join(th, NULL), "");
zassert_equal(0xB105F00D, x, "");
}
#else
void test_posix_thread_attr_stacksize(void)
{
}
#endif
15 changes: 15 additions & 0 deletions tests/posix/common/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,18 @@ tests:
extra_configs:
- CONFIG_NEWLIB_LIBC=y
- CONFIG_TEST_HW_STACK_PROTECTION=n
portability.posix.common.dynamic_stack:
platform_exclude: nsim_sem_mpu_stack_guard nsim_em_mpu_stack_guard
extra_configs:
- CONFIG_NEWLIB_LIBC=n
- CONFIG_PTHREAD_DYNAMIC_STACKS=y
- CONFIG_IDLE_STACK_SIZE=768
- CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=16384
portability.posix.common.dynamic_stack.newlib:
platform_exclude: nsim_sem_mpu_stack_guard nsim_em_mpu_stack_guard
filter: TOOLCHAIN_HAS_NEWLIB == 1
extra_configs:
- CONFIG_NEWLIB_LIBC=y
- CONFIG_PTHREAD_DYNAMIC_STACKS=y
- CONFIG_IDLE_STACK_SIZE=768
- CONFIG_HEAP_MEM_POOL_SIZE=16384