From 9416ef2684ba403a4adcde3a95af63187615e3e7 Mon Sep 17 00:00:00 2001 From: Peter Mitsis Date: Tue, 8 Apr 2025 14:50:27 -0700 Subject: [PATCH] doc: Update context switching for arch porting Updates the context switching documentation for arch porting to reflect the current state. Signed-off-by: Peter Mitsis --- doc/hardware/porting/arch.rst | 68 ++++++++++++++--------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/doc/hardware/porting/arch.rst b/doc/hardware/porting/arch.rst index 7e79d1d2b97..7c3e4ff9975 100644 --- a/doc/hardware/porting/arch.rst +++ b/doc/hardware/porting/arch.rst @@ -176,15 +176,16 @@ Thread Context Switching ************************ Multi-threading is the basic purpose to have a kernel at all. Zephyr supports -two types of threads: preemptible and cooperative. +two types of threads: preemptible and cooperative. The rules for determining +the next thread to schedule are handled by the kernel. However, it is up to +the architecture port to implement the method of the context switch itself. -Two crucial concepts when writing an architecture port are the following: - -* Cooperative threads run at a higher priority than preemptible ones, and - always preempt them. - -* After handling an interrupt, if a cooperative thread was interrupted, the - kernel always goes back to running that thread, since it is not preemptible. +Zephyr provides two mutually exclusive interfaces for context switching. The +preferred interface to use is :code:`arch_switch` which is selected when +:kconfig:option:`CONFIG_USE_SWITCH` is enabled. The alternative interface is +:code:`arch_swap`--selected when :kconfig:option:`CONFIG_USE_SWITCH` +is disabled. When porting to a new architecture, only one of these needs to +implemented; however, for SMP platforms it must be :code:`arch_switch`. A context switch can happen in several circumstances: @@ -203,12 +204,6 @@ A context switch can happen in several circumstances: threads. For example, referencing invalid memory, Therefore, the context switching must thus be able to handle all these cases. -Zephyr provides two mutually exclusive methods for context switching. The first -is the traditional method that uses :code:`arch_swap()`. The second (and -recommended) approach uses :code:`arch_switch()`. - -The kernel keeps the next thread to run in a "cache", and thus the context -switching code only has to fetch from that cache to select which thread to run. There are two types of context switches: :dfn:`cooperative` and :dfn:`preemptive`. @@ -231,19 +226,17 @@ There are two types of context switches: :dfn:`cooperative` and :dfn:`preemptive running thread. A cooperative context switch is always done by having a thread call the -:code:`z_swap()` kernel internal symbol (or one of its variants). When -:code:`z_swap` is called, the kernel logic knows that a context switch has to -happen: :code:`z_swap` does not check to see if a context switch must happen. -Rather, :code:`z_swap` decides what thread to context switch in. -:code:`z_swap` is called by the kernel logic when an object being operated on -is unavailable, and some thread yielding/sleeping primitives. +internal kernel routine :code:`z_swap` (or one of its variants). This in turn +will call either :code:`arch_switch` or :code:`arch_swap` as appropriate. +When these are called, no checks are done to determine if the context switch is +to happen--the context switch must happen. .. note:: - On x86 and Nios2, :code:`z_swap` is generic enough and the architecture - flexible enough that :code:`z_swap` can be called when exiting an interrupt - to provoke the context switch. This should not be taken as a rule, since - neither the ARM Cortex-M or ARCv2 port do this. + On x86 and Nios2, :code:`arch_swap` is generic enough and the architecture + flexible enough that it can be called when exiting an interrupt to provoke + the context switch. This should not be taken as a rule, since + neither the ARM Cortex-M nor ARCv2 port do this. Since :code:`z_swap` is cooperative, the caller-saved registers from the ABI are already on the stack. There is no need to save them in the k_thread structure. @@ -255,34 +248,25 @@ an ISR, in the kernel interrupt exit stub: * :code:`z_arm_exc_exit` and :code:`z_arm_int_exit` on ARM. * :code:`_firq_exit` and :code:`_rirq_exit` on ARCv2. -In this case, the context switch must only be invoked when the interrupted -thread was preemptible, not when it was a cooperative one, and only when the -current interrupt is not nested. +The decision logic to invoke the context switch is simple and is only performed +when exiting a non-nested interrupt: -The kernel also has the concept of "locking the scheduler". This is a concept -similar to locking the interrupts, but lighter-weight since interrupts can -still occur. If a thread has locked the scheduler, is it temporarily -non-preemptible. +When :kconfig:option:`CONFIG_USE_SWITCH` is enabled ... -So, the decision logic to invoke the context switch when exiting an interrupt -is simple: +* The interrupt exit code shall call :c:func:`z_get_next_switch_handle`, and + return to the thread context identified by the returned switch handle -* If the interrupted thread is not preemptible, do not invoke it. -* Else, fetch the cached thread from the ready queue, and: +When :kconfig:option:`CONFIG_USE_SWITCH` is not enabled ... + +* The interrupt exit code shall fetch the cached thread from the ready queue, and: * If the cached thread is not the current thread, invoke the context switch. - * Else, do not invoke it. + * Otherwise do not invoke it. This is simple, but crucial: if this is not implemented correctly, the kernel will not function as intended and will experience bizarre crashes, mostly due to stack corruption. -.. note:: - - If running a coop-only system, i.e. if :kconfig:option:`CONFIG_NUM_PREEMPT_PRIORITIES` - is 0, no preemptive context switch ever happens. The interrupt code can be - optimized to not take any scheduling decision when this is the case. - Thread Creation and Termination *******************************