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
|
|
|
/*
|
2018-05-03 14:51:49 -07: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
|
|
|
*
|
2017-01-18 17:01:01 -08: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
|
|
|
*/
|
2022-05-06 11:04:23 +02:00
|
|
|
#include <zephyr/kernel.h>
|
2026-08-10 10:17:51 -07:00
|
|
|
#include <kspinlock.h>
|
2016-10-13 10:31:48 -04:00
|
|
|
#include <ksched.h>
|
2022-05-06 11:04:23 +02:00
|
|
|
#include <zephyr/spinlock.h>
|
2023-08-29 17:03:12 +00:00
|
|
|
#include <wait_q.h>
|
2024-02-26 17:03:35 -05:00
|
|
|
#include <kthread.h>
|
2024-02-26 11:30:49 -05:00
|
|
|
#include <priority_q.h>
|
2018-01-25 15:24:15 -08:00
|
|
|
#include <kswap.h>
|
2024-03-08 07:51:01 -05:00
|
|
|
#include <ipi.h>
|
2018-05-03 14:51:49 -07:00
|
|
|
#include <kernel_arch_func.h>
|
2023-09-26 22:46:01 +00:00
|
|
|
#include <zephyr/internal/syscall_handler.h>
|
2022-05-06 11:04:23 +02:00
|
|
|
#include <zephyr/drivers/timer/system_timer.h>
|
2018-11-21 16:22:15 -08:00
|
|
|
#include <stdbool.h>
|
2019-09-21 17:54:37 -07:00
|
|
|
#include <kernel_internal.h>
|
2022-05-06 11:04:23 +02:00
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
|
#include <zephyr/sys/atomic.h>
|
|
|
|
|
#include <zephyr/sys/math_extras.h>
|
|
|
|
|
#include <zephyr/timing/timing.h>
|
2023-04-11 15:34:39 +02:00
|
|
|
#include <zephyr/sys/util.h>
|
2026-04-02 15:11:31 -04:00
|
|
|
#include <metairq.h>
|
2026-04-06 11:01:20 -04:00
|
|
|
#include <run_q.h>
|
2026-04-06 17:11:00 -04:00
|
|
|
#include <timeslicing.h>
|
2021-09-28 09:38:43 -07:00
|
|
|
|
2020-11-26 19:32:34 +01:00
|
|
|
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
|
|
|
|
2026-04-02 14:47:56 -04:00
|
|
|
/* pending_current is owned by timeslicing.c; sleep.c also accesses it */
|
2024-03-08 07:51:01 -05:00
|
|
|
#if defined(CONFIG_SWAP_NONATOMIC) && defined(CONFIG_TIMESLICING)
|
|
|
|
|
extern struct k_thread *pending_current;
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-08-10 12:21:47 -07:00
|
|
|
struct k_spinlock _sched_spinlock; /* The scheduler's spinlock */
|
2018-05-03 14:51:49 -07:00
|
|
|
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
/* Storage to "complete" the context switch from an invalid/incomplete thread
|
2025-01-07 12:00:43 -05:00
|
|
|
* context (ex: exiting an ISR that aborted _current)
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
*/
|
2024-04-19 15:03:09 -07:00
|
|
|
__incoherent struct k_thread _thread_dummy;
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
|
2024-10-08 18:13:03 -04:00
|
|
|
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);
|
2023-08-14 14:22:05 -04:00
|
|
|
static void add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q);
|
2020-09-05 11:50:18 -07:00
|
|
|
|
2023-08-14 16:41:05 -04:00
|
|
|
/* Clear the halting bits (_THREAD_ABORTING and _THREAD_SUSPENDING) */
|
|
|
|
|
static inline void clear_halting(struct k_thread *thread)
|
|
|
|
|
{
|
2024-11-09 11:47:54 -08:00
|
|
|
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);
|
|
|
|
|
}
|
2023-08-14 16:41:05 -04:00
|
|
|
}
|
|
|
|
|
|
2019-01-28 09:36:36 -08:00
|
|
|
static ALWAYS_INLINE struct k_thread *next_up(void)
|
2018-04-11 14:52:47 -07:00
|
|
|
{
|
2023-07-24 15:42:52 +03:00
|
|
|
#ifdef CONFIG_SMP
|
2026-07-05 18:26:15 +08:00
|
|
|
bool ipi_idle_target_rebound = false;
|
|
|
|
|
struct k_thread *ipi_idle_target = ipi_idle_reserved_take();
|
|
|
|
|
|
2025-09-19 10:51:55 +08:00
|
|
|
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.
|
|
|
|
|
*/
|
2025-09-19 10:51:55 +08:00
|
|
|
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);
|
2023-07-24 15:42:52 +03:00
|
|
|
}
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SMP */
|
2023-07-24 15:42:52 +03:00
|
|
|
|
2021-09-23 18:44:40 -07:00
|
|
|
struct k_thread *thread = runq_best();
|
2019-11-13 09:41:52 -08:00
|
|
|
|
2026-04-02 15:11:31 -04:00
|
|
|
thread = metairq_preempt_recover(thread);
|
2019-11-13 09:41:52 -08:00
|
|
|
|
2018-05-03 14:51:49 -07:00
|
|
|
#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
|
2019-03-08 14:19:05 -07:00
|
|
|
* responsible for putting it back in z_swap and ISR return!),
|
2018-05-03 14:51:49 -07:00
|
|
|
* which makes this choice simple.
|
|
|
|
|
*/
|
2021-03-29 17:13:47 -04:00
|
|
|
return (thread != NULL) ? thread : _current_cpu->idle_thread;
|
2018-04-11 14:52:47 -07:00
|
|
|
#else
|
2018-05-03 14:51:49 -07:00
|
|
|
/* Under SMP, the "cache" mechanism for selecting the next
|
|
|
|
|
* thread doesn't work, so we have more work to do to test
|
2025-01-07 12:00:43 -05:00
|
|
|
* _current against the best choice from the queue. Here, the
|
2019-11-13 09:41:52 -08:00
|
|
|
* thread selected above represents "the best thread that is
|
|
|
|
|
* not current".
|
2018-05-30 11:23:02 -07:00
|
|
|
*
|
2025-11-10 14:13:16 -08:00
|
|
|
* 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".
|
2018-05-03 14:51:49 -07:00
|
|
|
*/
|
2025-01-07 12:00:43 -05:00
|
|
|
bool queued = z_is_thread_queued(_current);
|
2025-09-20 13:57:56 +08:00
|
|
|
bool active = z_is_thread_ready(_current);
|
2018-04-11 14:52:47 -07:00
|
|
|
|
2019-12-19 08:19:45 -05:00
|
|
|
if (thread == NULL) {
|
|
|
|
|
thread = _current_cpu->idle_thread;
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
2018-04-11 14:52:47 -07:00
|
|
|
|
2018-05-30 11:23:02 -07:00
|
|
|
if (active) {
|
2025-01-07 12:00:43 -05:00
|
|
|
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 */
|
2021-03-01 09:19:57 -08:00
|
|
|
if ((cmp > 0) || ((cmp == 0) && !_current_cpu->swap_ok)) {
|
2025-01-07 12:00:43 -05:00
|
|
|
thread = _current;
|
2018-05-30 11:23:02 -07:00
|
|
|
}
|
|
|
|
|
|
2019-12-19 08:19:45 -05:00
|
|
|
if (!should_preempt(thread, _current_cpu->swap_ok)) {
|
2025-01-07 12:00:43 -05:00
|
|
|
thread = _current;
|
2018-05-30 11:23:02 -07:00
|
|
|
}
|
2018-04-11 14:52:47 -07:00
|
|
|
}
|
|
|
|
|
|
2025-11-10 14:13:16 -08:00
|
|
|
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)
|
2026-04-02 15:11:31 -04:00
|
|
|
&& metairq_current_requeue_allowed()) {
|
2025-11-10 14:13:16 -08:00
|
|
|
queue_thread(_current);
|
|
|
|
|
}
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
2018-04-11 14:52:47 -07:00
|
|
|
|
2025-01-07 12:00:43 -05:00
|
|
|
/* Take the new _current out of the queue */
|
2019-12-19 08:19:45 -05:00
|
|
|
if (z_is_thread_queued(thread)) {
|
2026-07-05 18:26:15 +08:00
|
|
|
/* 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);
|
|
|
|
|
}
|
2021-09-23 16:41:30 -07:00
|
|
|
dequeue_thread(thread);
|
2018-05-30 11:23:02 -07:00
|
|
|
}
|
2018-04-11 14:52:47 -07:00
|
|
|
|
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;
|
2026-07-05 18:26:15 +08:00
|
|
|
|
|
|
|
|
/* 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));
|
|
|
|
|
}
|
2019-12-19 08:19:45 -05:00
|
|
|
return thread;
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SMP */
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
2018-04-11 14:52:47 -07:00
|
|
|
|
2025-11-20 13:35:35 -08:00
|
|
|
void move_current_to_end_of_prio_q(void)
|
2020-09-05 11:50:18 -07:00
|
|
|
{
|
2025-11-20 13:39:36 -08:00
|
|
|
runq_yield();
|
|
|
|
|
|
2025-11-20 13:35:35 -08:00
|
|
|
update_cache(1);
|
2020-09-05 11:50:18 -07:00
|
|
|
}
|
|
|
|
|
|
2024-10-08 18:13:03 -04:00
|
|
|
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
|
|
|
{
|
2018-05-03 14:51:49 -07:00
|
|
|
#ifndef CONFIG_SMP
|
2019-12-19 08:19:45 -05:00
|
|
|
struct k_thread *thread = next_up();
|
2018-05-21 11:48:35 -07:00
|
|
|
|
2019-12-19 08:19:45 -05:00
|
|
|
if (should_preempt(thread, preempt_ok)) {
|
2019-08-16 21:29:26 -07:00
|
|
|
#ifdef CONFIG_TIMESLICING
|
2025-01-07 12:00:43 -05:00
|
|
|
if (thread != _current) {
|
2026-04-06 20:53:51 -04:00
|
|
|
z_time_slice_reset(thread);
|
2018-09-25 10:56:09 -07:00
|
|
|
}
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_TIMESLICING */
|
2019-12-19 08:19:45 -05:00
|
|
|
update_metairq_preempt(thread);
|
|
|
|
|
_kernel.ready_q.cache = thread;
|
2018-05-30 11:23:02 -07:00
|
|
|
} else {
|
2025-01-07 12:00:43 -05:00
|
|
|
_kernel.ready_q.cache = _current;
|
2018-05-21 11:48:35 -07:00
|
|
|
}
|
2018-05-30 11:23:02 -07:00
|
|
|
|
|
|
|
|
#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;
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SMP */
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
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
|
|
|
|
2025-12-19 13:46:17 -08:00
|
|
|
/**
|
|
|
|
|
* Returns pointer to _cpu if the thread is currently running on
|
|
|
|
|
* another CPU.
|
|
|
|
|
*/
|
2024-03-01 14:44:26 -05:00
|
|
|
static struct _cpu *thread_active_elsewhere(struct k_thread *thread)
|
2021-02-19 15:24:24 -08:00
|
|
|
{
|
|
|
|
|
#ifdef CONFIG_SMP
|
2025-12-19 13:46:17 -08:00
|
|
|
int thread_cpu_id = thread->base.cpu;
|
|
|
|
|
struct _cpu *thread_cpu;
|
2021-02-19 15:24:24 -08:00
|
|
|
|
2025-12-19 13:46:17 -08:00
|
|
|
__ASSERT_NO_MSG((thread_cpu_id >= 0) &&
|
|
|
|
|
(thread_cpu_id < arch_num_cpus()));
|
2022-10-18 09:45:13 -05:00
|
|
|
|
2025-12-19 13:46:17 -08:00
|
|
|
thread_cpu = &_kernel.cpus[thread_cpu_id];
|
|
|
|
|
if ((thread_cpu->current == thread) && (thread_cpu != _current_cpu)) {
|
|
|
|
|
return thread_cpu;
|
2021-02-19 15:24:24 -08:00
|
|
|
}
|
2025-12-19 13:46:17 -08:00
|
|
|
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SMP */
|
2023-08-21 15:30:26 +02:00
|
|
|
ARG_UNUSED(thread);
|
2024-03-01 14:44:26 -05:00
|
|
|
return NULL;
|
2021-02-19 15:24:24 -08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 19:54:49 -04:00
|
|
|
static inline void ready_thread(struct k_thread *thread)
|
2018-05-03 14:51:49 -07:00
|
|
|
{
|
2020-12-07 13:15:42 -05:00
|
|
|
#ifdef CONFIG_KERNEL_COHERENCE
|
2025-10-29 11:52:18 -07:00
|
|
|
__ASSERT_NO_MSG(sys_cache_is_mem_coherent(thread));
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_KERNEL_COHERENCE */
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
|
2020-10-16 20:00:17 -04:00
|
|
|
/* 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)) {
|
2021-03-26 10:59:08 +01:00
|
|
|
SYS_PORT_TRACING_OBJ_FUNC(k_thread, sched_ready, thread);
|
|
|
|
|
|
2021-09-23 16:41:30 -07:00
|
|
|
queue_thread(thread);
|
2018-05-21 11:48:35 -07:00
|
|
|
update_cache(0);
|
2024-02-16 13:54:47 -05:00
|
|
|
|
|
|
|
|
flag_ipi(ipi_mask_create(thread));
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
2020-01-23 13:28:30 -08:00
|
|
|
void z_ready_thread(struct k_thread *thread)
|
|
|
|
|
{
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2024-03-01 14:44:26 -05:00
|
|
|
if (thread_active_elsewhere(thread) == NULL) {
|
2021-02-19 15:24:24 -08:00
|
|
|
ready_thread(thread);
|
|
|
|
|
}
|
2020-01-23 13:28:30 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 21:45:51 -07:00
|
|
|
void z_sched_ready_locked(struct k_thread *thread) ALIAS_OF(ready_thread);
|
2026-04-02 14:32:14 -04:00
|
|
|
|
2026-04-06 11:39:59 -04:00
|
|
|
static void unready_thread(struct k_thread *thread)
|
|
|
|
|
{
|
|
|
|
|
if (z_is_thread_queued(thread)) {
|
2026-07-05 18:26:15 +08:00
|
|
|
/* Clear idle CPU coverage before removing the thread from the run queue. */
|
|
|
|
|
ipi_idle_thread_unreserve(thread);
|
2026-04-06 11:39:59 -04:00
|
|
|
dequeue_thread(thread);
|
|
|
|
|
}
|
|
|
|
|
update_cache(thread == _current);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 12:15:57 -04:00
|
|
|
/* This routine exists for benchmarking purposes. It is not used in
|
|
|
|
|
* general production code.
|
|
|
|
|
*/
|
|
|
|
|
void z_unready_thread(struct k_thread *thread)
|
|
|
|
|
{
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2026-04-06 12:15:57 -04:00
|
|
|
unready_thread(thread);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-07-25 21:45:51 -07:00
|
|
|
void z_sched_unready_locked(struct k_thread *thread) ALIAS_OF(unready_thread);
|
2026-04-06 11:39:59 -04:00
|
|
|
|
2025-11-20 13:20:50 -08:00
|
|
|
/* 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
|
|
|
{
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2025-11-20 13:35:35 -08:00
|
|
|
move_current_to_end_of_prio_q();
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
|
2024-04-06 07:44:47 -07: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!).
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
* Acts to release the provided lock before returning.
|
2023-08-14 14:22:05 -04:00
|
|
|
*/
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
static void thread_halt_spin(struct k_thread *thread, k_spinlock_key_t key)
|
2023-08-14 14:22:05 -04:00
|
|
|
{
|
2025-09-19 10:51:55 +08:00
|
|
|
if (z_is_thread_halting(_current)) {
|
2025-01-07 12:00:43 -05:00
|
|
|
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);
|
2024-04-06 07:44:47 -07:00
|
|
|
}
|
2026-08-10 10:17:51 -07:00
|
|
|
z_sched_spinlock_unlock(key);
|
2025-09-19 10:51:55 +08:00
|
|
|
while (z_is_thread_halting(thread)) {
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
unsigned int k = arch_irq_lock();
|
|
|
|
|
|
|
|
|
|
arch_spin_relax(); /* Requires interrupts be masked */
|
|
|
|
|
arch_irq_unlock(k);
|
2023-08-14 14:22:05 -04:00
|
|
|
}
|
2024-04-06 07:44:47 -07:00
|
|
|
}
|
2023-08-14 14:22:05 -04:00
|
|
|
|
2024-04-06 07:44:47 -07:00
|
|
|
/* 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
|
2025-01-07 12:00:43 -05:00
|
|
|
* (aborting _current will not return, obviously), which may be after
|
2024-04-06 07:44:47 -07:00
|
|
|
* a context switch.
|
|
|
|
|
*/
|
2026-04-02 14:32:14 -04:00
|
|
|
void z_thread_halt(struct k_thread *thread, k_spinlock_key_t key,
|
2024-11-09 12:13:43 -08:00
|
|
|
bool terminate)
|
2024-04-06 07:44:47 -07:00
|
|
|
{
|
|
|
|
|
_wait_q_t *wq = &thread->join_queue;
|
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
|
wq = terminate ? wq : &thread->halt_queue;
|
|
|
|
|
#endif
|
2023-08-14 14:22:05 -04:00
|
|
|
|
2025-11-06 14:47:17 -08:00
|
|
|
z_metairq_preempted_clear(thread);
|
|
|
|
|
|
2024-04-06 07:44:47 -07:00
|
|
|
/* 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.
|
|
|
|
|
*/
|
2024-03-01 14:44:26 -05:00
|
|
|
|
|
|
|
|
struct _cpu *cpu = thread_active_elsewhere(thread);
|
|
|
|
|
|
|
|
|
|
if (cpu != NULL) {
|
2023-08-14 16:41:05 -04:00
|
|
|
thread->base.thread_state |= (terminate ? _THREAD_ABORTING
|
2024-04-06 07:44:47 -07:00
|
|
|
: _THREAD_SUSPENDING);
|
|
|
|
|
#if defined(CONFIG_SMP) && defined(CONFIG_SCHED_IPI_SUPPORTED)
|
2024-03-04 10:52:24 -05:00
|
|
|
#ifdef CONFIG_ARCH_HAS_DIRECTED_IPIS
|
|
|
|
|
arch_sched_directed_ipi(IPI_CPU_MASK(cpu->id));
|
|
|
|
|
#else
|
|
|
|
|
arch_sched_broadcast_ipi();
|
2025-09-20 13:57:56 +08:00
|
|
|
#endif /* CONFIG_ARCH_HAS_DIRECTED_IPIS */
|
|
|
|
|
#endif /* CONFIG_SMP && CONFIG_SCHED_IPI_SUPPORTED */
|
2023-08-14 14:22:05 -04:00
|
|
|
if (arch_is_in_isr()) {
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
thread_halt_spin(thread, key);
|
2024-04-06 07:44:47 -07:00
|
|
|
} else {
|
2025-01-07 12:00:43 -05:00
|
|
|
add_to_waitq_locked(_current, wq);
|
2026-08-10 11:17:43 -07:00
|
|
|
z_swap_locked(key);
|
2023-08-14 14:22:05 -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
|
|
|
/* 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
|
2026-08-10 12:21:47 -07:00
|
|
|
* (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) {
|
|
|
|
|
}
|
2023-08-14 16:41:05 -04:00
|
|
|
} 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);
|
2025-01-07 12:00:43 -05:00
|
|
|
if ((thread == _current) && !arch_is_in_isr()) {
|
2025-02-06 10:23:51 -08:00
|
|
|
if (z_is_thread_essential(thread)) {
|
2026-08-10 10:17:51 -07:00
|
|
|
z_sched_spinlock_unlock(key);
|
2025-02-06 10:23:51 -08:00
|
|
|
k_panic();
|
2026-08-10 10:17:51 -07:00
|
|
|
key = z_sched_spinlock_lock();
|
2025-02-06 10:23:51 -08:00
|
|
|
}
|
2026-08-10 11:17:43 -07:00
|
|
|
z_swap_locked(key);
|
2025-01-07 12:00:43 -05:00
|
|
|
__ASSERT(!terminate, "aborted _current back from dead");
|
2024-04-06 07:44:47 -07:00
|
|
|
} else {
|
2026-08-10 10:17:51 -07:00
|
|
|
z_sched_spinlock_unlock(key);
|
2024-04-06 07:44:47 -07:00
|
|
|
}
|
2023-08-14 14:22:05 -04:00
|
|
|
}
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
/* 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!
|
|
|
|
|
*/
|
2023-08-14 14:22:05 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-06 11:59:45 -07:00
|
|
|
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);
|
2025-08-06 11:59:45 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 12:07:55 -07:00
|
|
|
/**
|
2026-08-10 12:21:47 -07:00
|
|
|
* Like reschedule(), but the scheduler's spinlock is known to be the lock.
|
2026-08-10 12:07:55 -07:00
|
|
|
*/
|
|
|
|
|
static void reschedule_locked(k_spinlock_key_t key)
|
|
|
|
|
{
|
|
|
|
|
return reschedule(&_sched_spinlock, key);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 16:51:56 -04:00
|
|
|
void z_sched_lock_reschedule(k_spinlock_key_t key)
|
|
|
|
|
{
|
|
|
|
|
update_cache(0);
|
2026-08-10 12:07:55 -07:00
|
|
|
reschedule_locked(key);
|
2026-04-06 16:51:56 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 14:53:30 -04:00
|
|
|
void z_sched_yield(void)
|
|
|
|
|
{
|
2026-08-10 11:17:43 -07:00
|
|
|
k_spinlock_key_t key = z_sched_spinlock_lock();
|
2026-04-02 14:53:30 -04:00
|
|
|
|
|
|
|
|
runq_yield();
|
|
|
|
|
update_cache(1);
|
2026-08-10 11:17:43 -07:00
|
|
|
z_swap_locked(key);
|
2026-04-02 14:53:30 -04:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 12:21:47 -07:00
|
|
|
/* The scheduler's spinlock must be held */
|
2020-02-20 16:33:06 -08:00
|
|
|
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
|
|
|
{
|
kernel: sched: panic on blocking pend from ISR context
A blocking pend from ISR context is a programming error with no safe
recovery: it attempts to sleep whatever thread was interrupted and, on
CONFIG_SWAP_NONATOMIC, corrupts the scheduler (the qnode_dlist
double-link / NULL-deref traced in #111518).
Catch it at the funnel instead of per-API: every blocking primitive
(sem, mutex, msgq, queue, stack, pipe, poll, events, mailbox, mem_slab,
kheap, futex, ...) routes through z_pend_curr, and reaching it always
means a real block (callers handle K_NO_WAIT beforehand). So one check
there covers them all, with no per-API return-contract churn:
if (arch_is_in_isr()) {
__ASSERT(false, "blocking pend from ISR context");
k_panic();
}
__ASSERT(false, ...) gives the message and backtrace in debug builds;
k_panic() makes it fatal in every build, closing the production gap
(CONFIG_ASSERT defaults to n) that let the misuse ship and run for
hours. Located before _sched_spinlock is acquired, so no lock is held
on the panic path.
Also assert in add_to_waitq_locked() that a thread is not already on a
wait queue when added to a new one (pended_on == NULL) -- belt-and-
suspenders for a double-pend that reaches the scheduler despite the
above, e.g. a non-current thread re-pended without an intervening
unpend.
Suggested-by: Nicolas Pitre <npitre@baylibre.com>
Signed-off-by: Tibor Kiss <kiss.tibor@gmail.com>
2026-06-26 13:01:27 +02: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);
|
|
|
|
|
|
2020-02-20 16:33:06 -08:00
|
|
|
unready_thread(thread);
|
|
|
|
|
z_mark_thread_as_pending(thread);
|
2021-03-26 10:59:08 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-02-20 16:33:06 -08:00
|
|
|
if (wait_q != NULL) {
|
|
|
|
|
thread->base.pended_on = wait_q;
|
2024-04-11 11:59:07 -04:00
|
|
|
_priq_wait_add(&wait_q->waitq, thread);
|
2018-09-26 13:19:31 -07:00
|
|
|
}
|
2020-02-20 16:33:06 -08:00
|
|
|
}
|
2018-09-26 13:19:31 -07:00
|
|
|
|
2026-04-02 14:32:14 -04:00
|
|
|
void z_sched_add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q)
|
2026-07-25 21:45:51 -07:00
|
|
|
ALIAS_OF(add_to_waitq_locked);
|
2026-04-02 14:32:14 -04:00
|
|
|
|
2022-10-08 07:24:28 -07:00
|
|
|
static void pend_locked(struct k_thread *thread, _wait_q_t *wait_q,
|
|
|
|
|
k_timeout_t timeout)
|
2020-02-20 16:33:06 -08:00
|
|
|
{
|
2020-12-07 13:15:42 -05:00
|
|
|
#ifdef CONFIG_KERNEL_COHERENCE
|
2025-10-29 11:52:18 -07:00
|
|
|
__ASSERT_NO_MSG(wait_q == NULL || sys_cache_is_mem_coherent(wait_q));
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_KERNEL_COHERENCE */
|
2022-10-08 07:24:28 -07:00
|
|
|
add_to_waitq_locked(thread, wait_q);
|
2026-07-23 11:30:58 +02:00
|
|
|
z_add_thread_timeout(thread, timeout);
|
2020-02-20 16:33:06 -08:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
2025-01-07 12:00:43 -05:00
|
|
|
__ASSERT_NO_MSG(thread == _current || is_thread_dummy(thread));
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2022-10-08 07:24:28 -07:00
|
|
|
pend_locked(thread, wait_q, timeout);
|
|
|
|
|
}
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
2018-03-09 12:17:45 -08:00
|
|
|
|
2024-07-19 14:15:58 -07:00
|
|
|
void z_unpend_thread_no_timeout(struct k_thread *thread)
|
2018-05-03 14:51:49 -07:00
|
|
|
{
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2023-01-06 13:20:28 -05:00
|
|
|
if (thread->base.pended_on != NULL) {
|
|
|
|
|
unpend_thread_no_timeout(thread);
|
|
|
|
|
}
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
2026-02-16 11:23:18 +01:00
|
|
|
void z_sched_wake_thread_locked(struct k_thread *thread)
|
2018-09-27 16:50:00 -07:00
|
|
|
{
|
2026-08-10 12:21:47 -07:00
|
|
|
/* No K_SPINLOCK: caller must hold the scheduler's spinlock when calling */
|
2026-02-16 10:38:48 +01:00
|
|
|
bool killed = (thread->base.thread_state &
|
|
|
|
|
(_THREAD_DEAD | _THREAD_ABORTING));
|
2018-09-27 16:50:00 -07:00
|
|
|
|
2026-02-16 10:38:48 +01:00
|
|
|
if (!killed) {
|
|
|
|
|
/* The thread is not being killed */
|
|
|
|
|
if (thread->base.pended_on != NULL) {
|
|
|
|
|
unpend_thread_no_timeout(thread);
|
2020-09-05 11:44:01 -07:00
|
|
|
}
|
2026-02-16 10:38:48 +01:00
|
|
|
z_mark_thread_as_not_sleeping(thread);
|
|
|
|
|
ready_thread(thread);
|
2018-09-27 16:50:00 -07:00
|
|
|
}
|
2023-03-08 16:54:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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);
|
|
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
kernel: sched: switch z_thread_timeout() to the superseded check
With every caller of z_abort_thread_timeout() now migrated to
z_try_abort_thread_timeout() (sched.c, thread.c, scheduler.c,
events.c, pipe.c) or to z_unpend_first_thread_locked() (sem.c,
mutex.c, mem_slab.c, stack.c, condvar.c, msg_q.c, queue.c, futex.c),
remove the inline wrapper (and !CONFIG_SYS_CLOCK_EXISTS stub) for
z_abort_thread_timeout().
z_thread_timeout() still needs a cancellation check, but it no longer
relies on the dticks=ANNOUNCING sentinel: switch it to
z_timeout_inflight_superseded(). The check is still required, and for
the same reason 1b8c7a3 added it. A concurrent waker on another CPU
(e.g. a sem give via z_unpend_first_thread_locked()) can unpend and
ready the thread while this timeout's handler is blocked on
_sched_spinlock; the thread may then run and re-pend on a different
object -- possibly with no timeout (K_FOREVER). The waker aborts this
timeout, which flags it superseded, and z_thread_timeout() bails on
that flag so it does not wake the thread from its new wait. The
atomic wake-under-_sched_spinlock closes the swap_retval window; the
superseded check closes this re-pend window.
The remaining TIMEOUT_DTICKS_ANNOUNCING sentinel and
z_is_timeout_handler_canceled() helper still have other users
(kernel/timer.c, kernel/poll.c, kernel/work.c) and are removed in the
later cleanup commit once those subsystems have been migrated as well.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-27 16:50:00 -07:00
|
|
|
}
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SYS_CLOCK_EXISTS */
|
2018-09-27 16:50:00 -07:00
|
|
|
|
2019-03-08 14:19:05 -07:00
|
|
|
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)
|
2018-07-24 13:37:59 -07:00
|
|
|
{
|
kernel: sched: panic on blocking pend from ISR context
A blocking pend from ISR context is a programming error with no safe
recovery: it attempts to sleep whatever thread was interrupted and, on
CONFIG_SWAP_NONATOMIC, corrupts the scheduler (the qnode_dlist
double-link / NULL-deref traced in #111518).
Catch it at the funnel instead of per-API: every blocking primitive
(sem, mutex, msgq, queue, stack, pipe, poll, events, mailbox, mem_slab,
kheap, futex, ...) routes through z_pend_curr, and reaching it always
means a real block (callers handle K_NO_WAIT beforehand). So one check
there covers them all, with no per-API return-contract churn:
if (arch_is_in_isr()) {
__ASSERT(false, "blocking pend from ISR context");
k_panic();
}
__ASSERT(false, ...) gives the message and backtrace in debug builds;
k_panic() makes it fatal in every build, closing the production gap
(CONFIG_ASSERT defaults to n) that let the misuse ship and run for
hours. Located before _sched_spinlock is acquired, so no lock is held
on the panic path.
Also assert in add_to_waitq_locked() that a thread is not already on a
wait queue when added to a new one (pended_on == NULL) -- belt-and-
suspenders for a double-pend that reaches the scheduler despite the
above, e.g. a non-current thread re-pended without an intervening
unpend.
Suggested-by: Nicolas Pitre <npitre@baylibre.com>
Signed-off-by: Tibor Kiss <kiss.tibor@gmail.com>
2026-06-26 13:01:27 +02:00
|
|
|
/* 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();
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-24 13:37:59 -07:00
|
|
|
#if defined(CONFIG_TIMESLICING) && defined(CONFIG_SWAP_NONATOMIC)
|
2025-01-07 12:00:43 -05:00
|
|
|
pending_current = _current;
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_TIMESLICING && CONFIG_SWAP_NONATOMIC */
|
2026-08-10 10:17:51 -07:00
|
|
|
__ASSERT_NO_MSG((sizeof(struct k_spinlock) == 0) || !z_is_sched_spinlock(lock));
|
2022-10-08 07:24:28 -07:00
|
|
|
|
|
|
|
|
/* 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
|
2024-07-06 01:12:07 +07:00
|
|
|
* masked until we reach the context switch. z_swap() itself
|
2022-10-08 07:24:28 -07:00
|
|
|
* has similar code; the duplication is because it's a legacy
|
|
|
|
|
* API that doesn't expect to be called with scheduler lock
|
|
|
|
|
* held.
|
|
|
|
|
*/
|
2026-08-10 10:17:51 -07:00
|
|
|
(void) z_sched_spinlock_lock();
|
2025-01-07 12:00:43 -05:00
|
|
|
pend_locked(_current, wait_q, timeout);
|
2022-10-08 07:24:28 -07:00
|
|
|
k_spin_release(lock);
|
2026-08-10 11:17:43 -07:00
|
|
|
return z_swap_locked(key);
|
2018-07-24 13:37:59 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-09 16:47:47 -08:00
|
|
|
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)
|
|
|
|
|
{
|
|
|
|
|
struct k_thread *thread = NULL;
|
|
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2021-02-09 16:47:47 -08:00
|
|
|
thread = _priq_wait_best(&wait_q->waitq);
|
|
|
|
|
|
|
|
|
|
if (thread != NULL) {
|
|
|
|
|
unpend_thread_no_timeout(thread);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return thread;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-08 14:19:05 -07:00
|
|
|
void z_unpend_thread(struct k_thread *thread)
|
2018-05-03 14:51:49 -07:00
|
|
|
{
|
2026-08-10 10:17:51 -07:00
|
|
|
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) {
|
2026-08-10 10:17:51 -07:00
|
|
|
z_sched_spinlock_unlock(key);
|
|
|
|
|
key = z_sched_spinlock_lock();
|
2026-05-04 12:17:20 -07:00
|
|
|
}
|
2026-08-10 10:17:51 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2019-08-20 11:21:28 -07:00
|
|
|
/* Priority set utility that does no rescheduling, it just changes the
|
|
|
|
|
* run queue state, returning true if a reschedule is needed later.
|
|
|
|
|
*/
|
2024-02-24 11:37:56 -05:00
|
|
|
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
|
|
|
{
|
2026-01-30 16:55:22 +01:00
|
|
|
bool need_sched = false;
|
2024-03-01 14:44:26 -05:00
|
|
|
int old_prio = thread->base.prio;
|
2018-05-03 14:51:49 -07:00
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2019-03-08 14:19:05 -07:00
|
|
|
need_sched = z_is_thread_ready(thread);
|
2018-05-03 14:51:49 -07:00
|
|
|
|
|
|
|
|
if (need_sched) {
|
2019-07-01 10:25:55 -07:00
|
|
|
if (!IS_ENABLED(CONFIG_SMP) || z_is_thread_queued(thread)) {
|
2021-09-23 16:41:30 -07:00
|
|
|
dequeue_thread(thread);
|
2019-07-01 10:25:55 -07:00
|
|
|
thread->base.prio = prio;
|
2021-09-23 16:41:30 -07:00
|
|
|
queue_thread(thread);
|
2024-03-01 14:44:26 -05:00
|
|
|
|
|
|
|
|
if (old_prio > prio) {
|
2024-02-16 13:54:47 -05:00
|
|
|
flag_ipi(ipi_mask_create(thread));
|
2024-03-01 14:44:26 -05:00
|
|
|
}
|
2019-07-01 10:25:55 -07:00
|
|
|
} else {
|
2024-03-01 14:44:26 -05:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-07-01 10:25:55 -07:00
|
|
|
thread->base.prio = prio;
|
2024-03-01 14:44:26 -05:00
|
|
|
|
|
|
|
|
struct _cpu *cpu;
|
|
|
|
|
|
|
|
|
|
cpu = thread_active_elsewhere(thread);
|
|
|
|
|
if ((cpu != NULL) && (old_prio < prio)) {
|
2024-02-16 13:54:47 -05:00
|
|
|
flag_ipi(IPI_CPU_MASK(cpu->id));
|
2024-03-01 14:44:26 -05:00
|
|
|
}
|
2019-07-01 10:25:55 -07:00
|
|
|
}
|
2024-03-01 14:44:26 -05:00
|
|
|
|
2018-05-21 11:48:35 -07:00
|
|
|
update_cache(1);
|
kernel/sched: Re-sort waitq on priority change
k_thread_priority_set() on a pended thread wasn't re-inserting into the
waitq, causing the incorrect thread to run based on priority. When using
the scalable waitq config, this can also break assumptions of the tree
and leave the owner of a waitq still being in the waitq tree, cycles in
the tree, or a crash.
Remove and re-add a thread to a waitq to ensure the waitq remains in
order and the tree's assumptions are not violated.
To illustrate the issue, consider 4 threads in decreasing priority
order: A, B, C, and D along with two mutexes, m0 and m1. This is
implemented in the new complex_inversion mutex_api test.
1. D locks m1
2. C locks m0
3. C pends on m1
4. B pends on m1
5. A pends on m0, boosts C's priority, now tree on m1 is not sorted
6. D unlocks m1, left-most thread on tree is B. When removing B from
tree it cannot be found because it searches to the right of C due to
C's boosted priority when the node is actually on the left. rb_remove
silently fails.
7. B unlocks m1, left-most thread on tree is still B and it tries to
unpend itself, resulting in a NULL pointer dereference on
B->base.pended_on.
Signed-off-by: Josh DeWitt <josh.dewitt@garmin.com>
2024-02-12 16:41:57 -06:00
|
|
|
} 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);
|
2018-05-03 14:51:49 -07:00
|
|
|
} else {
|
|
|
|
|
thread->base.prio = prio;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-26 10:59:08 +01:00
|
|
|
|
|
|
|
|
SYS_PORT_TRACING_OBJ_FUNC(k_thread, sched_priority_set, thread, prio);
|
2018-05-03 14:51:49 -07:00
|
|
|
|
2019-08-20 11:21:28 -07:00
|
|
|
return need_sched;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 21:45:51 -07:00
|
|
|
void z_reschedule(struct k_spinlock *lock, k_spinlock_key_t key) ALIAS_OF(reschedule);
|
2018-05-03 14:51:49 -07:00
|
|
|
|
2026-08-10 12:07:55 -07:00
|
|
|
void z_reschedule_locked(k_spinlock_key_t key) ALIAS_OF(reschedule_locked);
|
|
|
|
|
|
2020-05-27 11:26:57 -05:00
|
|
|
void z_reschedule_irqlock(uint32_t key)
|
2018-07-24 13:37:59 -07:00
|
|
|
{
|
2023-12-21 11:01:54 +09:00
|
|
|
if (resched(key) && need_swap()) {
|
2019-03-08 14:19:05 -07:00
|
|
|
z_swap_irqlock(key);
|
2018-07-24 13:37:59 -07:00
|
|
|
} else {
|
2026-08-10 12:21:47 -07:00
|
|
|
/* 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.
|
|
|
|
|
*/
|
2022-04-06 10:10:17 -07:00
|
|
|
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);
|
2018-07-24 13:37:59 -07:00
|
|
|
}
|
kernel: Scheduler refactoring: use _reschedule_*() always
There was a somewhat promiscuous pattern in the kernel where IPC
mechanisms would do something that might effect the current thread
choice, then check _must_switch_threads() (or occasionally
__must_switch_threads -- don't ask, the distinction is being replaced
by real English words), sometimes _is_in_isr() (but not always, even
in contexts where that looks like it would be a mistake), and then
call _Swap() if everything is OK, otherwise releasing the irq_lock().
Sometimes this was done directly, sometimes via the inverted test,
sometimes (poll, heh) by doing the test when the thread state was
modified and then needlessly passing the result up the call stack to
the point of the _Swap().
And some places were just calling _reschedule_threads(), which did all
this already.
Unify all this madness. The old _reschedule_threads() function has
split into two variants: _reschedule_yield() and
_reschedule_noyield(). The latter is the "normal" one that respects
the cooperative priority of the current thread (i.e. it won't switch
out even if there is a higher priority thread ready -- the current
thread has to pend itself first), the former is used in the handful of
places where code was doing a swap unconditionally, just to preserve
precise behavior across the refactor. I'm not at all convinced it
should exist...
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2018-03-26 10:54:40 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-18 10:15:23 -08:00
|
|
|
struct k_thread *z_swap_next_thread(void)
|
2018-05-03 14:51:49 -07:00
|
|
|
{
|
2022-04-06 10:10:17 -07:00
|
|
|
struct k_thread *ret = next_up();
|
|
|
|
|
|
2025-01-07 12:00:43 -05:00
|
|
|
if (ret == _current) {
|
2022-04-06 10:10:17 -07:00
|
|
|
/* When not swapping, have to signal IPIs here. In
|
|
|
|
|
* the context switch case it must happen later, after
|
2025-01-07 12:00:43 -05:00
|
|
|
* _current gets requeued.
|
2022-04-06 10:10:17 -07:00
|
|
|
*/
|
|
|
|
|
signal_pending_ipi();
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
2021-02-18 10:15:23 -08:00
|
|
|
}
|
2018-05-03 14:51:49 -07:00
|
|
|
|
2021-12-06 10:56:33 -07:00
|
|
|
#ifdef CONFIG_USE_SWITCH
|
2025-01-07 15:42:07 -05:00
|
|
|
/* Just a wrapper around z_current_thread_set(xxx) with tracing */
|
2019-02-19 17:24:30 -08:00
|
|
|
static inline void set_current(struct k_thread *new_thread)
|
|
|
|
|
{
|
2025-07-07 08:28:24 -04:00
|
|
|
/* 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();
|
|
|
|
|
}
|
2025-01-07 15:42:07 -05:00
|
|
|
z_current_thread_set(new_thread);
|
2019-02-19 17:24:30 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-15 22:36:20 -04:00
|
|
|
/**
|
|
|
|
|
* @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
|
2025-01-07 12:00:43 -05:00
|
|
|
* The _current value may have changed after this call and not refer
|
2022-03-15 22:36:20 -04:00
|
|
|
* 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.
|
2026-02-28 04:03:08 +05:30
|
|
|
* @return Handle for the next thread to execute, or @p interrupted when
|
2022-03-15 22:36:20 -04:00
|
|
|
* no new thread is to be scheduled.
|
|
|
|
|
*/
|
2019-03-08 14:19:05 -07:00
|
|
|
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
|
|
|
{
|
2019-03-29 16:25:27 -07:00
|
|
|
z_check_stack_sentinel();
|
|
|
|
|
|
2018-05-30 11:23:02 -07:00
|
|
|
#ifdef CONFIG_SMP
|
2021-02-05 08:15:02 -08:00
|
|
|
void *ret = NULL;
|
|
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2025-01-07 12:00:43 -05:00
|
|
|
struct k_thread *old_thread = _current, *new_thread;
|
2025-09-27 09:38:38 +08:00
|
|
|
|
2025-10-31 11:43:12 -04:00
|
|
|
__ASSERT(old_thread->switch_handle == NULL || is_thread_dummy(old_thread),
|
2025-09-27 09:38:38 +08:00
|
|
|
"old thread handle should be null.");
|
|
|
|
|
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
new_thread = next_up();
|
|
|
|
|
|
kernel/sched: Add "thread_usage" API for thread runtime cycle monitoring
This is an alternate backend that does what THREAD_RUNTIME_STATS is
doing currently, but with a few advantages:
* Correctly synchronized: you can't race against a running thread
(potentially on another CPU!) while querying its usage.
* Realtime results: you get the right answer always, up to timer
precision, even if a thread has been running for a while
uninterrupted and hasn't updated its total.
* Portable, no need for per-architecture code at all for the simple
case. (It leverages the USE_SWITCH layer to do this, so won't work
on older architectures)
* Faster/smaller: minimizes use of 64 bit math; lower overhead in
thread struct (keeps the scratch "started" time in the CPU struct
instead). One 64 bit counter per thread and a 32 bit scratch
register in the CPU struct.
* Standalone. It's a core (but optional) scheduler feature, no
dependence on para-kernel configuration like the tracing
infrastructure.
* More precise: allows architectures to optionally call a trivial
zero-argument/no-result cdecl function out of interrupt entry to
avoid accounting for ISR runtime in thread totals. No configuration
needed here, if it's called then you get proper ISR accounting, and
if not you don't.
For right now, pending unification, it's added side-by-side with the
older API and left as a z_*() internal symbol.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2021-09-27 08:22:43 -07:00
|
|
|
z_sched_usage_switch(new_thread);
|
|
|
|
|
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
if (old_thread != new_thread) {
|
2024-04-23 13:53:40 -04:00
|
|
|
uint8_t cpu_id;
|
|
|
|
|
|
2023-05-26 09:12:51 -07:00
|
|
|
z_sched_switch_spin(new_thread);
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
arch_cohere_stacks(old_thread, interrupted, new_thread);
|
2019-11-13 09:41:52 -08:00
|
|
|
|
2018-05-30 11:23:02 -07:00
|
|
|
_current_cpu->swap_ok = 0;
|
2024-04-23 13:53:40 -04:00
|
|
|
cpu_id = arch_curr_cpu()->id;
|
|
|
|
|
new_thread->base.cpu = cpu_id;
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
set_current(new_thread);
|
|
|
|
|
|
2021-11-30 18:26:26 -08:00
|
|
|
#ifdef CONFIG_TIMESLICING
|
2026-04-06 20:53:51 -04:00
|
|
|
z_time_slice_reset(new_thread);
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_TIMESLICING */
|
2021-11-30 18:26:26 -08:00
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
/* Changed _current! Update the scheduler's spinlock
|
2021-04-30 09:58:20 -04:00
|
|
|
* bookkeeping so the validation doesn't get
|
2019-02-20 10:07:31 -08:00
|
|
|
* confused when the "wrong" thread tries to
|
|
|
|
|
* release the lock.
|
|
|
|
|
*/
|
2026-08-10 10:17:51 -07:00
|
|
|
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)) {
|
2024-04-23 13:53:40 -04:00
|
|
|
#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
|
2021-09-23 18:44:40 -07:00
|
|
|
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
|
|
|
}
|
2018-05-30 11:23:02 -07:00
|
|
|
}
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 15:34:04 +00:00
|
|
|
old_thread->switch_handle = interrupted;
|
2021-02-05 08:15:02 -08:00
|
|
|
ret = new_thread->switch_handle;
|
2025-09-20 13:57:56 +08:00
|
|
|
/* 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();
|
2018-05-03 14:51:49 -07:00
|
|
|
}
|
2021-02-05 08:15:02 -08:00
|
|
|
return ret;
|
2018-05-30 11:23:02 -07:00
|
|
|
#else
|
kernel/sched: Add "thread_usage" API for thread runtime cycle monitoring
This is an alternate backend that does what THREAD_RUNTIME_STATS is
doing currently, but with a few advantages:
* Correctly synchronized: you can't race against a running thread
(potentially on another CPU!) while querying its usage.
* Realtime results: you get the right answer always, up to timer
precision, even if a thread has been running for a while
uninterrupted and hasn't updated its total.
* Portable, no need for per-architecture code at all for the simple
case. (It leverages the USE_SWITCH layer to do this, so won't work
on older architectures)
* Faster/smaller: minimizes use of 64 bit math; lower overhead in
thread struct (keeps the scratch "started" time in the CPU struct
instead). One 64 bit counter per thread and a 32 bit scratch
register in the CPU struct.
* Standalone. It's a core (but optional) scheduler feature, no
dependence on para-kernel configuration like the tracing
infrastructure.
* More precise: allows architectures to optionally call a trivial
zero-argument/no-result cdecl function out of interrupt entry to
avoid accounting for ISR runtime in thread totals. No configuration
needed here, if it's called then you get proper ISR accounting, and
if not you don't.
For right now, pending unification, it's added side-by-side with the
older API and left as a z_*() internal symbol.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2021-09-27 08:22:43 -07:00
|
|
|
z_sched_usage_switch(_kernel.ready_q.cache);
|
2025-01-07 12:00:43 -05:00
|
|
|
_current->switch_handle = interrupted;
|
2021-02-18 10:15:23 -08:00
|
|
|
set_current(_kernel.ready_q.cache);
|
2025-01-07 12:00:43 -05:00
|
|
|
return _current->switch_handle;
|
2024-03-08 12:00:10 +01:00
|
|
|
#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
|
|
|
}
|
2024-03-08 12:00:10 +01: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)
|
2018-05-10 09:45:42 -07:00
|
|
|
{
|
2018-05-10 11:10:34 -07:00
|
|
|
int need_sched = 0;
|
2019-12-19 08:19:45 -05:00
|
|
|
struct k_thread *thread;
|
2026-04-04 12:00:08 -04:00
|
|
|
|
2026-08-10 10:17:51 -07:00
|
|
|
Z_SCHED_SPINLOCK {
|
2026-08-10 15:51:18 -07:00
|
|
|
for (thread = z_waitq_head_locked(wait_q);
|
2026-08-10 10:17:51 -07:00
|
|
|
thread != NULL;
|
2026-08-10 15:51:18 -07:00
|
|
|
thread = z_waitq_head_locked(wait_q)) {
|
2026-08-10 10:17:51 -07:00
|
|
|
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;
|
|
|
|
|
}
|
2018-05-10 09:45:42 -07:00
|
|
|
}
|
2018-05-10 11:10:34 -07:00
|
|
|
|
2026-04-02 17:28:39 -04:00
|
|
|
return need_sched;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 12:15:22 -04:00
|
|
|
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
|
|
|
{
|
2026-04-06 12:15:22 -04:00
|
|
|
struct k_thread *thread;
|
2021-09-24 13:49:14 -07:00
|
|
|
|
2026-08-10 15:51:18 -07:00
|
|
|
for (thread = z_waitq_head_locked(wait_q);
|
|
|
|
|
thread != NULL;
|
|
|
|
|
thread = z_waitq_head_locked(wait_q)) {
|
2026-04-06 12:15:22 -04:00
|
|
|
unpend_thread_no_timeout(thread);
|
|
|
|
|
arch_thread_return_value_set(thread, 0);
|
2026-06-19 02:25:58 -04:00
|
|
|
/* 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
|
|
|
*/
|
2026-06-19 02:25:58 -04:00
|
|
|
(void)z_try_abort_thread_timeout(thread);
|
|
|
|
|
ready_thread(thread);
|
2021-09-24 10:57:39 -07:00
|
|
|
}
|
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
|
|
|
}
|
2021-02-19 15:32:19 -08:00
|
|
|
|
2024-02-22 14:10:17 -05:00
|
|
|
#ifdef CONFIG_THREAD_ABORT_HOOK
|
|
|
|
|
extern void thread_abort_hook(struct k_thread *thread);
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_THREAD_ABORT_HOOK */
|
2021-09-06 13:59:40 +08:00
|
|
|
|
2023-08-14 14:06:52 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Dequeues the specified thread
|
|
|
|
|
*
|
|
|
|
|
* Dequeues the specified thread and move it into the specified new state.
|
|
|
|
|
*
|
|
|
|
|
* @param thread Identify the thread to halt
|
2023-08-14 16:41:05 -04:00
|
|
|
* @param new_state New thread state (_THREAD_DEAD or _THREAD_SUSPENDED)
|
2026-06-30 16:00:48 -04:00
|
|
|
* @param key Pointer to the scheduler spinlock key held by the caller
|
2023-08-14 14:06:52 -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
|
|
|
static ALWAYS_INLINE void halt_thread(struct k_thread *thread, uint8_t new_state,
|
|
|
|
|
k_spinlock_key_t *key)
|
2021-02-19 15:32:19 -08:00
|
|
|
{
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
bool dummify = false;
|
|
|
|
|
|
2021-02-19 15:32:19 -08:00
|
|
|
/* We hold the lock, and the thread is known not to be running
|
|
|
|
|
* anywhere.
|
|
|
|
|
*/
|
2023-08-14 14:06:52 -04:00
|
|
|
if ((thread->base.thread_state & new_state) == 0U) {
|
|
|
|
|
thread->base.thread_state |= new_state;
|
2021-02-19 15:32:19 -08:00
|
|
|
if (z_is_thread_queued(thread)) {
|
2026-07-05 18:26:15 +08:00
|
|
|
/* Clear idle CPU coverage before removing the thread from the run queue. */
|
|
|
|
|
ipi_idle_thread_unreserve(thread);
|
2021-09-23 16:41:30 -07:00
|
|
|
dequeue_thread(thread);
|
2021-02-19 15:32:19 -08:00
|
|
|
}
|
2023-08-14 16:41:05 -04:00
|
|
|
|
|
|
|
|
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) {
|
2026-08-10 10:17:51 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
2023-08-14 16:41:05 -04:00
|
|
|
unpend_all(&thread->join_queue);
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
|
2025-01-07 12:00:43 -05:00
|
|
|
/* Edge case: aborting _current from within an
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
* ISR that preempted it requires clearing the
|
2025-01-07 12:00:43 -05:00
|
|
|
* _current pointer so the upcoming context
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
* switch doesn't clobber the now-freed
|
|
|
|
|
* memory
|
|
|
|
|
*/
|
2025-01-07 12:00:43 -05:00
|
|
|
if (thread == _current && arch_is_in_isr()) {
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
dummify = true;
|
|
|
|
|
}
|
2021-02-19 15:32:19 -08:00
|
|
|
}
|
2023-08-14 16:41:05 -04:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
|
unpend_all(&thread->halt_queue);
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_SMP */
|
2021-02-19 15:32:19 -08:00
|
|
|
update_cache(1);
|
|
|
|
|
|
2023-08-14 16:41:05 -04:00
|
|
|
if (new_state == _THREAD_SUSPENDED) {
|
2024-04-19 15:08:55 -07:00
|
|
|
clear_halting(thread);
|
2023-08-14 16:41:05 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-11 11:54:40 -07:00
|
|
|
arch_coprocessors_disable(thread);
|
2023-08-14 09:41:52 +12:00
|
|
|
|
2021-03-26 10:59:08 +01:00
|
|
|
SYS_PORT_TRACING_FUNC(k_thread, sched_abort, thread);
|
|
|
|
|
|
2021-02-19 15:32:19 -08:00
|
|
|
z_thread_monitor_exit(thread);
|
2024-02-22 14:10:17 -05:00
|
|
|
#ifdef CONFIG_THREAD_ABORT_HOOK
|
|
|
|
|
thread_abort_hook(thread);
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_THREAD_ABORT_HOOK */
|
2021-09-06 13:59:40 +08:00
|
|
|
|
kernel: Integrate object cores into kernel
Integrates object cores into the following kernel structures
sys_mem_blocks, k_mem_slab
_cpu, z_kernel
k_thread, k_timer
k_condvar, k_event, k_mutex, k_sem
k_mbox, k_msgq, k_pipe, k_fifo, k_lifo, k_stack
Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2023-05-11 14:06:46 -04:00
|
|
|
#ifdef CONFIG_OBJ_CORE_THREAD
|
2023-06-01 12:16:40 -04:00
|
|
|
#ifdef CONFIG_OBJ_CORE_STATS_THREAD
|
|
|
|
|
k_obj_core_stats_deregister(K_OBJ_CORE(thread));
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_OBJ_CORE_STATS_THREAD */
|
kernel: Integrate object cores into kernel
Integrates object cores into the following kernel structures
sys_mem_blocks, k_mem_slab
_cpu, z_kernel
k_thread, k_timer
k_condvar, k_event, k_mutex, k_sem
k_mbox, k_msgq, k_pipe, k_fifo, k_lifo, k_stack
Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2023-05-11 14:06:46 -04:00
|
|
|
k_obj_core_unlink(K_OBJ_CORE(thread));
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_OBJ_CORE_THREAD */
|
kernel: Integrate object cores into kernel
Integrates object cores into the following kernel structures
sys_mem_blocks, k_mem_slab
_cpu, z_kernel
k_thread, k_timer
k_condvar, k_event, k_mutex, k_sem
k_mbox, k_msgq, k_pipe, k_fifo, k_lifo, k_stack
Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
2023-05-11 14:06:46 -04:00
|
|
|
|
2021-02-19 15:32:19 -08:00
|
|
|
#ifdef CONFIG_USERSPACE
|
|
|
|
|
z_mem_domain_exit_thread(thread);
|
2023-09-27 10:45:48 +00:00
|
|
|
k_thread_perms_all_clear(thread);
|
2023-09-27 10:45:18 +00:00
|
|
|
k_object_uninit(thread->stack_obj);
|
|
|
|
|
k_object_uninit(thread);
|
2024-03-08 12:00:10 +01:00
|
|
|
#endif /* CONFIG_USERSPACE */
|
2024-03-26 11:54:31 -07:00
|
|
|
|
|
|
|
|
#ifdef CONFIG_THREAD_ABORT_NEED_CLEANUP
|
|
|
|
|
k_thread_abort_cleanup(thread);
|
|
|
|
|
#endif /* CONFIG_THREAD_ABORT_NEED_CLEANUP */
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
|
2025-01-07 12:00:43 -05:00
|
|
|
/* Do this "set _current to dummy" step last so that
|
|
|
|
|
* subsystems above can rely on _current being
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
* unchanged. Disabled for posix as that arch
|
2025-01-07 12:00:43 -05:00
|
|
|
* continues to use the _current pointer in its swap
|
2024-04-29 12:50:41 -07:00
|
|
|
* 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).
|
2025-10-31 10:46:41 -04:00
|
|
|
* Use 1 as a clearly invalid but non-NULL value.
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
*/
|
|
|
|
|
if (dummify && !IS_ENABLED(CONFIG_ARCH_POSIX)) {
|
2024-04-29 12:50:41 -07:00
|
|
|
#ifdef CONFIG_USE_SWITCH
|
2025-10-31 10:46:41 -04:00
|
|
|
_current->switch_handle = (void *)1;
|
2026-06-01 13:32:54 +05:30
|
|
|
#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);
|
|
|
|
|
}
|
2024-04-29 12:50:41 -07:00
|
|
|
#endif
|
2024-04-19 15:03:09 -07:00
|
|
|
z_dummy_thread_init(&_thread_dummy);
|
2024-04-29 12:50:41 -07:00
|
|
|
|
kernel/sched: Fix free-memory write when ISRs abort _current
After a k_thread_abort(), the resulting thread struct is documented as
unused/free memory that may be re-used (for example, to respawn a new
thread).
But in the special case of aborting the current thread from within an
ISR, that wasn't quite happening. The scheduler cleanup would
complete, but the architecture layer would still try to context switch
away from the aborted thread on exit, and that can include writes to
the now-reused thread struct! The specifics will depend on
architecture (some do a full context save on entry, most don't), but
in the case of USE_SWITCH=y it will at the very least write the
switch_handle field.
Fix this simply, with a per-cpu "switch dummy" thread struct for use
as a target for context switches like this. There is some non-trivial
memory cost to that; thread structs on many architectures are large.
Pleasingly, this also addresses a known deadlock on SMP: because the
"spin in ISR" step now happens as the very last stage of
k_thread_abort() handling, the existing scheduler lock works to
serialize calls such that it's impossible for a cycle of threads to
independently decide to spin on each other: at least one will see
itself as "already aborting" and break the cycle.
Fixes #64646
Signed-off-by: Andy Ross <andyross@google.com>
2024-03-26 08:38:01 -04:00
|
|
|
}
|
2024-04-19 15:08:55 -07:00
|
|
|
|
|
|
|
|
/* Finally update the halting thread state, on which
|
|
|
|
|
* other CPUs might be spinning (see
|
|
|
|
|
* thread_halt_spin()).
|
|
|
|
|
*/
|
|
|
|
|
clear_halting(thread);
|
2021-02-19 15:32:19 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 14:32:14 -04:00
|
|
|
void z_thread_suspend_current(struct k_thread *thread)
|
2021-02-19 15:32:19 -08:00
|
|
|
{
|
2026-08-10 11:17:43 -07:00
|
|
|
k_spinlock_key_t key = z_sched_spinlock_lock();
|
2021-02-19 15:32:19 -08:00
|
|
|
|
2026-04-02 14:32:14 -04:00
|
|
|
z_mark_thread_as_suspended(thread);
|
|
|
|
|
z_metairq_preempted_clear(thread);
|
|
|
|
|
dequeue_thread(thread);
|
|
|
|
|
update_cache(1);
|
2026-08-10 11:17:43 -07:00
|
|
|
z_swap_locked(key);
|
2020-03-24 16:09:24 -07:00
|
|
|
}
|