Skip to content

Commit f071306

Browse files
committed
pthread: facilitate dynamically allocated thread stacks
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 zephyrproject-rtos#25973 Signed-off-by: Christopher Friedt <[email protected]>
1 parent b5a3fe1 commit f071306

File tree

4 files changed

+131
-40
lines changed

4 files changed

+131
-40
lines changed

include/kernel.h

+14-24
Original file line numberDiff line numberDiff line change
@@ -234,20 +234,17 @@ extern void k_thread_foreach_unlocked(
234234
#define K_INHERIT_PERMS (BIT(3))
235235

236236
/**
237-
* @brief Callback item state
237+
* @brief dynamically allocated stack
238238
*
239-
* @details
240-
* This is a single bit of state reserved for "callback manager"
241-
* utilities (p4wq initially) who need to track operations invoked
242-
* from within a user-provided callback they have been invoked.
243-
* Effectively it serves as a tiny bit of zero-overhead TLS data.
239+
* This flag indicates that a thread stack has been heap-allocated with
240+
* @ref k_malloc.
244241
*/
245-
#define K_CALLBACK_STATE (BIT(4))
242+
#define K_STACK_ON_HEAP (BIT(4))
246243

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

250-
#if defined(CONFIG_FPU_SHARING) && defined(CONFIG_X86_SSE)
247+
#if defined(CONFIG_FPU_SHARING) && defined(CONFIG_SSE)
251248
/* thread uses SSEx (and also FP) registers */
252249
#define K_SSE_REGS (BIT(7))
253250
#endif
@@ -420,9 +417,7 @@ void k_thread_system_pool_assign(struct k_thread *thread);
420417
* to being aborted, self-exiting, or taking a fatal error. This API returns
421418
* immediately if the thread isn't running.
422419
*
423-
* This API may only be called from ISRs with a K_NO_WAIT timeout,
424-
* where it can be useful as a predicate to detect when a thread has
425-
* aborted.
420+
* This API may only be called from ISRs with a K_NO_WAIT timeout.
426421
*
427422
* @param thread Thread to wait to exit
428423
* @param timeout upper bound time to wait for the thread to exit.
@@ -539,17 +534,6 @@ __syscall k_tid_t k_current_get(void);
539534
* released. It is the responsibility of the caller of this routine to ensure
540535
* all necessary cleanup is performed.
541536
*
542-
* After k_thread_abort() returns, the thread is guaranteed not to be
543-
* running or to become runnable anywhere on the system. Normally
544-
* this is done via blocking the caller (in the same manner as
545-
* k_thread_join()), but in interrupt context on SMP systems the
546-
* implementation is required to spin for threads that are running on
547-
* other CPUs. Note that as specified, this means that on SMP
548-
* platforms it is possible for application code to create a deadlock
549-
* condition by simultaneously aborting a cycle of threads using at
550-
* least one termination from interrupt context. Zephyr cannot detect
551-
* all such conditions.
552-
*
553537
* @param thread ID of thread to abort.
554538
*
555539
* @return N/A
@@ -1969,8 +1953,8 @@ static inline void *z_impl_k_queue_peek_tail(struct k_queue *queue)
19691953
* A k_futex is a lightweight mutual exclusion primitive designed
19701954
* to minimize kernel involvement. Uncontended operation relies
19711955
* only on atomic access to shared memory. k_futex are tracked as
1972-
* kernel objects and can live in user memory so that any access
1973-
* bypasses the kernel object permission management mechanism.
1956+
* kernel objects and can live in user memory so any access bypass
1957+
* the kernel object permission management mechanism.
19741958
*/
19751959
struct k_futex {
19761960
atomic_t val;
@@ -4328,6 +4312,12 @@ void k_heap_free(struct k_heap *h, void *mem);
43284312
}, \
43294313
}
43304314

4315+
extern int z_mem_pool_alloc(struct k_mem_pool *pool, struct k_mem_block *block,
4316+
size_t size, k_timeout_t timeout);
4317+
extern void *z_mem_pool_malloc(struct k_mem_pool *pool, size_t size);
4318+
extern void z_mem_pool_free(struct k_mem_block *block);
4319+
extern void z_mem_pool_free_id(struct k_mem_block_id *id);
4320+
43314321
/**
43324322
* @}
43334323
*/

include/posix/pthread.h

+1
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ static inline int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
477477
return 0;
478478
}
479479

480+
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
480481
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
481482
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
482483
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

lib/posix/Kconfig

+24
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ config SEM_VALUE_MAX
3939
help
4040
Maximum semaphore count in POSIX compliant Application.
4141

42+
config PTHREAD_DYNAMIC_STACK
43+
bool "Support for dynamic stacks"
44+
select THREAD_STACK_INFO
45+
default y
46+
help
47+
POSIX 1003.1 allows a NULL pthread_attr_t* to be passed to
48+
pthread_create(3). However, Zephyr has traditionally required
49+
that the caller statically allocate a stack and pass it in via the
50+
pthread_attr_t*. With this option selected, NULL will be permitted
51+
and a suitable stack will be automatically allocated and assigned,
52+
inheriting permissions from the calling thread.
53+
54+
if PTHREAD_DYNAMIC_STACK
55+
config PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
56+
int "Default size for a dynamic pthread stack (in bytes)"
57+
default 1024
58+
help
59+
This value is used for the default size of dynamically-allocated
60+
stacks. However, users may still specify the size of
61+
dynamically-allocated stacks via pthread_attr_setstacksize(3)
62+
prior to calling pthread_create(3).
63+
64+
endif # PTHREAD_DYNAMIC_STACK
65+
4266
endif # PTHREAD_IPC
4367

4468
config POSIX_CLOCK

lib/posix/pthread.c

+92-16
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@
66

77
#include <kernel.h>
88
#include <stdio.h>
9+
#include <stdlib.h>
910
#include <sys/atomic.h>
1011
#include <ksched.h>
1112
#include <wait_q.h>
1213
#include <posix/pthread.h>
1314
#include <sys/slist.h>
1415

16+
#ifdef CONFIG_PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
17+
#define DYNAMIC_STACK_SIZE CONFIG_PTHREAD_DYNAMIC_STACK_DEFAULT_SIZE
18+
#else
19+
#define DYNAMIC_STACK_SIZE 0
20+
#endif
21+
1522
#define PTHREAD_INIT_FLAGS PTHREAD_CANCEL_ENABLE
1623
#define PTHREAD_CANCELED ((void *) -1)
1724

@@ -115,7 +122,10 @@ int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,
115122
static void zephyr_thread_wrapper(void *arg1, void *arg2, void *arg3)
116123
{
117124
void * (*fun_ptr)(void *) = arg3;
118-
125+
struct _thread_stack_info *stack_info
126+
= &k_current_get()->stack_info;
127+
stack_info->delta = (size_t)
128+
((uint8_t *)stack_info->start - (uint8_t *)arg2);
119129
fun_ptr(arg1);
120130
pthread_exit(NULL);
121131
}
@@ -135,17 +145,43 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr,
135145
uint32_t pthread_num;
136146
pthread_condattr_t cond_attr;
137147
struct posix_thread *thread;
148+
pthread_attr_t dynamic_attr;
149+
k_thread_stack_t *dynamic_stack = NULL;
150+
/* a non-const pthread_attr_t* that we can modify, if needed */
151+
pthread_attr_t *mattr = (pthread_attr_t *)attr;
138152

139-
/*
140-
* FIXME: Pthread attribute must be non-null and it provides stack
141-
* pointer and stack size. So even though POSIX 1003.1 spec accepts
142-
* attrib as NULL but zephyr needs it initialized with valid stack.
143-
*/
144-
if ((attr == NULL) || (attr->initialized == 0U)
145-
|| (attr->stack == NULL) || (attr->stacksize == 0)) {
153+
if (mattr != NULL && mattr->initialized == 0) {
146154
return EINVAL;
147155
}
148156

157+
if (mattr == NULL || mattr->stack == NULL) {
158+
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)) {
159+
/*
160+
* We dynamically allocate space when either
161+
* 1) attr == NULL -> use DYNAMIC_STACK_SIZE, or
162+
* 2) attr != NULL && attr->stack == NULL
163+
* -> allocate attr->stacksize
164+
*/
165+
if (mattr == NULL) {
166+
(void) pthread_attr_init(&dynamic_attr);
167+
dynamic_attr.stacksize = DYNAMIC_STACK_SIZE;
168+
mattr = &dynamic_attr;
169+
}
170+
171+
dynamic_stack = k_aligned_alloc(ARCH_STACK_PTR_ALIGN,
172+
Z_KERNEL_STACK_SIZE_ADJUST(mattr->stacksize));
173+
if (dynamic_stack == NULL) {
174+
return EAGAIN;
175+
}
176+
177+
__ASSERT_NO_MSG(dynamic_stack != NULL);
178+
mattr->stack = dynamic_stack;
179+
mattr->flags |= K_STACK_ON_HEAP;
180+
} else {
181+
return EINVAL;
182+
}
183+
}
184+
149185
pthread_mutex_lock(&pthread_pool_lock);
150186
for (pthread_num = 0;
151187
pthread_num < CONFIG_MAX_PTHREAD_COUNT; pthread_num++) {
@@ -158,10 +194,14 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr,
158194
pthread_mutex_unlock(&pthread_pool_lock);
159195

160196
if (pthread_num >= CONFIG_MAX_PTHREAD_COUNT) {
197+
if (IS_ENABLED(CONFIG_PTHREAD_DYNAMIC_STACK)
198+
&& dynamic_stack != NULL) {
199+
k_free(dynamic_stack);
200+
}
161201
return EAGAIN;
162202
}
163203

164-
prio = posix_to_zephyr_priority(attr->priority, attr->schedpolicy);
204+
prio = posix_to_zephyr_priority(mattr->priority, mattr->schedpolicy);
165205

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

174214
pthread_mutex_lock(&thread->cancel_lock);
175-
thread->cancel_state = (1 << _PTHREAD_CANCEL_POS) & attr->flags;
215+
thread->cancel_state = (1 << _PTHREAD_CANCEL_POS) & mattr->flags;
176216
thread->cancel_pending = 0;
177217
pthread_mutex_unlock(&thread->cancel_lock);
178218

179219
pthread_mutex_lock(&thread->state_lock);
180-
thread->state = attr->detachstate;
220+
thread->state = mattr->detachstate;
181221
pthread_mutex_unlock(&thread->state_lock);
182222

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

186-
*newthread = (pthread_t) k_thread_create(&thread->thread, attr->stack,
187-
attr->stacksize,
226+
*newthread = (pthread_t) k_thread_create(&thread->thread, mattr->stack,
227+
mattr->stacksize,
188228
(k_thread_entry_t)
189229
zephyr_thread_wrapper,
190-
(void *)arg, NULL,
230+
(void *)arg, dynamic_stack,
191231
threadroutine, prio,
192-
(~K_ESSENTIAL & attr->flags),
193-
K_MSEC(attr->delayedstart));
232+
(~K_ESSENTIAL & mattr->flags),
233+
K_MSEC(mattr->delayedstart));
194234
return 0;
195235
}
196236

@@ -347,6 +387,23 @@ int pthread_once(pthread_once_t *once, void (*init_func)(void))
347387
return 0;
348388
}
349389

390+
#ifdef CONFIG_PTHREAD_DYNAMIC_STACK
391+
static void zephyr_pthread_stack_reclaim(struct k_thread *thread)
392+
{
393+
uint8_t *p = (uint8_t *)thread->stack_info.start;
394+
395+
p -= thread->stack_info.delta;
396+
memset((void *)thread->stack_info.start, 0,
397+
thread->stack_info.size);
398+
k_free(p);
399+
}
400+
#else
401+
static inline void zephyr_pthread_stack_reclaim(struct k_thread *thread)
402+
{
403+
ARG_UNUSED(thread);
404+
}
405+
#endif
406+
350407
/**
351408
* @brief Terminate calling thread.
352409
*
@@ -385,6 +442,10 @@ void pthread_exit(void *retval)
385442
}
386443
}
387444

445+
if ((self->thread.base.user_options & K_STACK_ON_HEAP) != 0) {
446+
self->thread.fn_abort = zephyr_pthread_stack_reclaim;
447+
}
448+
388449
pthread_mutex_unlock(&self->state_lock);
389450
k_thread_abort((k_tid_t)self);
390451
}
@@ -534,6 +595,21 @@ int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)
534595
return 0;
535596
}
536597

598+
/**
599+
* @brief Set stack size attribute in thread attributes object.
600+
*
601+
* See IEEE 1003.1
602+
*/
603+
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
604+
{
605+
if ((attr == NULL) || (attr->initialized == 0U)) {
606+
return EINVAL;
607+
}
608+
609+
attr->stacksize = stacksize;
610+
return 0;
611+
}
612+
537613
/**
538614
* @brief Get stack size attribute in thread attributes object.
539615
*

0 commit comments

Comments
 (0)