kernel: add k_thread_join()
Callers will go to sleep until the thread exits, either normally or crashing out. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This commit is contained in:
parent
7ff4a632b7
commit
322816eada
3 changed files with 151 additions and 20 deletions
|
@ -500,6 +500,8 @@ struct _thread_base {
|
||||||
/* this thread's entry in a timeout queue */
|
/* this thread's entry in a timeout queue */
|
||||||
struct _timeout timeout;
|
struct _timeout timeout;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
_wait_q_t join_waiters;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct _thread_base _thread_base_t;
|
typedef struct _thread_base _thread_base_t;
|
||||||
|
@ -879,6 +881,26 @@ __syscall int k_thread_stack_space_get(const struct k_thread *thread,
|
||||||
void k_thread_system_pool_assign(struct k_thread *thread);
|
void k_thread_system_pool_assign(struct k_thread *thread);
|
||||||
#endif /* (CONFIG_HEAP_MEM_POOL_SIZE > 0) */
|
#endif /* (CONFIG_HEAP_MEM_POOL_SIZE > 0) */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sleep until a thread exits
|
||||||
|
*
|
||||||
|
* The caller will be put to sleep until the target thread exits, either due
|
||||||
|
* to being aborted, self-exiting, or taking a fatal error. This API returns
|
||||||
|
* immediately if the thread isn't running.
|
||||||
|
*
|
||||||
|
* This API may only be called from ISRs with a K_NO_WAIT timeout.
|
||||||
|
*
|
||||||
|
* @param thread Thread to wait to exit
|
||||||
|
* @param timeout non-negative upper bound time in ms to wait for the thread
|
||||||
|
* to exit.
|
||||||
|
* @retval 0 success, target thread has exited or wasn't running
|
||||||
|
* @retval -EBUSY returned without waiting
|
||||||
|
* @retval -EAGAIN waiting period timed out
|
||||||
|
* @retval -EDEADLK target thread is joining on the caller, or target thread
|
||||||
|
* is the caller
|
||||||
|
*/
|
||||||
|
__syscall int k_thread_join(struct k_thread *thread, s32_t timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Put the current thread to sleep.
|
* @brief Put the current thread to sleep.
|
||||||
*
|
*
|
||||||
|
|
147
kernel/sched.c
147
kernel/sched.c
|
@ -488,6 +488,8 @@ void z_thread_single_abort(struct k_thread *thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCKED(&sched_spinlock) {
|
LOCKED(&sched_spinlock) {
|
||||||
|
struct k_thread *waiter;
|
||||||
|
|
||||||
if (z_is_thread_ready(thread)) {
|
if (z_is_thread_ready(thread)) {
|
||||||
if (z_is_thread_queued(thread)) {
|
if (z_is_thread_queued(thread)) {
|
||||||
_priq_run_remove(&_kernel.ready_q.runq,
|
_priq_run_remove(&_kernel.ready_q.runq,
|
||||||
|
@ -517,20 +519,38 @@ void z_thread_single_abort(struct k_thread *thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
thread->base.thread_state |= mask;
|
thread->base.thread_state |= mask;
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
/* Clear initialized state so that this thread object may be
|
||||||
|
* re-used and triggers errors if API calls are made on it from
|
||||||
|
* user threads
|
||||||
|
*
|
||||||
|
* For a whole host of reasons this is not ideal and should be
|
||||||
|
* iterated on.
|
||||||
|
*/
|
||||||
|
z_object_uninit(thread->stack_obj);
|
||||||
|
z_object_uninit(thread);
|
||||||
|
|
||||||
|
/* Revoke permissions on thread's ID so that it may be
|
||||||
|
* recycled
|
||||||
|
*/
|
||||||
|
z_thread_perms_all_clear(thread);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Wake everybody up who was trying to join with this thread.
|
||||||
|
* A reschedule is invoked later by k_thread_abort().
|
||||||
|
*/
|
||||||
|
while ((waiter = z_waitq_head(&thread->base.join_waiters)) !=
|
||||||
|
NULL) {
|
||||||
|
_priq_wait_remove(&pended_on(waiter)->waitq, waiter);
|
||||||
|
z_mark_thread_as_not_pending(waiter);
|
||||||
|
waiter->base.pended_on = NULL;
|
||||||
|
arch_thread_return_value_set(waiter, 0);
|
||||||
|
ready_thread(waiter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_trace_thread_abort(thread);
|
sys_trace_thread_abort(thread);
|
||||||
|
|
||||||
#ifdef CONFIG_USERSPACE
|
|
||||||
/* Clear initialized state so that this thread object may be re-used
|
|
||||||
* and triggers errors if API calls are made on it from user threads
|
|
||||||
*/
|
|
||||||
z_object_uninit(thread->stack_obj);
|
|
||||||
z_object_uninit(thread);
|
|
||||||
|
|
||||||
/* Revoke permissions on thread's ID so that it may be recycled */
|
|
||||||
z_thread_perms_all_clear(thread);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unready_thread(struct k_thread *thread)
|
static void unready_thread(struct k_thread *thread)
|
||||||
|
@ -549,19 +569,21 @@ void z_remove_thread_from_ready_q(struct k_thread *thread)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pend(struct k_thread *thread, _wait_q_t *wait_q, s32_t timeout)
|
/* sched_spinlock must be held */
|
||||||
|
static void add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q)
|
||||||
{
|
{
|
||||||
LOCKED(&sched_spinlock) {
|
unready_thread(thread);
|
||||||
unready_thread(thread);
|
z_mark_thread_as_pending(thread);
|
||||||
z_mark_thread_as_pending(thread);
|
sys_trace_thread_pend(thread);
|
||||||
sys_trace_thread_pend(thread);
|
|
||||||
|
|
||||||
if (wait_q != NULL) {
|
if (wait_q != NULL) {
|
||||||
thread->base.pended_on = wait_q;
|
thread->base.pended_on = wait_q;
|
||||||
z_priq_wait_add(&wait_q->waitq, thread);
|
z_priq_wait_add(&wait_q->waitq, thread);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_thread_timeout_ms(struct k_thread *thread, s32_t timeout)
|
||||||
|
{
|
||||||
if (timeout != K_FOREVER) {
|
if (timeout != K_FOREVER) {
|
||||||
s32_t ticks;
|
s32_t ticks;
|
||||||
|
|
||||||
|
@ -575,6 +597,15 @@ static void pend(struct k_thread *thread, _wait_q_t *wait_q, s32_t timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void pend(struct k_thread *thread, _wait_q_t *wait_q, s32_t timeout)
|
||||||
|
{
|
||||||
|
LOCKED(&sched_spinlock) {
|
||||||
|
add_to_waitq_locked(thread, wait_q);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_thread_timeout_ms(thread, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q, s32_t timeout)
|
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q, s32_t timeout)
|
||||||
{
|
{
|
||||||
__ASSERT_NO_MSG(thread == _current || is_thread_dummy(thread));
|
__ASSERT_NO_MSG(thread == _current || is_thread_dummy(thread));
|
||||||
|
@ -1375,3 +1406,79 @@ int k_thread_cpu_mask_disable(k_tid_t thread, int cpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_SCHED_CPU_MASK */
|
#endif /* CONFIG_SCHED_CPU_MASK */
|
||||||
|
|
||||||
|
int z_impl_k_thread_join(struct k_thread *thread, s32_t timeout)
|
||||||
|
{
|
||||||
|
k_spinlock_key_t key;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
__ASSERT(((arch_is_in_isr() == false) || (timeout == K_NO_WAIT)), "");
|
||||||
|
|
||||||
|
key = k_spin_lock(&sched_spinlock);
|
||||||
|
|
||||||
|
if ((thread->base.pended_on == &_current->base.join_waiters) ||
|
||||||
|
(thread == _current)) {
|
||||||
|
ret = -EDEADLK;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((thread->base.thread_state & _THREAD_DEAD) != 0) {
|
||||||
|
ret = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == K_NO_WAIT) {
|
||||||
|
ret = -EBUSY;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_TIMESLICING) && defined(CONFIG_SWAP_NONATOMIC)
|
||||||
|
pending_current = _current;
|
||||||
|
#endif
|
||||||
|
add_to_waitq_locked(_current, &thread->base.join_waiters);
|
||||||
|
add_thread_timeout_ms(_current, timeout);
|
||||||
|
|
||||||
|
return z_swap(&sched_spinlock, key);
|
||||||
|
out:
|
||||||
|
k_spin_unlock(&sched_spinlock, key);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
/* Special case: don't oops if the thread is uninitialized. This is because
|
||||||
|
* the initialization bit does double-duty for thread objects; if false, means
|
||||||
|
* the thread object is truly uninitialized, or the thread ran and exited for
|
||||||
|
* some reason.
|
||||||
|
*
|
||||||
|
* Return true in this case indicating we should just do nothing and return
|
||||||
|
* success to the caller.
|
||||||
|
*/
|
||||||
|
static bool thread_obj_validate(struct k_thread *thread)
|
||||||
|
{
|
||||||
|
struct _k_object *ko = z_object_find(thread);
|
||||||
|
int ret = z_object_validate(ko, K_OBJ_THREAD, _OBJ_INIT_TRUE);
|
||||||
|
|
||||||
|
switch (ret) {
|
||||||
|
case 0:
|
||||||
|
return false;
|
||||||
|
case -EINVAL:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
#ifdef CONFIG_LOG
|
||||||
|
z_dump_object_error(ret, thread, ko, K_OBJ_THREAD);
|
||||||
|
#endif
|
||||||
|
Z_OOPS(Z_SYSCALL_VERIFY_MSG(ret, "access denied"));
|
||||||
|
}
|
||||||
|
CODE_UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int z_vrfy_k_thread_join(struct k_thread *thread, s32_t timeout)
|
||||||
|
{
|
||||||
|
if (thread_obj_validate(thread)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return z_impl_k_thread_join(thread, timeout);
|
||||||
|
}
|
||||||
|
#include <syscalls/k_thread_join_mrsh.c>
|
||||||
|
#endif /* CONFIG_USERSPACE */
|
||||||
|
|
|
@ -526,6 +526,8 @@ void z_setup_new_thread(struct k_thread *new_thread,
|
||||||
#endif
|
#endif
|
||||||
stack_size = adjust_stack_size(stack_size);
|
stack_size = adjust_stack_size(stack_size);
|
||||||
|
|
||||||
|
z_waitq_init(&new_thread->base.join_waiters);
|
||||||
|
|
||||||
#ifdef CONFIG_THREAD_USERSPACE_LOCAL_DATA
|
#ifdef CONFIG_THREAD_USERSPACE_LOCAL_DATA
|
||||||
#ifndef CONFIG_THREAD_USERSPACE_LOCAL_DATA_ARCH_DEFER_SETUP
|
#ifndef CONFIG_THREAD_USERSPACE_LOCAL_DATA_ARCH_DEFER_SETUP
|
||||||
/* reserve space on top of stack for local data */
|
/* reserve space on top of stack for local data */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue