It was possible with pathological timing (see below) for the scheduler
to pick a cycle of threads on each CPU and enter the context switch
path on all of them simultaneously.
Example:
* CPU0 is idle, CPU1 is running thread A
* CPU1 makes high priority thread B runnable
* CPU1 reaches a schedule point (or returns from an interrupt) and
decides to run thread B instead
* CPU0 simultaneously takes its IPI and returns, selecting thread A
Now both CPUs enter wait_for_switch() to spin, waiting for the context
switch code on the other thread to finish and mark the thread
runnable. So we have a deadlock, each CPU is spinning waiting for the
other!
Actually, in practice this seems not to happen on existing hardware
platforms, it's only exercisable in emulation. The reason is that the
hardware IPI time is much faster than the software paths required to
reach a schedule point or interrupt exit, so CPU1 always selects the
newly scheduled thread and no deadlock appears. I tried for a bit to
make this happen with a cycle of three threads, but it's complicated
to get right and I still couldn't get the timing to hit correctly. In
qemu, though, the IPI is implemented as a Unix signal sent to the
thread running the other CPU, which is far slower and opens the window
to see this happen.
The solution is simple enough: don't store the _current thread in the
run queue until we are on the tail end of the context switch path,
after wait_for_switch() and going to reach the end in guaranteed time.
Note that this requires changing a little logic to handle the yield
case: because we can no longer rely on _current's position in the run
queue to suppress it, we need to do the priority comparison directly
based on the existing "swap_ok" flag (which has always meant
"yielded", and maybe should be renamed).
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
298 lines
7.8 KiB
C
298 lines
7.8 KiB
C
/*
|
|
* Copyright (c) 2016-2017 Wind River Systems, Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#ifndef ZEPHYR_KERNEL_INCLUDE_KSCHED_H_
|
|
#define ZEPHYR_KERNEL_INCLUDE_KSCHED_H_
|
|
|
|
#include <kernel_structs.h>
|
|
#include <kernel_internal.h>
|
|
#include <timeout_q.h>
|
|
#include <tracing/tracing.h>
|
|
#include <stdbool.h>
|
|
|
|
BUILD_ASSERT(K_LOWEST_APPLICATION_THREAD_PRIO
|
|
>= K_HIGHEST_APPLICATION_THREAD_PRIO);
|
|
|
|
#ifdef CONFIG_MULTITHREADING
|
|
#define Z_VALID_PRIO(prio, entry_point) \
|
|
(((prio) == K_IDLE_PRIO && z_is_idle_thread_entry(entry_point)) || \
|
|
((K_LOWEST_APPLICATION_THREAD_PRIO \
|
|
>= K_HIGHEST_APPLICATION_THREAD_PRIO) \
|
|
&& (prio) >= K_HIGHEST_APPLICATION_THREAD_PRIO \
|
|
&& (prio) <= K_LOWEST_APPLICATION_THREAD_PRIO))
|
|
|
|
#define Z_ASSERT_VALID_PRIO(prio, entry_point) do { \
|
|
__ASSERT(Z_VALID_PRIO((prio), (entry_point)), \
|
|
"invalid priority (%d); allowed range: %d to %d", \
|
|
(prio), \
|
|
K_LOWEST_APPLICATION_THREAD_PRIO, \
|
|
K_HIGHEST_APPLICATION_THREAD_PRIO); \
|
|
} while (false)
|
|
#else
|
|
#define Z_VALID_PRIO(prio, entry_point) ((prio) == -1)
|
|
#define Z_ASSERT_VALID_PRIO(prio, entry_point) __ASSERT((prio) == -1, "")
|
|
#endif
|
|
|
|
void z_sched_init(void);
|
|
void z_move_thread_to_end_of_prio_q(struct k_thread *thread);
|
|
void z_remove_thread_from_ready_q(struct k_thread *thread);
|
|
int z_is_thread_time_slicing(struct k_thread *thread);
|
|
void z_unpend_thread_no_timeout(struct k_thread *thread);
|
|
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q);
|
|
int z_pend_curr(struct k_spinlock *lock, k_spinlock_key_t key,
|
|
_wait_q_t *wait_q, k_timeout_t timeout);
|
|
int z_pend_curr_irqlock(uint32_t key, _wait_q_t *wait_q, k_timeout_t timeout);
|
|
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q,
|
|
k_timeout_t timeout);
|
|
void z_reschedule(struct k_spinlock *lock, k_spinlock_key_t key);
|
|
void z_reschedule_irqlock(uint32_t key);
|
|
struct k_thread *z_unpend_first_thread(_wait_q_t *wait_q);
|
|
void z_unpend_thread(struct k_thread *thread);
|
|
int z_unpend_all(_wait_q_t *wait_q);
|
|
void z_thread_priority_set(struct k_thread *thread, int prio);
|
|
bool z_set_prio(struct k_thread *thread, int prio);
|
|
void *z_get_next_switch_handle(void *interrupted);
|
|
struct k_thread *z_find_first_thread_to_unpend(_wait_q_t *wait_q,
|
|
struct k_thread *from);
|
|
void idle(void *a, void *b, void *c);
|
|
void z_time_slice(int ticks);
|
|
void z_reset_time_slice(void);
|
|
void z_sched_abort(struct k_thread *thread);
|
|
void z_sched_ipi(void);
|
|
void z_sched_start(struct k_thread *thread);
|
|
void z_ready_thread(struct k_thread *thread);
|
|
void z_thread_single_abort(struct k_thread *thread);
|
|
FUNC_NORETURN void z_self_abort(void);
|
|
void z_requeue_current(struct k_thread *curr);
|
|
|
|
static inline void z_pend_curr_unlocked(_wait_q_t *wait_q, k_timeout_t timeout)
|
|
{
|
|
(void) z_pend_curr_irqlock(arch_irq_lock(), wait_q, timeout);
|
|
}
|
|
|
|
static inline void z_reschedule_unlocked(void)
|
|
{
|
|
(void) z_reschedule_irqlock(arch_irq_lock());
|
|
}
|
|
|
|
/* find which one is the next thread to run */
|
|
/* must be called with interrupts locked */
|
|
#ifdef CONFIG_SMP
|
|
extern struct k_thread *z_get_next_ready_thread(void);
|
|
#else
|
|
static ALWAYS_INLINE struct k_thread *z_get_next_ready_thread(void)
|
|
{
|
|
return _kernel.ready_q.cache;
|
|
}
|
|
#endif
|
|
|
|
static inline bool z_is_idle_thread_entry(void *entry_point)
|
|
{
|
|
return entry_point == idle;
|
|
}
|
|
|
|
static inline bool z_is_idle_thread_object(struct k_thread *thread)
|
|
{
|
|
#ifdef CONFIG_MULTITHREADING
|
|
#ifdef CONFIG_SMP
|
|
return thread->base.is_idle;
|
|
#else
|
|
return thread == &z_idle_threads[0];
|
|
#endif
|
|
#else
|
|
return false;
|
|
#endif /* CONFIG_MULTITHREADING */
|
|
}
|
|
|
|
static inline bool z_is_thread_suspended(struct k_thread *thread)
|
|
{
|
|
return (thread->base.thread_state & _THREAD_SUSPENDED) != 0U;
|
|
}
|
|
|
|
static inline bool z_is_thread_pending(struct k_thread *thread)
|
|
{
|
|
return (thread->base.thread_state & _THREAD_PENDING) != 0U;
|
|
}
|
|
|
|
static inline bool z_is_thread_prevented_from_running(struct k_thread *thread)
|
|
{
|
|
uint8_t state = thread->base.thread_state;
|
|
|
|
return (state & (_THREAD_PENDING | _THREAD_PRESTART | _THREAD_DEAD |
|
|
_THREAD_DUMMY | _THREAD_SUSPENDED)) != 0U;
|
|
|
|
}
|
|
|
|
static inline bool z_is_thread_timeout_active(struct k_thread *thread)
|
|
{
|
|
return !z_is_inactive_timeout(&thread->base.timeout);
|
|
}
|
|
|
|
static inline bool z_is_thread_ready(struct k_thread *thread)
|
|
{
|
|
return !((z_is_thread_prevented_from_running(thread)) != 0U ||
|
|
z_is_thread_timeout_active(thread));
|
|
}
|
|
|
|
static inline bool z_has_thread_started(struct k_thread *thread)
|
|
{
|
|
return (thread->base.thread_state & _THREAD_PRESTART) == 0U;
|
|
}
|
|
|
|
static inline bool z_is_thread_state_set(struct k_thread *thread, uint32_t state)
|
|
{
|
|
return (thread->base.thread_state & state) != 0U;
|
|
}
|
|
|
|
static inline bool z_is_thread_queued(struct k_thread *thread)
|
|
{
|
|
return z_is_thread_state_set(thread, _THREAD_QUEUED);
|
|
}
|
|
|
|
static inline void z_mark_thread_as_suspended(struct k_thread *thread)
|
|
{
|
|
thread->base.thread_state |= _THREAD_SUSPENDED;
|
|
sys_trace_thread_suspend(thread);
|
|
}
|
|
|
|
static inline void z_mark_thread_as_not_suspended(struct k_thread *thread)
|
|
{
|
|
thread->base.thread_state &= ~_THREAD_SUSPENDED;
|
|
sys_trace_thread_resume(thread);
|
|
}
|
|
|
|
static inline void z_mark_thread_as_started(struct k_thread *thread)
|
|
{
|
|
thread->base.thread_state &= ~_THREAD_PRESTART;
|
|
}
|
|
|
|
static inline void z_mark_thread_as_pending(struct k_thread *thread)
|
|
{
|
|
thread->base.thread_state |= _THREAD_PENDING;
|
|
}
|
|
|
|
static inline void z_mark_thread_as_not_pending(struct k_thread *thread)
|
|
{
|
|
thread->base.thread_state &= ~_THREAD_PENDING;
|
|
}
|
|
|
|
static inline void z_set_thread_states(struct k_thread *thread, uint32_t states)
|
|
{
|
|
thread->base.thread_state |= states;
|
|
}
|
|
|
|
static inline void z_reset_thread_states(struct k_thread *thread,
|
|
uint32_t states)
|
|
{
|
|
thread->base.thread_state &= ~states;
|
|
}
|
|
|
|
static inline bool z_is_under_prio_ceiling(int prio)
|
|
{
|
|
return prio >= CONFIG_PRIORITY_CEILING;
|
|
}
|
|
|
|
static inline int z_get_new_prio_with_ceiling(int prio)
|
|
{
|
|
return z_is_under_prio_ceiling(prio) ? prio : CONFIG_PRIORITY_CEILING;
|
|
}
|
|
|
|
static inline bool z_is_prio1_higher_than_or_equal_to_prio2(int prio1, int prio2)
|
|
{
|
|
return prio1 <= prio2;
|
|
}
|
|
|
|
static inline bool z_is_prio_higher_or_equal(int prio1, int prio2)
|
|
{
|
|
return z_is_prio1_higher_than_or_equal_to_prio2(prio1, prio2);
|
|
}
|
|
|
|
static inline bool z_is_prio1_lower_than_or_equal_to_prio2(int prio1, int prio2)
|
|
{
|
|
return prio1 >= prio2;
|
|
}
|
|
|
|
static inline bool z_is_prio1_higher_than_prio2(int prio1, int prio2)
|
|
{
|
|
return prio1 < prio2;
|
|
}
|
|
|
|
static inline bool z_is_prio_higher(int prio, int test_prio)
|
|
{
|
|
return z_is_prio1_higher_than_prio2(prio, test_prio);
|
|
}
|
|
|
|
static inline bool z_is_prio_lower_or_equal(int prio1, int prio2)
|
|
{
|
|
return z_is_prio1_lower_than_or_equal_to_prio2(prio1, prio2);
|
|
}
|
|
|
|
bool z_is_t1_higher_prio_than_t2(struct k_thread *t1, struct k_thread *t2);
|
|
|
|
static inline bool _is_valid_prio(int prio, void *entry_point)
|
|
{
|
|
if (prio == K_IDLE_PRIO && z_is_idle_thread_entry(entry_point)) {
|
|
return true;
|
|
}
|
|
|
|
if (!z_is_prio_higher_or_equal(prio,
|
|
K_LOWEST_APPLICATION_THREAD_PRIO)) {
|
|
return false;
|
|
}
|
|
|
|
if (!z_is_prio_lower_or_equal(prio,
|
|
K_HIGHEST_APPLICATION_THREAD_PRIO)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void _ready_one_thread(_wait_q_t *wq)
|
|
{
|
|
struct k_thread *thread = z_unpend_first_thread(wq);
|
|
|
|
if (thread != NULL) {
|
|
z_ready_thread(thread);
|
|
}
|
|
}
|
|
|
|
static inline void z_sched_lock(void)
|
|
{
|
|
#ifdef CONFIG_PREEMPT_ENABLED
|
|
__ASSERT(!arch_is_in_isr(), "");
|
|
__ASSERT(_current->base.sched_locked != 1, "");
|
|
|
|
--_current->base.sched_locked;
|
|
|
|
compiler_barrier();
|
|
|
|
#endif
|
|
}
|
|
|
|
static ALWAYS_INLINE void z_sched_unlock_no_reschedule(void)
|
|
{
|
|
#ifdef CONFIG_PREEMPT_ENABLED
|
|
__ASSERT(!arch_is_in_isr(), "");
|
|
__ASSERT(_current->base.sched_locked != 0, "");
|
|
|
|
compiler_barrier();
|
|
|
|
++_current->base.sched_locked;
|
|
#endif
|
|
}
|
|
|
|
static ALWAYS_INLINE bool z_is_thread_timeout_expired(struct k_thread *thread)
|
|
{
|
|
#ifdef CONFIG_SYS_CLOCK_EXISTS
|
|
return thread->base.timeout.dticks == _EXPIRED;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#endif /* ZEPHYR_KERNEL_INCLUDE_KSCHED_H_ */
|