Skip to content

Commit 88f09f2

Browse files
andyrosscfriedt
authored andcommitted
kernel/sched: Fix SMP race on pend
For historical reasons[1] suspending threads would release the scheduler lock between pend() (which places the current thread onto a wait queue) and z_swap() (which effects the context swtich). This process happens with the caller's lock held, so local interrupts are masked. But on SMP this opens a tiny race where another CPU could grab the pended thread and switch to it while we were still executing on its stack! Fix this by elevating the "lock swap" code that already exists in the (portable/switch-based) z_swap() code one level so that it happens in z_pend_curr() also. Now we hold the scheduler lock between pend and the final context switch. Note that this technique can't work for the older z_swap_irqlock() implementation, which exists to vestigially support a few bits of arch code (mostly direct interrupts) that don't work on SMP anyway. Address with an assert to prevent future misuse. [1] z_swap() is a historical API implemented in per-arch assembly for older architectures (like ARM32!). It was designed to be called with what at the time was a global IRQ lock, so it doesn't understand the idea of a separate scheduler lock. When we finally get all archictures on arch_switch() this design can be cleaned up quite a bit. Signed-off-by: Andy Ross <[email protected]> (cherry picked from commit c32f376)
1 parent 568c09c commit 88f09f2

File tree

1 file changed

+26
-11
lines changed

1 file changed

+26
-11
lines changed

kernel/sched.c

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -626,25 +626,23 @@ static void add_thread_timeout(struct k_thread *thread, k_timeout_t timeout)
626626
}
627627
}
628628

629-
static void pend(struct k_thread *thread, _wait_q_t *wait_q,
630-
k_timeout_t timeout)
629+
static void pend_locked(struct k_thread *thread, _wait_q_t *wait_q,
630+
k_timeout_t timeout)
631631
{
632632
#ifdef CONFIG_KERNEL_COHERENCE
633633
__ASSERT_NO_MSG(wait_q == NULL || arch_mem_coherent(wait_q));
634634
#endif
635-
636-
LOCKED(&sched_spinlock) {
637-
add_to_waitq_locked(thread, wait_q);
638-
}
639-
635+
add_to_waitq_locked(thread, wait_q);
640636
add_thread_timeout(thread, timeout);
641637
}
642638

643639
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q,
644640
k_timeout_t timeout)
645641
{
646642
__ASSERT_NO_MSG(thread == _current || is_thread_dummy(thread));
647-
pend(thread, wait_q, timeout);
643+
LOCKED(&sched_spinlock) {
644+
pend_locked(thread, wait_q, timeout);
645+
}
648646
}
649647

650648
static inline void unpend_thread_no_timeout(struct k_thread *thread)
@@ -686,7 +684,12 @@ void z_thread_timeout(struct _timeout *timeout)
686684

687685
int z_pend_curr_irqlock(uint32_t key, _wait_q_t *wait_q, k_timeout_t timeout)
688686
{
689-
pend(_current, wait_q, timeout);
687+
/* This is a legacy API for pre-switch architectures and isn't
688+
* correctly synchronized for multi-cpu use
689+
*/
690+
__ASSERT_NO_MSG(!IS_ENABLED(CONFIG_SMP));
691+
692+
pend_locked(_current, wait_q, timeout);
690693

691694
#if defined(CONFIG_TIMESLICING) && defined(CONFIG_SWAP_NONATOMIC)
692695
pending_current = _current;
@@ -709,8 +712,20 @@ int z_pend_curr(struct k_spinlock *lock, k_spinlock_key_t key,
709712
#if defined(CONFIG_TIMESLICING) && defined(CONFIG_SWAP_NONATOMIC)
710713
pending_current = _current;
711714
#endif
712-
pend(_current, wait_q, timeout);
713-
return z_swap(lock, key);
715+
__ASSERT_NO_MSG(sizeof(sched_spinlock) == 0 || lock != &sched_spinlock);
716+
717+
/* We do a "lock swap" prior to calling z_swap(), such that
718+
* the caller's lock gets released as desired. But we ensure
719+
* that we hold the scheduler lock and leave local interrupts
720+
* masked until we reach the context swich. z_swap() itself
721+
* has similar code; the duplication is because it's a legacy
722+
* API that doesn't expect to be called with scheduler lock
723+
* held.
724+
*/
725+
(void) k_spin_lock(&sched_spinlock);
726+
pend_locked(_current, wait_q, timeout);
727+
k_spin_release(lock);
728+
return z_swap(&sched_spinlock, key);
714729
}
715730

716731
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)

0 commit comments

Comments
 (0)