Documentation: migrate "Critical Section Monitor" from wiki
links: https://cwiki.apache.org/confluence/display/NUTTX/Critical+Section+Monitor
This commit is contained in:
parent
f96064db75
commit
fdc671fa3e
|
@ -2,6 +2,40 @@
|
||||||
Critical Sections
|
Critical Sections
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
Types and Effects of Critical Sections
|
||||||
|
======================================
|
||||||
|
|
||||||
|
A critical section is a short sequence of code where exclusive execution is
|
||||||
|
assured by globally disabling other activities while that code sequence executes.
|
||||||
|
When we discuss critical sections here we really refer to one of two mechanisms:
|
||||||
|
|
||||||
|
* **Critical Section proper** A critical section is established by calling
|
||||||
|
``enter_critical_section()``; the code sequence exits the critical section by
|
||||||
|
calling ``leave_critical_section()``. For the single CPU case, this amounts to
|
||||||
|
simply disabling interrupts but is more complex in the SMP case where spinlocks
|
||||||
|
are also involved.
|
||||||
|
|
||||||
|
* **Disabling Pre-emption** This is a related mechanism that is lumped into this
|
||||||
|
discussion because of the similarity of its effects on the system. When pre-emption
|
||||||
|
is disabled (via ``sched_lock()``), interrupts remain enabled, but context switches
|
||||||
|
may not occur; the current task is locked in place and cannot be suspended until
|
||||||
|
the scheduler is unlocked (via ``sched_unlock()``).
|
||||||
|
|
||||||
|
The use of either mechanism will always harm real-time performance.
|
||||||
|
The effects of critical sections on real-time performance is discussed in
|
||||||
|
`Effects of Disabling Interrupts or Pre-Emption on Response Latency <https://cwiki.apache.org/confluence/display/NUTTX/Effects+of+Disabling+Interrupts+or+Pre-Emption+on+Response+Latency>`_ [TODO: move to documentation].
|
||||||
|
The end result is that a certain amount of **jitter** is added to the real-time response.
|
||||||
|
|
||||||
|
Critical sections cannot be avoided within the OS and, as a consequence, a certain
|
||||||
|
amount of "jitter" in the response time is expected. The important thing is to monitor
|
||||||
|
the maximum time that critical sections are in place in order to manage that jitter so
|
||||||
|
that the variability in response time is within an acceptable range.
|
||||||
|
|
||||||
|
NOTE: This discussion applies to Normal interrupt processing. Most of this discussion
|
||||||
|
does not apply to :doc:`/guides/zerolatencyinterrupts`. Those interrupts are not masked
|
||||||
|
in the same fashion and none of the issues address in this page apply to those
|
||||||
|
interrupts.
|
||||||
|
|
||||||
Single CPU Critical Sections
|
Single CPU Critical Sections
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
@ -161,3 +195,238 @@ the single CPU case. Here are the caveats:
|
||||||
themselves at any time (say, via ``sleep()``). In that case, only the CPU's
|
themselves at any time (say, via ``sleep()``). In that case, only the CPU's
|
||||||
IDLE task will be permitted to run.
|
IDLE task will be permitted to run.
|
||||||
|
|
||||||
|
The Critical Section Monitor
|
||||||
|
============================
|
||||||
|
|
||||||
|
Internal OS Hooks
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
**The Critical Section Monitor**
|
||||||
|
|
||||||
|
In order to measure the time that tasks hold critical sections, the OS supports
|
||||||
|
a Critical Section Monitor. This is internal instrumentation that records the
|
||||||
|
time that a task holds a critical section. It also records the amount of time
|
||||||
|
that interrupts are disabled globally. The Critical Section Monitor then retains
|
||||||
|
the maximum time that the critical section is in place, both per-task and globally.
|
||||||
|
|
||||||
|
The Critical Section Monitor is enabled with the following setting in the
|
||||||
|
configuration::
|
||||||
|
|
||||||
|
CONFIG_SCHED_CRITMONITOR=y
|
||||||
|
|
||||||
|
**Perf Timers interface**
|
||||||
|
|
||||||
|
.. todo:: missing description for perf_xxx interface
|
||||||
|
|
||||||
|
**Per Thread and Global Critical Sections**
|
||||||
|
|
||||||
|
In NuttX critical sections are controlled on a per-task basis. For example,
|
||||||
|
consider the following code sequence:
|
||||||
|
|
||||||
|
.. code-block:: C
|
||||||
|
|
||||||
|
irqstate_t flags = enter_critical_section();
|
||||||
|
sleep(5);
|
||||||
|
leave_critical_section(flags);
|
||||||
|
|
||||||
|
The task, say Task A, establishes the critical section with
|
||||||
|
``enter_critical_section()``, but when Task A is suspended by the ``sleep(5)``
|
||||||
|
statement, it relinquishes the critical section. The state of the system will
|
||||||
|
then be determined by the next task to be resumed, say Task B: Typically, the
|
||||||
|
next task will not be in a critical section and so the critical section is
|
||||||
|
broken while the task sleeps. That critical section will be re-established when
|
||||||
|
that Task A runs again after the sleep time expires.
|
||||||
|
|
||||||
|
However, if Task B that is resumed is also within a critical section, then the
|
||||||
|
critical section will be extended even longer! This is why the global time that
|
||||||
|
the critical section in place may be longer than any time that an individual
|
||||||
|
thread holds the critical section.
|
||||||
|
|
||||||
|
ProcFS
|
||||||
|
------
|
||||||
|
|
||||||
|
The OS reports these maximum times via the ProcFS file system, typically
|
||||||
|
mounted at ``/proc``:
|
||||||
|
|
||||||
|
* The ``/proc/<ID>/critmon`` pseudo-file reports the per-thread maximum value
|
||||||
|
for thread ID = <ID>. There is one instance of this critmon file for each
|
||||||
|
active task in the system.
|
||||||
|
|
||||||
|
* The ``/proc/critmon`` pseuo-file reports similar information for the global
|
||||||
|
state of the CPU.
|
||||||
|
|
||||||
|
The form of the output from the ``/proc/<ID>/critmon`` file is::
|
||||||
|
|
||||||
|
X.XXXXXXXXX,X.XXXXXXXXX
|
||||||
|
|
||||||
|
Where ``X.XXXXXXXXX`` is the time in seconds with nanosecond precision
|
||||||
|
(but not necessarily accuracy, accuracy is dependent on the timing clock
|
||||||
|
source). The first number is the maximum time that the held pre-emption
|
||||||
|
disabled; the second number number is the longest duration that the critical
|
||||||
|
section was held.
|
||||||
|
|
||||||
|
This file cat be read from NSH like:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
nsh> cat /proc/1/critmon
|
||||||
|
0.000009610,0.000001165
|
||||||
|
|
||||||
|
The form of the output from the ``/proc/critmon`` file is simlar::
|
||||||
|
|
||||||
|
X,X.XXXXXXXXX,X.XXXXXXXXX
|
||||||
|
|
||||||
|
Where the first X is the CPU number and the following two numbers have the
|
||||||
|
same interpretation as for ``/proc/<ID>/critmon``. In the single CPU case,
|
||||||
|
there will be one line in the pseudo-file with ``X=0``; in the SMP case
|
||||||
|
there will be multiple lines, one for each CPU.
|
||||||
|
|
||||||
|
This file can also be read from NSH:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
nsh> cat /proc/critmon
|
||||||
|
0,0.000009902,0.000023590
|
||||||
|
|
||||||
|
These statistics are cleared each time that the pseudo-file is read so that
|
||||||
|
the reported values are the maximum since the last time that the ProcFS pseudo
|
||||||
|
file was read.
|
||||||
|
|
||||||
|
``apps/system/critmon``
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Also available is a application daemon at ``apps/sysem/critmon``. This daemon
|
||||||
|
periodically reads the ProcFS files described above and dumps the output to
|
||||||
|
stdout. This daemon is enabled with:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
nsh> critmon_start
|
||||||
|
Csection Monitor: Started: 3
|
||||||
|
Csection Monitor: Running: 3
|
||||||
|
nsh>
|
||||||
|
PRE-EMPTION CSECTION PID DESCRIPTION
|
||||||
|
MAX DISABLE MAX TIME
|
||||||
|
0.000100767 0.000005242 --- CPU 0
|
||||||
|
0.000000292 0.000023590 0 Idle Task
|
||||||
|
0.000036696 0.000004078 1 init
|
||||||
|
0.000000000 0.000014562 3 Csection Monitor
|
||||||
|
...
|
||||||
|
|
||||||
|
And can be stopped with:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
nsh> critmon_stop
|
||||||
|
Csection Monitor: Stopping: 3
|
||||||
|
Csection Monitor: Stopped: 3
|
||||||
|
|
||||||
|
IRQ Monitor and Worst Case Response Time
|
||||||
|
========================================
|
||||||
|
|
||||||
|
The IRQ Monitor is additional OS instrumentation. A full discusssion of the
|
||||||
|
IRQ Monitor is beyond the scope of this page. Suffice it to say:
|
||||||
|
|
||||||
|
* The IRQ Monitor is enabled with ``CONFIG_SCHED_IRQMONITOR=y``.
|
||||||
|
|
||||||
|
* The data collected by the IRQ Monitor is provided in ``/proc/irqs``.
|
||||||
|
|
||||||
|
* This data can also be viewed using the ``nsh> irqinfo`` command.
|
||||||
|
|
||||||
|
* This data includes the number of interrupts received for each IRQ and the
|
||||||
|
time required to process the interrupt, from entry into the attached
|
||||||
|
interrupt handler until exit from the interrupt handler.
|
||||||
|
|
||||||
|
From this information we can calculate the worst case response time from
|
||||||
|
interrupt request until a task runs that can process the the interrupt.
|
||||||
|
That worst cast response time, ``Tresp``, is given by:
|
||||||
|
|
||||||
|
* ``Tresp1 = Tcrit + Tintr + C1``
|
||||||
|
|
||||||
|
* ``Tresp2 = Tintr + Tpreempt + C2``
|
||||||
|
|
||||||
|
* ``Tresp = MAX(Tresp1, Tresp2)``
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
* ``C1`` and ``C2`` are unknown, irreducible constants that reflect such things as
|
||||||
|
hardware interrupt latency and context switching time,
|
||||||
|
|
||||||
|
* ``Tcrit`` is the longest observed time within a critical section,
|
||||||
|
|
||||||
|
* ``Tintr`` is the time required for interrupt handler execution for the event
|
||||||
|
of interest, and
|
||||||
|
|
||||||
|
* ``Tpreempt`` is the longest observed time with preemption disabled.
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
|
||||||
|
#. This calculation assumes that the task of interest is the highest priority task
|
||||||
|
in the system. It does not consider the possibility of the responding task being
|
||||||
|
delayed due to insufficient priority.
|
||||||
|
|
||||||
|
#. This calculation does not address the case where the interfering task has both
|
||||||
|
preemption disabled and holds the critical section. Certainly Tresp1 is valid
|
||||||
|
in this case, but Tresp2 is not. There might some additional, unmeasured delay
|
||||||
|
after the interrupt and before the responding task can run depending on the order
|
||||||
|
in which the critical section is released and preemption is re-enabled:
|
||||||
|
|
||||||
|
* When the task leaves the critical section, the pending interrupt will execute
|
||||||
|
immediately with or without preemption enabled.
|
||||||
|
|
||||||
|
* If preemption is enabled first, then the will be no delay after the interrupt
|
||||||
|
because preemption will be enabled when the interrupt returns.
|
||||||
|
|
||||||
|
* If the task leaves critical section first, then there will be some small delay
|
||||||
|
of unknown duration after the interrupts returns and before the responding
|
||||||
|
task can run because preemption will be disabled when the interrupt returns.
|
||||||
|
|
||||||
|
#. This calculation does not address concurrent interrupts. All interrupts run at the
|
||||||
|
same priority and if an interrupt request occurs while within an interrupt handler,
|
||||||
|
then it must pend until completion of that interrupt. So perhaps the above formula
|
||||||
|
for ``Tresp1`` should instead be the following? (This assumes that hardware arbitration
|
||||||
|
is such that the interrupt of interest will be deferred by no more than one interrupt).
|
||||||
|
Concurrent, nested interrupts might be better supported with prioritized.
|
||||||
|
See more: :doc:`/guides/nestedinterrupts`.
|
||||||
|
|
||||||
|
* ``Tresp1 = Tcrit + Tintrmax + Tintr + C1``
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
* ``Tintrmax`` is the longest interrupt processing time of all interrupt sources
|
||||||
|
(excluding the interrupt for the event under consideration).
|
||||||
|
|
||||||
|
What can you do?
|
||||||
|
----------------
|
||||||
|
|
||||||
|
What can you do if the timing data indicates that you cannot meet your deadline?
|
||||||
|
You have these options:
|
||||||
|
|
||||||
|
#. Use these tools to find the exact function that holds the critical section or
|
||||||
|
disables preemption too long. Then optimize that function so that it releases
|
||||||
|
that resource sooner. Often critical sections are established over long sequences
|
||||||
|
or code when they could be re-designed to use critical sections over shorter code
|
||||||
|
sequences.
|
||||||
|
|
||||||
|
#. In some cases, use of critical sections or disabling of pre-emption could replaced
|
||||||
|
with a locking semaphore. The scope of the locking effect for the use of such locks
|
||||||
|
is not global but is limited only to tasks that share the same resource. Critical
|
||||||
|
sections should correctly be used only to protect resources that are shared between
|
||||||
|
tasking level logic and interrupt level logic.
|
||||||
|
|
||||||
|
#. Switch to :doc:`/guides/zerolatencyinterrupts`. Those interrupts are not subject
|
||||||
|
to most of the issues discussed in this page.
|
||||||
|
|
||||||
|
**NOTE**
|
||||||
|
|
||||||
|
There are a few places in the OS were preemption is disabled via ``sched_lock()`` in
|
||||||
|
order to establish a critical section. That is an incorrect use of ``sched_lock()``.
|
||||||
|
``sched_lock()`` simply prevents the currently executing task from being suspended.
|
||||||
|
For the case of the single CPU platform, that does effectively create a critical
|
||||||
|
section: Since no other task can run, the locking task does have exclusive access
|
||||||
|
to all resources that are not shared with interrupt level logic.
|
||||||
|
|
||||||
|
But in the multi-CPU SMP case that is not true. ``sched_lock()`` still keeps the
|
||||||
|
current task running on CPU from being suspended, but it does not support any
|
||||||
|
exclusivity in accesses because there will be other tasks running on other CPUs
|
||||||
|
that may access the same resources.
|
||||||
|
|
Loading…
Reference in New Issue