zephyr/kernel/sched.c

987 lines
28 KiB
C
Raw Permalink Normal View History

unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
/*
* Copyright (c) 2018 Intel Corporation
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
*
* SPDX-License-Identifier: Apache-2.0
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
*/
#include <zephyr/kernel.h>
#include <kspinlock.h>
#include <ksched.h>
#include <zephyr/spinlock.h>
#include <wait_q.h>
#include <kthread.h>
#include <priority_q.h>
#include <kswap.h>
#include <ipi.h>
#include <kernel_arch_func.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <stdbool.h>
#include <kernel_internal.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/math_extras.h>
#include <zephyr/timing/timing.h>
#include <zephyr/sys/util.h>
#include <metairq.h>
#include <run_q.h>
#include <timeslicing.h>
LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
/* pending_current is owned by timeslicing.c; sleep.c also accesses it */
#if defined(CONFIG_SWAP_NONATOMIC) && defined(CONFIG_TIMESLICING)
extern struct k_thread *pending_current;
#endif
struct k_spinlock _sched_spinlock; /* The scheduler's spinlock */
/* Storage to "complete" the context switch from an invalid/incomplete thread
* context (ex: exiting an ISR that aborted _current)
*/
__incoherent struct k_thread _thread_dummy;
static ALWAYS_INLINE void update_cache(int preempt_ok);
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
static ALWAYS_INLINE void halt_thread(struct k_thread *thread, uint8_t new_state,
k_spinlock_key_t *key);
static void add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q);
/* Clear the halting bits (_THREAD_ABORTING and _THREAD_SUSPENDING) */
static inline void clear_halting(struct k_thread *thread)
{
if (IS_ENABLED(CONFIG_SMP) && (CONFIG_MP_MAX_NUM_CPUS > 1)) {
barrier_dmem_fence_full(); /* Other cpus spin on this locklessly! */
thread->base.thread_state &= ~(_THREAD_ABORTING | _THREAD_SUSPENDING);
}
}
static ALWAYS_INLINE struct k_thread *next_up(void)
{
#ifdef CONFIG_SMP
bool ipi_idle_target_rebound = false;
struct k_thread *ipi_idle_target = ipi_idle_reserved_take();
if (z_is_thread_halting(_current)) {
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
/* NULL key: scheduler context, no retry possible. _current
* cannot have an in-flight timeout (a running thread's
* timeout already fired and its handler returned), so the
* abort inside halt_thread won't see -EAGAIN.
*/
halt_thread(_current, z_is_thread_aborting(_current) ?
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
_THREAD_DEAD : _THREAD_SUSPENDED, NULL);
}
#endif /* CONFIG_SMP */
struct k_thread *thread = runq_best();
thread = metairq_preempt_recover(thread);
#ifndef CONFIG_SMP
/* In uniprocessor mode, we can leave the current thread in
* the queue (actually we have to, otherwise the assembly
* context switch code for all architectures would be
* responsible for putting it back in z_swap and ISR return!),
* which makes this choice simple.
*/
return (thread != NULL) ? thread : _current_cpu->idle_thread;
#else
/* Under SMP, the "cache" mechanism for selecting the next
* thread doesn't work, so we have more work to do to test
* _current against the best choice from the queue. Here, the
* thread selected above represents "the best thread that is
* not current".
*
* Subtle note on "queued": in SMP mode, neither _current nor
* metairq_premepted live in the queue, so this isn't exactly the
* same thing as "ready", it means "the thread already been
* added back to the queue such that we don't want to re-add it".
*/
bool queued = z_is_thread_queued(_current);
bool active = z_is_thread_ready(_current);
if (thread == NULL) {
thread = _current_cpu->idle_thread;
}
if (active) {
int32_t cmp = z_sched_prio_cmp(_current, thread);
kernel/sched: Fix rare SMP deadlock 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>
2021-02-08 08:28:54 -08:00
/* Ties only switch if state says we yielded */
if ((cmp > 0) || ((cmp == 0) && !_current_cpu->swap_ok)) {
thread = _current;
}
if (!should_preempt(thread, _current_cpu->swap_ok)) {
thread = _current;
}
}
if (thread != _current) {
update_metairq_preempt(thread);
/*
* Put _current back into the queue unless it is ..
* 1. not active (i.e., blocked, suspended, dead), or
* 2. already queued, or
* 3. the idle thread, or
* 4. preempted by a MetaIRQ thread
*/
if (active && !queued && !z_is_idle_thread_object(_current)
&& metairq_current_requeue_allowed()) {
queue_thread(_current);
}
}
/* Take the new _current out of the queue */
if (z_is_thread_queued(thread)) {
/* Remove or transfer the selected thread's idle CPU coverage. */
if (ipi_idle_target != NULL &&
thread != ipi_idle_target &&
z_is_thread_queued(ipi_idle_target)) {
ipi_idle_target_rebound =
ipi_idle_thread_rebind(thread, ipi_idle_target);
} else {
ipi_idle_thread_unreserve(thread);
}
dequeue_thread(thread);
}
kernel/sched: Fix rare SMP deadlock 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>
2021-02-08 08:28:54 -08:00
_current_cpu->swap_ok = false;
/* If this CPU consumed a different thread, preserve coverage for the
* runnable thread covered by this CPU's reservation.
*/
if (!ipi_idle_target_rebound &&
ipi_idle_target != NULL && z_is_thread_queued(ipi_idle_target)) {
flag_ipi(ipi_mask_create(ipi_idle_target));
}
return thread;
#endif /* CONFIG_SMP */
}
void move_current_to_end_of_prio_q(void)
{
runq_yield();
update_cache(1);
}
static ALWAYS_INLINE void update_cache(int preempt_ok)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
#ifndef CONFIG_SMP
struct k_thread *thread = next_up();
if (should_preempt(thread, preempt_ok)) {
#ifdef CONFIG_TIMESLICING
if (thread != _current) {
z_time_slice_reset(thread);
}
#endif /* CONFIG_TIMESLICING */
update_metairq_preempt(thread);
_kernel.ready_q.cache = thread;
} else {
_kernel.ready_q.cache = _current;
}
#else
/* The way this works is that the CPU record keeps its
* "cooperative swapping is OK" flag until the next reschedule
* call or context switch. It doesn't need to be tracked per
* thread because if the thread gets preempted for whatever
* reason the scheduler will make the same decision anyway.
*/
_current_cpu->swap_ok = preempt_ok;
#endif /* CONFIG_SMP */
}
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
/**
* Returns pointer to _cpu if the thread is currently running on
* another CPU.
*/
static struct _cpu *thread_active_elsewhere(struct k_thread *thread)
{
#ifdef CONFIG_SMP
int thread_cpu_id = thread->base.cpu;
struct _cpu *thread_cpu;
__ASSERT_NO_MSG((thread_cpu_id >= 0) &&
(thread_cpu_id < arch_num_cpus()));
thread_cpu = &_kernel.cpus[thread_cpu_id];
if ((thread_cpu->current == thread) && (thread_cpu != _current_cpu)) {
return thread_cpu;
}
#endif /* CONFIG_SMP */
ARG_UNUSED(thread);
return NULL;
}
static inline void ready_thread(struct k_thread *thread)
{
#ifdef CONFIG_KERNEL_COHERENCE
__ASSERT_NO_MSG(sys_cache_is_mem_coherent(thread));
#endif /* CONFIG_KERNEL_COHERENCE */
/* If thread is queued already, do not try and added it to the
* run queue again
*/
if (!z_is_thread_queued(thread) && z_is_thread_ready(thread)) {
SYS_PORT_TRACING_OBJ_FUNC(k_thread, sched_ready, thread);
queue_thread(thread);
update_cache(0);
flag_ipi(ipi_mask_create(thread));
}
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
}
void z_ready_thread(struct k_thread *thread)
{
Z_SCHED_SPINLOCK {
if (thread_active_elsewhere(thread) == NULL) {
ready_thread(thread);
}
}
}
void z_sched_ready_locked(struct k_thread *thread) ALIAS_OF(ready_thread);
static void unready_thread(struct k_thread *thread)
{
if (z_is_thread_queued(thread)) {
/* Clear idle CPU coverage before removing the thread from the run queue. */
ipi_idle_thread_unreserve(thread);
dequeue_thread(thread);
}
update_cache(thread == _current);
}
/* This routine exists for benchmarking purposes. It is not used in
* general production code.
*/
void z_unready_thread(struct k_thread *thread)
{
Z_SCHED_SPINLOCK {
unready_thread(thread);
}
}
void z_sched_unready_locked(struct k_thread *thread) ALIAS_OF(unready_thread);
/* This routine only used for testing purposes */
void z_yield_testing_only(void)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
Z_SCHED_SPINLOCK {
move_current_to_end_of_prio_q();
}
}
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
/* Spins in ISR context, waiting for a thread known to be running on
* another CPU to catch the IPI we sent and halt. Note that we check
* for ourselves being asynchronously halted first to prevent simple
* deadlocks (but not complex ones involving cycles of 3+ threads!).
* Acts to release the provided lock before returning.
*/
static void thread_halt_spin(struct k_thread *thread, k_spinlock_key_t key)
{
if (z_is_thread_halting(_current)) {
halt_thread(_current,
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
z_is_thread_aborting(_current) ? _THREAD_DEAD : _THREAD_SUSPENDED,
&key);
}
z_sched_spinlock_unlock(key);
while (z_is_thread_halting(thread)) {
unsigned int k = arch_irq_lock();
arch_spin_relax(); /* Requires interrupts be masked */
arch_irq_unlock(k);
}
}
/* Shared handler for k_thread_{suspend,abort}(). Called with the
* scheduler lock held and the key passed (which it may
* release/reacquire!) which will be released before a possible return
* (aborting _current will not return, obviously), which may be after
* a context switch.
*/
void z_thread_halt(struct k_thread *thread, k_spinlock_key_t key,
bool terminate)
{
_wait_q_t *wq = &thread->join_queue;
#ifdef CONFIG_SMP
wq = terminate ? wq : &thread->halt_queue;
#endif
z_metairq_preempted_clear(thread);
/* If the target is a thread running on another CPU, flag and
* poke (note that we might spin to wait, so a true
* synchronous IPI is needed here, not deferred!), it will
* halt itself in the IPI. Otherwise it's unscheduled, so we
* can clean it up directly.
*/
struct _cpu *cpu = thread_active_elsewhere(thread);
if (cpu != NULL) {
thread->base.thread_state |= (terminate ? _THREAD_ABORTING
: _THREAD_SUSPENDING);
#if defined(CONFIG_SMP) && defined(CONFIG_SCHED_IPI_SUPPORTED)
#ifdef CONFIG_ARCH_HAS_DIRECTED_IPIS
arch_sched_directed_ipi(IPI_CPU_MASK(cpu->id));
#else
arch_sched_broadcast_ipi();
#endif /* CONFIG_ARCH_HAS_DIRECTED_IPIS */
#endif /* CONFIG_SMP && CONFIG_SCHED_IPI_SUPPORTED */
if (arch_is_in_isr()) {
thread_halt_spin(thread, key);
} else {
add_to_waitq_locked(_current, wq);
z_swap_locked(key);
}
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
/* The target's next_up self-halt path passed NULL to
* halt_thread() and could not retry on -EAGAIN; an
* in-flight handler on a third CPU may not have run yet
* (it is blocked on the scheduler spinlock and will only
* acquire it after we drop it via the swap/spin above). Wait
* now, outside any lock, before the caller may free the thread
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
* storage. The handler, when it runs, sees _THREAD_DEAD /
* _THREAD_SUSPENDED and either bails (killed check) or
* no-ops in ready_thread() (z_is_thread_ready() rejects
* suspended threads). After this loop returns, no further
* dereference of thread->base will occur.
*/
while (z_try_abort_thread_timeout(thread) == -EAGAIN) {
}
} else {
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
halt_thread(thread, terminate ? _THREAD_DEAD : _THREAD_SUSPENDED, &key);
if ((thread == _current) && !arch_is_in_isr()) {
if (z_is_thread_essential(thread)) {
z_sched_spinlock_unlock(key);
k_panic();
key = z_sched_spinlock_lock();
}
z_swap_locked(key);
__ASSERT(!terminate, "aborted _current back from dead");
} else {
z_sched_spinlock_unlock(key);
}
}
/* NOTE: the scheduler lock has been released. Don't put
* logic here, it's likely to be racy/deadlocky even if you
* re-take the lock!
*/
}
static inline bool resched(uint32_t key)
{
#ifdef CONFIG_SMP
_current_cpu->swap_ok = 0;
#endif /* CONFIG_SMP */
return arch_irq_unlocked(key) && !arch_is_in_isr();
}
/*
* Check if the next ready thread is the same as the current thread
* and save the trip if true.
*/
static inline bool need_swap(void)
{
/* the SMP case will be handled in C based z_swap() */
#ifdef CONFIG_SMP
return true;
#else
struct k_thread *new_thread;
/* Check if the next ready thread is the same as the current thread */
new_thread = _kernel.ready_q.cache;
return new_thread != _current;
#endif /* CONFIG_SMP */
}
static void reschedule(struct k_spinlock *lock, k_spinlock_key_t key)
{
if (resched(key.key) && need_swap()) {
z_swap(lock, key);
} else {
signal_pending_ipi();
kernel/sched: fix race in consuming self-directed IPIs Move signal_pending_ipi() inside the K_SPINLOCK block in z_get_next_switch_handle(). Calling it after the lock release creates a window where a CPU can consume its own pending IPI bit via atomic_clear in signal_pending_ipi(), then silently drop it in arch_sched_directed_ipi() which skips the calling CPU (i == id). In configurations where secondary CPUs have a single pinned thread and take no timer or external interrupts, this can lead to a permanent hang: the idle CPU can only be woken by IPIs, but no IPIs are pending and no timeslicing IPIs will be generated since the idle thread is not sliceable. This was reproduced when running under QEMU with the following sequence of events observed: CPU 0 CPU 1 ───── ───── Thread calls k_poll(K_MSEC(1)) z_pend_curr(): mark thread PENDING z_add_timeout(1ms) do_swap() to idle thread WFI Timer tick fires sys_clock_announce(): slice_timeout(cpu1): flag_ipi(BIT(1)) signal_pending_ipi(): MSIP[cpu1] = 1 CPU1 wakes from WFI z_get_next_switch_handle(): acquire _sched_spinlock next_up() → idle (thread still PENDING, timeout hasn't fired yet) release _sched_spinlock Timer tick fires sys_clock_announce(): z_thread_timeout(thread): z_unpend_thread(thread) z_ready_thread(thread): flag_ipi(BIT(1)) signal_pending_ipi(): atomic_clear(pending_ipi) returns BIT(1) arch_sched_directed_ipi(BIT(1)) skips self, IPI silently lost return to idle thread WFI thread still on ready queue Such an interleaving of events is, of course, likely only reproducible in practice in virtualized environments where (v)CPUs can be descheduled. With signal_pending_ipi() inside the lock, next_up() and the IPI dispatch are atomic. Either the concurrent flag_ipi lands before the lock is acquired (and next_up sees the thread), or it lands after the lock is released (and the caller dispatches the IPI). There is no window where a CPU can consume its own bit for a thread it hasn't seen. Similar races exist in reschedule() and z_reschedule_irqlock() as well. Although they won't cause the same permanent hang described above, it can result in unnecessary rescheduling latency. Fix reschedule(), and add a TODO to z_reschedule_irqlock(); it doesn't not currently take the sched spinlock. Signed-off-by: Andrew Bresticker <abrestic@meta.com>
2026-03-16 09:10:53 -07:00
k_spin_unlock(lock, key);
}
}
/**
* Like reschedule(), but the scheduler's spinlock is known to be the lock.
*/
static void reschedule_locked(k_spinlock_key_t key)
{
return reschedule(&_sched_spinlock, key);
}
void z_sched_lock_reschedule(k_spinlock_key_t key)
{
update_cache(0);
reschedule_locked(key);
}
void z_sched_yield(void)
{
k_spinlock_key_t key = z_sched_spinlock_lock();
runq_yield();
update_cache(1);
z_swap_locked(key);
}
/* The scheduler's spinlock must be held */
static void add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q)
kernel/arch: enhance the "ready thread" cache The way the ready thread cache was implemented caused it to not always be "hot", i.e. there could be some misses, which happened when the cached thread was taken out of the ready queue. When that happened, it was not replaced immediately, since doing so could mean that the replacement might not run because the flow could be interrupted and another thread could take its place. This was the more conservative approach that insured that moving a thread to the cache would never be wasted. However, this caused two problems: 1. The cache could not be refilled until another thread context-switched in, since there was no thread in the cache to compare priorities against. 2. Interrupt exit code would always have to call into C to find what thread to run when the current thread was not coop and did not have the scheduler locked. Furthermore, it was possible for this code path to encounter a cold cache and then it had to find out what thread to run the long way. To fix this, filling the cache is now more aggressive, i.e. the next thread to put in the cache is found even in the case the current cached thread is context-switched out. This ensures the interrupt exit code is much faster on the slow path. In addition, since finding the next thread to run is now always "get it from the cache", which is a simple fetch from memory (_kernel.ready_q.cache), there is no need to call the more complex C code. On the ARM FRDM K64F board, this improvement is seen: Before: 1- Measure time to switch from ISR back to interrupted task switching time is 215 tcs = 1791 nsec 2- Measure time from ISR to executing a different task (rescheduled) switch time is 315 tcs = 2625 nsec After: 1- Measure time to switch from ISR back to interrupted task switching time is 130 tcs = 1083 nsec 2- Measure time from ISR to executing a different task (rescheduled) switch time is 225 tcs = 1875 nsec These are the most dramatic improvements, but most of the numbers generated by the latency_measure test are improved. Fixes ZEP-1401. Change-Id: I2eaac147048b1ec71a93bd0a285e743a39533973 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-12-02 10:37:27 -05:00
{
/* A thread must not already be on a wait queue when added to a new one. */
__ASSERT_NO_MSG(thread->base.pended_on == NULL);
unready_thread(thread);
z_mark_thread_as_pending(thread);
SYS_PORT_TRACING_FUNC(k_thread, sched_pend, thread);
kernel/arch: enhance the "ready thread" cache The way the ready thread cache was implemented caused it to not always be "hot", i.e. there could be some misses, which happened when the cached thread was taken out of the ready queue. When that happened, it was not replaced immediately, since doing so could mean that the replacement might not run because the flow could be interrupted and another thread could take its place. This was the more conservative approach that insured that moving a thread to the cache would never be wasted. However, this caused two problems: 1. The cache could not be refilled until another thread context-switched in, since there was no thread in the cache to compare priorities against. 2. Interrupt exit code would always have to call into C to find what thread to run when the current thread was not coop and did not have the scheduler locked. Furthermore, it was possible for this code path to encounter a cold cache and then it had to find out what thread to run the long way. To fix this, filling the cache is now more aggressive, i.e. the next thread to put in the cache is found even in the case the current cached thread is context-switched out. This ensures the interrupt exit code is much faster on the slow path. In addition, since finding the next thread to run is now always "get it from the cache", which is a simple fetch from memory (_kernel.ready_q.cache), there is no need to call the more complex C code. On the ARM FRDM K64F board, this improvement is seen: Before: 1- Measure time to switch from ISR back to interrupted task switching time is 215 tcs = 1791 nsec 2- Measure time from ISR to executing a different task (rescheduled) switch time is 315 tcs = 2625 nsec After: 1- Measure time to switch from ISR back to interrupted task switching time is 130 tcs = 1083 nsec 2- Measure time from ISR to executing a different task (rescheduled) switch time is 225 tcs = 1875 nsec These are the most dramatic improvements, but most of the numbers generated by the latency_measure test are improved. Fixes ZEP-1401. Change-Id: I2eaac147048b1ec71a93bd0a285e743a39533973 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-12-02 10:37:27 -05:00
if (wait_q != NULL) {
thread->base.pended_on = wait_q;
_priq_wait_add(&wait_q->waitq, thread);
}
}
void z_sched_add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q)
ALIAS_OF(add_to_waitq_locked);
static void pend_locked(struct k_thread *thread, _wait_q_t *wait_q,
k_timeout_t timeout)
{
#ifdef CONFIG_KERNEL_COHERENCE
__ASSERT_NO_MSG(wait_q == NULL || sys_cache_is_mem_coherent(wait_q));
#endif /* CONFIG_KERNEL_COHERENCE */
add_to_waitq_locked(thread, wait_q);
z_add_thread_timeout(thread, timeout);
}
kernel/timeout: Make timeout arguments an opaque type Add a k_timeout_t type, and use it everywhere that kernel API functions were accepting a millisecond timeout argument. Instead of forcing milliseconds everywhere (which are often not integrally representable as system ticks), do the conversion to ticks at the point where the timeout is created. This avoids an extra unit conversion in some application code, and allows us to express the timeout in units other than milliseconds to achieve greater precision. The existing K_MSEC() et. al. macros now return initializers for a k_timeout_t. The K_NO_WAIT and K_FOREVER constants have now become k_timeout_t values, which means they cannot be operated on as integers. Applications which have their own APIs that need to inspect these vs. user-provided timeouts can now use a K_TIMEOUT_EQ() predicate to test for equality. Timer drivers, which receive an integer tick count in ther z_clock_set_timeout() functions, now use the integer-valued K_TICKS_FOREVER constant instead of K_FOREVER. For the initial release, to preserve source compatibility, a CONFIG_LEGACY_TIMEOUT_API kconfig is provided. When true, the k_timeout_t will remain a compatible 32 bit value that will work with any legacy Zephyr application. Some subsystems present timeout (or timeout-like) values to their own users as APIs that would re-use the kernel's own constants and conventions. These will require some minor design work to adapt to the new scheme (in most cases just using k_timeout_t directly in their own API), and they have not been changed in this patch, instead selecting CONFIG_LEGACY_TIMEOUT_API via kconfig. These subsystems include: CAN Bus, the Microbit display driver, I2S, LoRa modem drivers, the UART Async API, Video hardware drivers, the console subsystem, and the network buffer abstraction. k_sleep() now takes a k_timeout_t argument, with a k_msleep() variant provided that works identically to the original API. Most of the changes here are just type/configuration management and documentation, but there are logic changes in mempool, where a loop that used a timeout numerically has been reworked using a new z_timeout_end_calc() predicate. Also in queue.c, a (when POLL was enabled) a similar loop was needlessly used to try to retry the k_poll() call after a spurious failure. But k_poll() does not fail spuriously, so the loop was removed. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2020-03-05 15:18:14 -08:00
void z_pend_thread(struct k_thread *thread, _wait_q_t *wait_q,
k_timeout_t timeout)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
__ASSERT_NO_MSG(thread == _current || is_thread_dummy(thread));
Z_SCHED_SPINLOCK {
pend_locked(thread, wait_q, timeout);
}
}
void z_unpend_thread_no_timeout(struct k_thread *thread)
{
Z_SCHED_SPINLOCK {
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
}
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
}
void z_sched_wake_thread_locked(struct k_thread *thread)
{
/* No K_SPINLOCK: caller must hold the scheduler's spinlock when calling */
bool killed = (thread->base.thread_state &
(_THREAD_DEAD | _THREAD_ABORTING));
if (!killed) {
/* The thread is not being killed */
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
z_mark_thread_as_not_sleeping(thread);
ready_thread(thread);
}
}
#ifdef CONFIG_SYS_CLOCK_EXISTS
/* Timeout handler for *_thread_timeout() APIs */
void z_thread_timeout(struct _timeout *timeout)
{
struct k_thread *thread = CONTAINER_OF(timeout,
struct k_thread, base.timeout);
Z_SCHED_SPINLOCK {
2026-05-27 18:29:25 -04:00
/* A concurrent waker (e.g. a sem give on another CPU) may
* have unpended and readied the thread, after which the
* thread could run and re-pend elsewhere -- possibly with no
* timeout. Such a waker aborts this timeout, flagging it
* superseded; bail so we don't wake the thread from its new
* wait.
*/
if (!z_timeout_inflight_superseded(timeout)) {
z_sched_wake_thread_locked(thread);
}
}
}
#endif /* CONFIG_SYS_CLOCK_EXISTS */
int z_pend_curr(struct k_spinlock *lock, k_spinlock_key_t key,
kernel/timeout: Make timeout arguments an opaque type Add a k_timeout_t type, and use it everywhere that kernel API functions were accepting a millisecond timeout argument. Instead of forcing milliseconds everywhere (which are often not integrally representable as system ticks), do the conversion to ticks at the point where the timeout is created. This avoids an extra unit conversion in some application code, and allows us to express the timeout in units other than milliseconds to achieve greater precision. The existing K_MSEC() et. al. macros now return initializers for a k_timeout_t. The K_NO_WAIT and K_FOREVER constants have now become k_timeout_t values, which means they cannot be operated on as integers. Applications which have their own APIs that need to inspect these vs. user-provided timeouts can now use a K_TIMEOUT_EQ() predicate to test for equality. Timer drivers, which receive an integer tick count in ther z_clock_set_timeout() functions, now use the integer-valued K_TICKS_FOREVER constant instead of K_FOREVER. For the initial release, to preserve source compatibility, a CONFIG_LEGACY_TIMEOUT_API kconfig is provided. When true, the k_timeout_t will remain a compatible 32 bit value that will work with any legacy Zephyr application. Some subsystems present timeout (or timeout-like) values to their own users as APIs that would re-use the kernel's own constants and conventions. These will require some minor design work to adapt to the new scheme (in most cases just using k_timeout_t directly in their own API), and they have not been changed in this patch, instead selecting CONFIG_LEGACY_TIMEOUT_API via kconfig. These subsystems include: CAN Bus, the Microbit display driver, I2S, LoRa modem drivers, the UART Async API, Video hardware drivers, the console subsystem, and the network buffer abstraction. k_sleep() now takes a k_timeout_t argument, with a k_msleep() variant provided that works identically to the original API. Most of the changes here are just type/configuration management and documentation, but there are logic changes in mempool, where a loop that used a timeout numerically has been reworked using a new z_timeout_end_calc() predicate. Also in queue.c, a (when POLL was enabled) a similar loop was needlessly used to try to retry the k_poll() call after a spurious failure. But k_poll() does not fail spuriously, so the loop was removed. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2020-03-05 15:18:14 -08:00
_wait_q_t *wait_q, k_timeout_t timeout)
{
/* A blocking pend from ISR context is a programming error with no
* safe recovery: it would sleep whatever thread was interrupted and,
* on CONFIG_SWAP_NONATOMIC, corrupt the scheduler. Refuse it fatally
* in every build rather than degrading into the corruption traced in
* #111518. Placed first so the refusal performs no side effect.
*/
if (arch_is_in_isr()) {
__ASSERT(false, "blocking pend from ISR context");
k_panic();
}
#if defined(CONFIG_TIMESLICING) && defined(CONFIG_SWAP_NONATOMIC)
pending_current = _current;
#endif /* CONFIG_TIMESLICING && CONFIG_SWAP_NONATOMIC */
__ASSERT_NO_MSG((sizeof(struct k_spinlock) == 0) || !z_is_sched_spinlock(lock));
/* We do a "lock swap" prior to calling z_swap(), such that
* the caller's lock gets released as desired. But we ensure
* that we hold the scheduler lock and leave local interrupts
* masked until we reach the context switch. z_swap() itself
* has similar code; the duplication is because it's a legacy
* API that doesn't expect to be called with scheduler lock
* held.
*/
(void) z_sched_spinlock_lock();
pend_locked(_current, wait_q, timeout);
k_spin_release(lock);
return z_swap_locked(key);
}
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)
{
struct k_thread *thread = NULL;
Z_SCHED_SPINLOCK {
thread = _priq_wait_best(&wait_q->waitq);
if (thread != NULL) {
unpend_thread_no_timeout(thread);
}
}
return thread;
}
void z_unpend_thread(struct k_thread *thread)
{
k_spinlock_key_t key = z_sched_spinlock_lock();
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
while (z_try_abort_thread_timeout(thread) == -EAGAIN) {
z_sched_spinlock_unlock(key);
key = z_sched_spinlock_lock();
}
z_sched_spinlock_unlock(key);
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
}
/* Priority set utility that does no rescheduling, it just changes the
* run queue state, returning true if a reschedule is needed later.
*/
bool z_thread_prio_set(struct k_thread *thread, int prio)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
bool need_sched = false;
int old_prio = thread->base.prio;
Z_SCHED_SPINLOCK {
need_sched = z_is_thread_ready(thread);
if (need_sched) {
if (!IS_ENABLED(CONFIG_SMP) || z_is_thread_queued(thread)) {
dequeue_thread(thread);
thread->base.prio = prio;
queue_thread(thread);
if (old_prio > prio) {
flag_ipi(ipi_mask_create(thread));
}
} else {
/*
* This is a running thread on SMP. Update its
* priority, but do not requeue it. An IPI is
* needed if the priority is both being lowered
* and it is running on another CPU.
*/
thread->base.prio = prio;
struct _cpu *cpu;
cpu = thread_active_elsewhere(thread);
if ((cpu != NULL) && (old_prio < prio)) {
flag_ipi(IPI_CPU_MASK(cpu->id));
}
}
update_cache(1);
} else if (z_is_thread_pending(thread)) {
/* Thread is pending, remove it from the waitq
* and reinsert it with the new priority to avoid
* violating waitq ordering and rb assumptions.
*/
_wait_q_t *wait_q = pended_on_thread(thread);
_priq_wait_remove(&wait_q->waitq, thread);
thread->base.prio = prio;
_priq_wait_add(&wait_q->waitq, thread);
} else {
thread->base.prio = prio;
}
}
SYS_PORT_TRACING_OBJ_FUNC(k_thread, sched_priority_set, thread, prio);
return need_sched;
}
void z_reschedule(struct k_spinlock *lock, k_spinlock_key_t key) ALIAS_OF(reschedule);
void z_reschedule_locked(k_spinlock_key_t key) ALIAS_OF(reschedule_locked);
void z_reschedule_irqlock(uint32_t key)
{
if (resched(key) && need_swap()) {
z_swap_irqlock(key);
} else {
/* TODO: We only hold the IRQ lock here, not the scheduler's
* spinlock, violating the locking requirement documented in
kernel/sched: fix race in consuming self-directed IPIs Move signal_pending_ipi() inside the K_SPINLOCK block in z_get_next_switch_handle(). Calling it after the lock release creates a window where a CPU can consume its own pending IPI bit via atomic_clear in signal_pending_ipi(), then silently drop it in arch_sched_directed_ipi() which skips the calling CPU (i == id). In configurations where secondary CPUs have a single pinned thread and take no timer or external interrupts, this can lead to a permanent hang: the idle CPU can only be woken by IPIs, but no IPIs are pending and no timeslicing IPIs will be generated since the idle thread is not sliceable. This was reproduced when running under QEMU with the following sequence of events observed: CPU 0 CPU 1 ───── ───── Thread calls k_poll(K_MSEC(1)) z_pend_curr(): mark thread PENDING z_add_timeout(1ms) do_swap() to idle thread WFI Timer tick fires sys_clock_announce(): slice_timeout(cpu1): flag_ipi(BIT(1)) signal_pending_ipi(): MSIP[cpu1] = 1 CPU1 wakes from WFI z_get_next_switch_handle(): acquire _sched_spinlock next_up() → idle (thread still PENDING, timeout hasn't fired yet) release _sched_spinlock Timer tick fires sys_clock_announce(): z_thread_timeout(thread): z_unpend_thread(thread) z_ready_thread(thread): flag_ipi(BIT(1)) signal_pending_ipi(): atomic_clear(pending_ipi) returns BIT(1) arch_sched_directed_ipi(BIT(1)) skips self, IPI silently lost return to idle thread WFI thread still on ready queue Such an interleaving of events is, of course, likely only reproducible in practice in virtualized environments where (v)CPUs can be descheduled. With signal_pending_ipi() inside the lock, next_up() and the IPI dispatch are atomic. Either the concurrent flag_ipi lands before the lock is acquired (and next_up sees the thread), or it lands after the lock is released (and the caller dispatches the IPI). There is no window where a CPU can consume its own bit for a thread it hasn't seen. Similar races exist in reschedule() and z_reschedule_irqlock() as well. Although they won't cause the same permanent hang described above, it can result in unnecessary rescheduling latency. Fix reschedule(), and add a TODO to z_reschedule_irqlock(); it doesn't not currently take the sched spinlock. Signed-off-by: Andrew Bresticker <abrestic@meta.com>
2026-03-16 09:10:53 -07:00
* signal_pending_ipi(). This can result in added delayed
* rescheduling.
*/
signal_pending_ipi();
kernel/sched: fix race in consuming self-directed IPIs Move signal_pending_ipi() inside the K_SPINLOCK block in z_get_next_switch_handle(). Calling it after the lock release creates a window where a CPU can consume its own pending IPI bit via atomic_clear in signal_pending_ipi(), then silently drop it in arch_sched_directed_ipi() which skips the calling CPU (i == id). In configurations where secondary CPUs have a single pinned thread and take no timer or external interrupts, this can lead to a permanent hang: the idle CPU can only be woken by IPIs, but no IPIs are pending and no timeslicing IPIs will be generated since the idle thread is not sliceable. This was reproduced when running under QEMU with the following sequence of events observed: CPU 0 CPU 1 ───── ───── Thread calls k_poll(K_MSEC(1)) z_pend_curr(): mark thread PENDING z_add_timeout(1ms) do_swap() to idle thread WFI Timer tick fires sys_clock_announce(): slice_timeout(cpu1): flag_ipi(BIT(1)) signal_pending_ipi(): MSIP[cpu1] = 1 CPU1 wakes from WFI z_get_next_switch_handle(): acquire _sched_spinlock next_up() → idle (thread still PENDING, timeout hasn't fired yet) release _sched_spinlock Timer tick fires sys_clock_announce(): z_thread_timeout(thread): z_unpend_thread(thread) z_ready_thread(thread): flag_ipi(BIT(1)) signal_pending_ipi(): atomic_clear(pending_ipi) returns BIT(1) arch_sched_directed_ipi(BIT(1)) skips self, IPI silently lost return to idle thread WFI thread still on ready queue Such an interleaving of events is, of course, likely only reproducible in practice in virtualized environments where (v)CPUs can be descheduled. With signal_pending_ipi() inside the lock, next_up() and the IPI dispatch are atomic. Either the concurrent flag_ipi lands before the lock is acquired (and next_up sees the thread), or it lands after the lock is released (and the caller dispatches the IPI). There is no window where a CPU can consume its own bit for a thread it hasn't seen. Similar races exist in reschedule() and z_reschedule_irqlock() as well. Although they won't cause the same permanent hang described above, it can result in unnecessary rescheduling latency. Fix reschedule(), and add a TODO to z_reschedule_irqlock(); it doesn't not currently take the sched spinlock. Signed-off-by: Andrew Bresticker <abrestic@meta.com>
2026-03-16 09:10:53 -07:00
irq_unlock(key);
}
}
struct k_thread *z_swap_next_thread(void)
{
struct k_thread *ret = next_up();
if (ret == _current) {
/* When not swapping, have to signal IPIs here. In
* the context switch case it must happen later, after
* _current gets requeued.
*/
signal_pending_ipi();
}
return ret;
}
#ifdef CONFIG_USE_SWITCH
/* Just a wrapper around z_current_thread_set(xxx) with tracing */
static inline void set_current(struct k_thread *new_thread)
{
/* If the new thread is the same as the current thread, we
* don't need to do anything.
*/
if (IS_ENABLED(CONFIG_INSTRUMENT_THREAD_SWITCHING) && new_thread != _current) {
z_thread_mark_switched_out();
}
z_current_thread_set(new_thread);
}
/**
* @brief Determine next thread to execute upon completion of an interrupt
*
* Thread preemption is performed by context switching after the completion
* of a non-recursed interrupt. This function determines which thread to
* switch to if any. This function accepts as @p interrupted either:
*
* - The handle for the interrupted thread in which case the thread's context
* must already be fully saved and ready to be picked up by a different CPU.
*
* - NULL if more work is required to fully save the thread's state after
* it is known that a new thread is to be scheduled. It is up to the caller
* to store the handle resulting from the thread that is being switched out
* in that thread's "switch_handle" field after its
* context has fully been saved, following the same requirements as with
* the @ref arch_switch() function.
*
* If a new thread needs to be scheduled then its handle is returned.
* Otherwise the same value provided as @p interrupted is returned back.
* Those handles are the same opaque types used by the @ref arch_switch()
* function.
*
* @warning
* The _current value may have changed after this call and not refer
* to the interrupted thread anymore. It might be necessary to make a local
* copy before calling this function.
*
* @param interrupted Handle for the thread that was interrupted or NULL.
* @return Handle for the next thread to execute, or @p interrupted when
* no new thread is to be scheduled.
*/
void *z_get_next_switch_handle(void *interrupted)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
z_check_stack_sentinel();
#ifdef CONFIG_SMP
void *ret = NULL;
Z_SCHED_SPINLOCK {
struct k_thread *old_thread = _current, *new_thread;
kernel: sched: plug assertion race in z_get_next_switch_handle() Commit d4d51dc062bf ("kernel: Replace redundant switch_handle assignment with assertion") introduced an assertion check that may be triggered as follows by tests/kernel/smp_abort: CPU0 CPU1 CPU2 ---- ---- ____ * [thread A] * [thread B] * [thread C] * irq_offload() * irq_offload() * irq_offload() * k_thread_abort(thread B) * k_thread_abort(thread C) * k_thread_abort(thread A) * thread_halt_spin() * z_is_thread_halting(_current) is false * while (z_is_thread_halting(thread B)); * thread_halt_spin() * z_is_thread_halting(_current) is true * halt_thread(_current...); * z_dummy_thread_init() - dummy_thread->switch_handle = NULL; - _current = dummy_thread; * while (z_is_thread_halting(thread C)); * z_get_next_switch_handle() * z_arm64_context_switch() * [thread A is dead] * thread_halt_spin() * z_is_thread_halting(_current) is true * halt_thread(_current...); * z_dummy_thread_init() - dummy_thread->switch_handle = NULL; - _current = dummy_thread; * while(z_is_thread_halting(thread A)); * z_get_next_switch_handle() - old_thread == dummy_thread - __ASSERT(old_thread->switch_handle == NULL) OK * z_arm64_context_switch() - str x1, [x1, #___thread_t_switch_handle_OFFSET] * [thread B is dead] * %%% dummy_thread->switch_handle no longer NULL %%% * z_get_next_switch_handle() - old_thread == dummy_thread - __ASSERT(old_thread-> switch_handle == NULL) FAIL This needs at least 3 CPUs and the perfect timing for the race to work as sometimes CPUs 1 and 2 may be close enough in their execution paths for the assertion to pass. For example, QEMU is OK while FVP is not. Also adding sufficient debug traces can make the issue go away. This happens because the dummy thread is shared among concurrent CPUs. It could be argued that a per-CPU dummy thread structure would be the proper solution to this problem. However the purpose of a dummy thread structure is to provide a dumping ground for the scheduler code to work while the original thread structure might already be reused and therefore can't be clobbered as demonstrated above. But the dummy structure _can_ be clobbered to some extent and it is not worth the additional memory footprint implied by per-CPU instances. We just have to ignore some validity tests when the dummy thread is concerned. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2025-10-31 11:43:12 -04:00
__ASSERT(old_thread->switch_handle == NULL || is_thread_dummy(old_thread),
"old thread handle should be null.");
new_thread = next_up();
z_sched_usage_switch(new_thread);
if (old_thread != new_thread) {
uint8_t cpu_id;
z_sched_switch_spin(new_thread);
arch_cohere_stacks(old_thread, interrupted, new_thread);
_current_cpu->swap_ok = 0;
cpu_id = arch_curr_cpu()->id;
new_thread->base.cpu = cpu_id;
set_current(new_thread);
#ifdef CONFIG_TIMESLICING
z_time_slice_reset(new_thread);
#endif /* CONFIG_TIMESLICING */
/* Changed _current! Update the scheduler's spinlock
* bookkeeping so the validation doesn't get
* confused when the "wrong" thread tries to
* release the lock.
*/
z_sched_spinlock_transfer_owner();
kernel/sched: Fix rare SMP deadlock 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>
2021-02-08 08:28:54 -08:00
/* A queued (runnable) old/current thread
* needs to be added back to the run queue
* here, and atomically with its switch handle
* being set below. This is safe now, as we
* will not return into it.
*/
if (z_is_thread_queued(old_thread)) {
#ifdef CONFIG_SCHED_IPI_CASCADE
if ((new_thread->base.cpu_mask != -1) &&
(old_thread->base.cpu_mask != BIT(cpu_id))) {
flag_ipi(ipi_mask_create(old_thread));
}
#endif
runq_add(old_thread);
kernel/sched: Fix rare SMP deadlock 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>
2021-02-08 08:28:54 -08:00
}
}
old_thread->switch_handle = interrupted;
ret = new_thread->switch_handle;
/* Active threads MUST have a null here */
new_thread->switch_handle = NULL;
kernel/sched: fix race in consuming self-directed IPIs Move signal_pending_ipi() inside the K_SPINLOCK block in z_get_next_switch_handle(). Calling it after the lock release creates a window where a CPU can consume its own pending IPI bit via atomic_clear in signal_pending_ipi(), then silently drop it in arch_sched_directed_ipi() which skips the calling CPU (i == id). In configurations where secondary CPUs have a single pinned thread and take no timer or external interrupts, this can lead to a permanent hang: the idle CPU can only be woken by IPIs, but no IPIs are pending and no timeslicing IPIs will be generated since the idle thread is not sliceable. This was reproduced when running under QEMU with the following sequence of events observed: CPU 0 CPU 1 ───── ───── Thread calls k_poll(K_MSEC(1)) z_pend_curr(): mark thread PENDING z_add_timeout(1ms) do_swap() to idle thread WFI Timer tick fires sys_clock_announce(): slice_timeout(cpu1): flag_ipi(BIT(1)) signal_pending_ipi(): MSIP[cpu1] = 1 CPU1 wakes from WFI z_get_next_switch_handle(): acquire _sched_spinlock next_up() → idle (thread still PENDING, timeout hasn't fired yet) release _sched_spinlock Timer tick fires sys_clock_announce(): z_thread_timeout(thread): z_unpend_thread(thread) z_ready_thread(thread): flag_ipi(BIT(1)) signal_pending_ipi(): atomic_clear(pending_ipi) returns BIT(1) arch_sched_directed_ipi(BIT(1)) skips self, IPI silently lost return to idle thread WFI thread still on ready queue Such an interleaving of events is, of course, likely only reproducible in practice in virtualized environments where (v)CPUs can be descheduled. With signal_pending_ipi() inside the lock, next_up() and the IPI dispatch are atomic. Either the concurrent flag_ipi lands before the lock is acquired (and next_up sees the thread), or it lands after the lock is released (and the caller dispatches the IPI). There is no window where a CPU can consume its own bit for a thread it hasn't seen. Similar races exist in reschedule() and z_reschedule_irqlock() as well. Although they won't cause the same permanent hang described above, it can result in unnecessary rescheduling latency. Fix reschedule(), and add a TODO to z_reschedule_irqlock(); it doesn't not currently take the sched spinlock. Signed-off-by: Andrew Bresticker <abrestic@meta.com>
2026-03-16 09:10:53 -07:00
/* Check for IPIs under the lock to avoid silently consuming a
* rescheduling IPI flagged by another CPU for ourselves.
*/
signal_pending_ipi();
}
return ret;
#else
z_sched_usage_switch(_kernel.ready_q.cache);
_current->switch_handle = interrupted;
set_current(_kernel.ready_q.cache);
return _current->switch_handle;
#endif /* CONFIG_SMP */
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
}
#endif /* CONFIG_USE_SWITCH */
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
int z_unpend_all(_wait_q_t *wait_q)
{
int need_sched = 0;
struct k_thread *thread;
Z_SCHED_SPINLOCK {
for (thread = z_waitq_head_locked(wait_q);
thread != NULL;
thread = z_waitq_head_locked(wait_q)) {
unpend_thread_no_timeout(thread);
/* Abort the timeout and ready the thread unconditionally. If
* the timeout handler is in flight on another CPU, the abort
* flags it superseded and z_thread_timeout() bails when it
* runs -- so it will NOT ready the thread, we must.
*/
(void)z_try_abort_thread_timeout(thread);
ready_thread(thread);
need_sched = 1;
}
}
return need_sched;
}
static inline void unpend_all(_wait_q_t *wait_q)
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
{
struct k_thread *thread;
for (thread = z_waitq_head_locked(wait_q);
thread != NULL;
thread = z_waitq_head_locked(wait_q)) {
unpend_thread_no_timeout(thread);
arch_thread_return_value_set(thread, 0);
/* See z_unpend_all(): the in-flight handler bails on the
* superseded mark, so we ready the thread unconditionally.
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
*/
(void)z_try_abort_thread_timeout(thread);
ready_thread(thread);
}
unified: initial unified kernel implementation Summary of what this includes: initialization: Copy from nano_init.c, with the following changes: - the main thread is the continuation of the init thread, but an idle thread is created as well - _main() initializes threads in groups and starts the EXE group - the ready queues are initialized - the main thread is marked as non-essential once the system init is done - a weak main() symbol is provided if the application does not provide a main() function scheduler: Not an exhaustive list, but basically provide primitives for: - adding/removing a thread to/from a wait queue - adding/removing a thread to/from the ready queue - marking thread as ready - locking/unlocking the scheduler - instead of locking interrupts - getting/setting thread priority - checking what state (coop/preempt) a thread is currenlty running in - rescheduling threads - finding what thread is the next to run - yielding/sleeping/aborting sleep - finding the current thread threads: - Add operationns on threads, such as creating and starting them. standardized handling of kernel object return codes: - Kernel objects now cause _Swap() to return the following values: 0 => operation successful -EAGAIN => operation timed out -Exxxxx => operation failed for another reason - The thread's swap_data field can be used to return any additional information required to complete the operation, such as the actual result of a successful operation. timeouts: - same as nano timeouts, renamed to simply 'timeouts' - the kernel is still tick-based, but objects take timeout values in ms for forward compatibility with a tickless kernel. semaphores: - Port of the nanokernel semaphores, which have the same basic behaviour as the microkernel ones. Semaphore groups are not yet implemented. - These semaphores are enhanced in that they accept an initial count and a count limit. This allows configuring them as binary semaphores, and also provisioning them without having to "give" the semaphore multiple times before using them. mutexes: - Straight port of the microkernel mutexes. An init function is added to allow defining them at runtime. pipes: - straight port timers: - amalgamation of nano and micro timers, with all functionalities intact. events: - re-implementation, using semaphores and workqueues. mailboxes: - straight port message queues: - straight port of microkernel FIFOs memory maps: - straight port workqueues: - Basically, have all APIs follow the k_ naming rule, and use the _timeout subsystem from the unified kernel directory, and not the _nano_timeout one. stacks: - Port of the nanokernel stacks. They can now have multiple threads pending on them and threads can wait with a timeout. LIFOs: - Straight port of the nanokernel LIFOs. FIFOs: - Straight port of the nanokernel FIFOs. Work by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Peter Mitsis <peter.mitsis@windriver.com> Allan Stephens <allan.stephens@windriver.com> Benjamin Walsh <benjamin.walsh@windriver.com> Change-Id: Id3cadb3694484ab2ca467889cfb029be3cd3a7d6 Signed-off-by: Benjamin Walsh <benjamin.walsh@windriver.com>
2016-09-02 18:55:39 -04:00
}
#ifdef CONFIG_THREAD_ABORT_HOOK
extern void thread_abort_hook(struct k_thread *thread);
#endif /* CONFIG_THREAD_ABORT_HOOK */
/**
* @brief Dequeues the specified thread
*
* Dequeues the specified thread and move it into the specified new state.
*
* @param thread Identify the thread to halt
* @param new_state New thread state (_THREAD_DEAD or _THREAD_SUSPENDED)
* @param key Pointer to the scheduler spinlock key held by the caller
*/
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
static ALWAYS_INLINE void halt_thread(struct k_thread *thread, uint8_t new_state,
k_spinlock_key_t *key)
{
bool dummify = false;
/* We hold the lock, and the thread is known not to be running
* anywhere.
*/
if ((thread->base.thread_state & new_state) == 0U) {
thread->base.thread_state |= new_state;
if (z_is_thread_queued(thread)) {
/* Clear idle CPU coverage before removing the thread from the run queue. */
ipi_idle_thread_unreserve(thread);
dequeue_thread(thread);
}
if (new_state == _THREAD_DEAD) {
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
/* Wait for any in-flight handler to complete before
* we proceed: the caller may free this thread's
* storage (dynamic threads), and an in-flight handler
* would UAF when it eventually runs z_thread_timeout()
* and dereferences thread->base. _THREAD_DEAD is
* already set, so once the handler runs it bails via
* z_sched_wake_thread_locked()'s killed check.
*
* NULL key is the next_up() self-halt path on a
* running _current that has no linked timeout; there
* is nothing to do here. That path is always reached
* via z_thread_halt()'s IF branch, which spins on
* z_try_abort_thread_timeout(thread) outside any lock
* after the halt-queue wait completes, closing any
* remaining in-flight window before the caller of
* z_thread_halt() returns.
*/
if (key != NULL) {
while (z_try_abort_thread_timeout(thread) == -EAGAIN) {
z_sched_spinlock_unlock(*key);
*key = z_sched_spinlock_lock();
kernel: sched: migrate scheduler-internal sites to z_try_abort_timeout() Migrate scheduler-internal callers of z_abort_thread_timeout() to the new z_try_abort_thread_timeout(). This covers the abort sites in sched.c, thread.c, scheduler.c (z_sched_wake), events.c, and pipe.c. The patterns used: z_unpend_thread (sched.c) retries on -EAGAIN: if the timeout handler is in flight on another CPU, drop _sched_spinlock so the handler can run to completion and retry. This preserves 1b8c7a3's unpend+abort atomicity from the caller's perspective. halt_thread (sched.c) takes the caller's sched-lock key as a pointer so its direct abort on the dying thread can retry on -EAGAIN. Waiting for the handler is mandatory: a caller may free the thread's storage as soon as halt_thread() returns, and without waiting, the still-in-flight handler would later dereference freed memory. _THREAD_DEAD is set before the abort, so the handler bails via the killed check in z_sched_wake_thread_locked(). For next_up() (the scheduler-context caller), the key is not cleanly available: K_SPINLOCK in z_get_next_switch_handle and do_swap's (void)k_spin_lock both discard it. halt_thread is invoked on _current with NULL key and no abort is performed -- _current is running, so its base.timeout cannot be linked. The gap is closed at the other end: z_thread_halt() spins on z_try_abort_thread_timeout() outside any lock after the halt-queue wait completes, before returning to the caller of k_thread_abort(). z_unpend_all_locked / unpend_all (sched.c) skip the local ready_thread() on -EAGAIN and let the still-blocked handler ready the thread when _sched_spinlock drops. The threads being woken are not freed, so no UAF risk; end state is identical. z_impl_k_wakeup (thread.c), z_sched_wake (scheduler.c) and event_walk_op (events.c) perform the wake entirely under _sched_spinlock, so a (void) abort is race-free -- a racing in-flight handler is blocked on the same lock during the wake. copy_to_pending_readers (pipe.c) is restructured to also wake the reader under the scheduler lock instead of after it, so the return-value set, unpend, abort, and ready all happen atomically. The dticks-cancel check in z_thread_timeout() is preserved for now because other callers (sem.c, mutex.c, ... via z_unpend_first_thread()) still use z_abort_thread_timeout() and rely on it for race protection. A follow-up commit migrates those, and a final commit drops the cancel check and removes z_abort_thread_timeout() itself. Add z_try_abort_thread_timeout() as an inline wrapper. Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-05-27 18:27:44 -04:00
}
}
unpend_all(&thread->join_queue);
/* Edge case: aborting _current from within an
* ISR that preempted it requires clearing the
* _current pointer so the upcoming context
* switch doesn't clobber the now-freed
* memory
*/
if (thread == _current && arch_is_in_isr()) {
dummify = true;
}
}
#ifdef CONFIG_SMP
unpend_all(&thread->halt_queue);
#endif /* CONFIG_SMP */
update_cache(1);
if (new_state == _THREAD_SUSPENDED) {
clear_halting(thread);
return;
}
arch_coprocessors_disable(thread);
SYS_PORT_TRACING_FUNC(k_thread, sched_abort, thread);
z_thread_monitor_exit(thread);
#ifdef CONFIG_THREAD_ABORT_HOOK
thread_abort_hook(thread);
#endif /* CONFIG_THREAD_ABORT_HOOK */
#ifdef CONFIG_OBJ_CORE_THREAD
#ifdef CONFIG_OBJ_CORE_STATS_THREAD
k_obj_core_stats_deregister(K_OBJ_CORE(thread));
#endif /* CONFIG_OBJ_CORE_STATS_THREAD */
k_obj_core_unlink(K_OBJ_CORE(thread));
#endif /* CONFIG_OBJ_CORE_THREAD */
#ifdef CONFIG_USERSPACE
z_mem_domain_exit_thread(thread);
k_thread_perms_all_clear(thread);
k_object_uninit(thread->stack_obj);
k_object_uninit(thread);
#endif /* CONFIG_USERSPACE */
#ifdef CONFIG_THREAD_ABORT_NEED_CLEANUP
k_thread_abort_cleanup(thread);
#endif /* CONFIG_THREAD_ABORT_NEED_CLEANUP */
/* Do this "set _current to dummy" step last so that
* subsystems above can rely on _current being
* unchanged. Disabled for posix as that arch
* continues to use the _current pointer in its swap
* code. Note that we must leave a non-null switch
* handle for any threads spinning in join() (this can
* never be used, as our thread is flagged dead, but
* it must not be NULL otherwise join can deadlock).
* Use 1 as a clearly invalid but non-NULL value.
*/
if (dummify && !IS_ENABLED(CONFIG_ARCH_POSIX)) {
#ifdef CONFIG_USE_SWITCH
_current->switch_handle = (void *)1;
#endif
#ifdef CONFIG_SPIN_VALIDATE
/* On arches where exceptions run as ISRs (e.g. Xtensa),
* the dying thread's lock tracking is never cleared via the
* normal abort path. Reset it here before _thread_dummy
* takes over. Sentinel-gated so genuine bugs still assert.
*/
if (thread->base.swap_data ==
(void *)&z_spinlock_abort_sentinel) {
z_spin_validate_reset(true);
}
#endif
z_dummy_thread_init(&_thread_dummy);
}
/* Finally update the halting thread state, on which
* other CPUs might be spinning (see
* thread_halt_spin()).
*/
clear_halting(thread);
}
}
void z_thread_suspend_current(struct k_thread *thread)
{
k_spinlock_key_t key = z_sched_spinlock_lock();
z_mark_thread_as_suspended(thread);
z_metairq_preempted_clear(thread);
dequeue_thread(thread);
update_cache(1);
z_swap_locked(key);
}