This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit f96064db75fe79e8ffb2a0d4a51fa7e063a5e068 Author: raiden00pl <raide...@railab.me> AuthorDate: Sat Nov 4 21:23:55 2023 +0100 Documentation: migrate "SMP Critical Sections" from wiki links: https://cwiki.apache.org/confluence/display/NUTTX/SMP+Critical+Sections --- Documentation/implementation/critical_sections.rst | 163 +++++++++++++++++++++ Documentation/implementation/index.rst | 1 + 2 files changed, 164 insertions(+) diff --git a/Documentation/implementation/critical_sections.rst b/Documentation/implementation/critical_sections.rst new file mode 100644 index 0000000000..45b30e21f1 --- /dev/null +++ b/Documentation/implementation/critical_sections.rst @@ -0,0 +1,163 @@ +================= +Critical Sections +================= + +Single CPU Critical Sections +============================ + +OS Interfaces +------------- + +Before we talk about SMP Critical Sections let's first review the internal OS +interfaces avaiable and what they do in the single CPU case: + +* ``up_irq_save()`` (and its companion, ``up_irq_restore()``). These simple + interfaces just enable and disable interrupts globally. This is the simplest + way to establish a critical section in the single CPU case. It does have + side-effects to real-time behavior as discussed elsewhere. + +* ``up_irq_save()`` should never be called directly, however. Instead, the wrapper + macros enter_critical_section() (and its companion ``leave_critical_section()``) + or ``spin_lock_irqsave()`` (and ``spin_unlock_irqrestore()``) should be used. + In the single CPU case, these macros are defined to be simply ``up_irq_save()`` + (or ``up_irq_save()``). Rather than being called directly, they should always + be called indirectly through these macros so that the code will function in the + SMP environment as well. + +* Finally, there is ``sched_lock()`` (and ``sched_unlock()``) that disable (and + enable) pre-emption. That is, ``sched_lock()`` will lock your kernel thread in + place and prevent other tasks from running. Interrupts are still enabled, but + other tasks cannot run. + + +Using sched_lock() for Critical Sections – **DON'T** +---------------------------------------------------- + +In the single CPU case, ``sched_lock()`` can do a pretty good job of establishing a +critical section too. After all, if no other tasks can run on the single CPU, +then that task has pretty much exclusive access to all resources (provided that +those resources are not shared with interrupt handlers). However, ``sched_lock()`` +must never be used to establish a critical section because it does not work the +same way in the SMP case. In the SMP case, locking the scheduer does not provide +any kind of exclusive access to resources. Tasks running on other CPUs are still +free to do whatever they wish. + +SMP Critical Sections +===================== + +``up_irq_save()`` and ``up_irq_restore()`` +------------------------------------------ + +As mentioned, ``up_irq_save()`` and ``up_irq_restore()`` should never be called +directly. That is because the behavior is different in multiple CPU systems. In +the multiple CPU case, these functions only enable (or disable) interrupts on the +local CPU. They have no effect on interrupts in the other CPUs and hence really +accomplish very little. Certainly they do not provide a critical section in any +sense. + +``enter_critical_section()`` and ``leave_critical_section()`` +------------------------------------------------------------- + +**spinlocks** + +In order to establish a critical section, we also need to employ spinlocks. Spins +locks are simply loops that execute in one processor. If processor A sets spinlock +x, then processor B would have to wait for the spinlock like: + +.. code-block:: C + + while (test_and_set(x)) + { + } + +Where test and set is an atomic operation that sets the value of a memory location +but also returns its previous value. Here we are talking about atomic in terms of +memory bus operations: The testing and setting of the memory location must be atomic +with respect to other bus operations. Special hardware support of some kind is +necessary to implement ``test_and_set()`` logic. + +When Task A released the lock x, Task B will successfully take the spinlock and +continue. + +**Implementation** + +Without going into the details of the implementation of ``enter_critical_section()`` +suffice it to say that it (1) disables interrupts on the local CPU and (2) uses +spinlocks to assure exclusive access to a code sequence across all CPUs. + +NOTE that a critical section is indeed created: While within the critical section, +the code does have exclusive access to the resource being protected. However the +behavior is really very different: + +* In the single CPU case, disable interrupts stops all possible activity from any + other task. The single CPU becomes single threaded and un-interruptible. +* In the SMP case, tasks continue to run on other CPUs. It is only when those other + tasks attempt to enter a code sequence protected by the critical section that those + tasks on other CPUs will be stopped. They will be stopped waiting on a spinlock. + +``spin_lock_irqsave()`` and ``spin_unlock_irqrestore()`` +-------------------------------------------------------- + +**Generic Interrupt Controller (GIC)** + +ARM provides a special, optional sub-system called MPCore that provides +multi-core support. One MPCore component is the Generic Interrupt Controller +or GIC. The GIC supports 16 inter-processor interrupts and is a key component for +implementing SMP on those platforms. The are called Software Generated Interrupts +or SGIs. + +One odd behavior of the GIC is that the SGIs cannot be disabled (at least not +using the standard ARM global interrupt disable logic). So disabling local +interrupts does not prevent these GIC interrupts. + +This causes numerous complexities and significant overhead in establishing a +critical section. + +**ARMv7-M NVIC** + +The GIC is available in all recent ARM architectures. However, most embedded +ARM7-M multi-core CPUs just incorporate the inter-processor interrupts as a +normal interrupt that is mask-able via the NVIC (each CPU will have its own NVIC). + +This means in those cases, the critical section logic can be greatly simplified. + +**Implementation** + +For the case of the GIC with no support for disabling interrupts, +``spin_lock_irqsave()`` and ``spin_unlock_irqstore()`` are equivalent to +``enter_critical_section()`` and ``leave_critical_section()``. In is only in the +case where inter-processor interrupts can be disabled that there is a difference. + +In that case, ``spin_lock_irqsave()`` will disable local interrupts and take +a spinlock. This is really very simple and efficient implementation of a critical +section. + +There are two important things to note, however: + +* The logic within this critical section must never suspend! For example, if + code were to call ``spin_lock_irqsave()`` then ``sleep()``, then the sleep + would occur with the spinlock in the lock state and the whole system could + be blocked. Rather, ``spin_lock_irqsave()`` can only be used with straight + line code. + +* This is a different critical section than the one established via + ``enter_critical_section()``. Taking one critical section, does not prevent + logic on another CPU from taking the other critical section and the result + is that you make not have the protection that you think you have. + +``sched_lock()`` and ``sched_unlock()`` +--------------------------------------- + +Other than some details, the SMP ``sched_lock()`` works much like it does in +the single CPU case. Here are the caveats: + +* As in the single CPU case, the case that calls ``sched_lock()`` is locked + in place and cannot be suspected. + +* However, tasks will continue to run on other CPUs so ``sched_lock()`` cannot + be used as a critical section. + +* Tasks on other CPUs are also locked in place. However, they may opt to suspend + themselves at any time (say, via ``sleep()``). In that case, only the CPU's + IDLE task will be permitted to run. + diff --git a/Documentation/implementation/index.rst b/Documentation/implementation/index.rst index 42f6054e22..f1aac1feb4 100644 --- a/Documentation/implementation/index.rst +++ b/Documentation/implementation/index.rst @@ -7,3 +7,4 @@ Implementation Details :caption: Contents: processes_vs_tasks.rst + critical_sections.rst