doc: Migrating the rest of documentation from cwiki.

* This completes task list in https://github.com/apache/nuttx/issues/11127.
* This preserves selected content from cwiki and moves it to new docs.
* Most pages are simple copy-paste with a simple RST formatting updates,
  with minor updates.
* Content update / reorganization will follow later on when needed.
* Files added (or updated title from cwiki -> current docs):
  * Documentation/implementation:
    * index.
    * cancellation_points.
    * Asynchronous vs. Synchronous Context Switches -> context_switches.rst.
    * ARMv7-M Hardfaults, SVCALL, and Debuggers -> hardfatuls.rst.
    * chip.h FAQ -> chip_h.rst.
    * Debug Output (SYSLOG) Issues -> syslog.rst.
    * Detaching File Descriptors -> file_descriptors.rst.
    * device_nodes.rst.
    * Dynamic Clocking -> power_management.rst.
    * ENOTTY ioctl() Return Value -> ioctl.rst.
    * memory_configurations.rst.
    * kernel_modules_vs_shared_libraries.rst.
    * NAKing USB OUT/IN Tokens -> usb.rst.
    * naming_arch_mcu_board_interfaces.rst.
    * naming_os_internals.rst.
    * nuttx_tasking.rst.
    * oneshot_timers_and_cpu_load.rst.
    * nuttx_initialization_sequence.rst.
    * short_time_delays.rst.
    * Signal Handler Tour -> signal_handlers.rst.
    * smp.rst.
    * syslog.rst.
    * Task Exit Sequence -> nuttx_tasking.rst.
    * tasks_vs_threads.rst.
    * tls.rst.
    * tickless_os.rst.
    * Why Can't Kernel Threads Have pthreads -> kernel_threads_vs_pthreads.rst.
  * Documentation/components/filesystem:
    * smartfs.rst.

Signed-off-by: Tomasz 'CeDeROM' CEDRO <tomek@cedro.info>
This commit is contained in:
Tomasz 'CeDeROM' CEDRO
2026-05-28 09:34:04 +08:00
committed by Xiang Xiao
parent 2354c720d8
commit 0cfa6f1b96
31 changed files with 8250 additions and 8 deletions
@@ -1,3 +1,5 @@
.. _nuttx-filesystem:
=================
NuttX File System
=================
@@ -1,3 +1,5 @@
.. _nuttx-pseudofs:
==================
Pseudo File System
==================
@@ -208,4 +210,4 @@ will also fail. You cannot do this, for example:
nsh>
See also NxFileSystem in
`Porting Guide <https://cwiki.apache.org/confluence/display/NUTTX/Porting+Guide>`_
`Porting Guide <https://cwiki.apache.org/confluence/display/NUTTX/Porting+Guide>`_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,441 @@
.. _cancellation-points:
===================
Cancellation Points
===================
Cancellation Types
==================
In POSIX, there are two pthread cancellation types:
``PTHREAD_CANCEL_DEFERRED`` and ``PTHREAD_CANCEL_ASYNCHRONOUS``.
``PTHREAD_CANCEL_DEFERRED`` is the default and the normal kind of pthread
cancellation that should be used.
You cannot have the ``PTHREAD_CANCEL_DEFERRED`` cancellation type without
cancellation points and you will not have cancellation points unless you
enable them with ``CONFIG_CANCELLATION_POINTS=y``.
``PTHREAD_CANCEL_ASYNCHRONOUS`` is brutal; it simply kills the pthread
immediately without regard for what it is doing.
``PTHREAD_CANCEL_DEFERRED`` works differently; nothing happens immediately.
Instead, the cancellation will be deferred until the pthread is at
(or a better preposition would be within) a cancellation point.
Cancellation points add special instrumentation to a specific set
of interface functions. Those interface functions are listed here:
http://OpenGroup.org.
Enter and Leave Cancelation Point
=================================
In NuttX, each of these functions ``CONFIG_CANCELLATION_POINTS=y`` will enable
a special call to ``enter_cancellation_point()`` at the beginning
of the function and a matching call to ``leave_cancellation_point()`` before
the interface function returns.
These may be nested: One cancellation point function may call another which
may call another, etc. The ``select()`` function, for example, calls
``poll()`` which calls ``sem_wait()`` so the nesting will be three deep.
These two cancellation point hooks behave in a complementary way.
``enter_cancellation_point()`` increments the nesting level and
``leave_cancellation_point()`` decrements the nesting level.
No action is taken unless the nesting level is zero then, in that case,
if there is a pending cancellation then the thread exists normally
by simply calling ``pthread_exit()``.
pthread_cancel()
================
When ``pthread_cancel()`` is called, it will check if the deferred
cancellation mode is enabled for the target pthread. If so it will:
1. Mark the cancellation as pending.
2. If the thread is waiting on a semaphore or message queue (and is in
a cancelable state), it will wake it just in the same was as if
the thread received a single (except with ``ECANCELED``
instead of ``EINTR``).
If, on the other hand, the asynchronous mode is enabled (and the thread is
in a cancelable state), ``pthread_cancel()`` will, instead,
kill the thread immediately.
.. note:: For pthreads, the cancelable state is controlled by the interface
``pthread_setcancelstate().`` This allows the pthread to disable or
re-enable cancelability. If the thread is not cancelable when
``pthread_cancel()`` is called, it will never do more than just mark
the cancellation as pending. When the cancelable state is
re-enabled, then any pending cancellation will take effect.
select()
========
Let's walk through the case of ``select()`` to see how it works:
* When ``select()`` is called, it allocates some memory that it needs to
handle the conversion to ``poll()``.
* It then calls ``poll()`` which sets up the poll with all of the relevant
drivers and, eventually, calls ``sem_wait()``.
So this pthread will spend almost all of its time waiting in ``sem_wait()``
and that is the most likely state of the pthread when ``pthread_cancel()``
is called with deferred cancellation eanbled.
* When ``pthread_cancel()`` is called, it will set the pending cancellation
state and wake up ``sem_wait()`` with the ``ECANCELED`` error.
* ``sem_wait()`` will see the pending cancellation, but will do nothing
because the nesting level is decremented only to two. It will return to
``poll()`` with the ``ECANCELED`` error. Note that the semaphore is left
in a healthy state.
* ``poll()`` will tear-down down the poll from all of the drivers and call
``leave_cancellation_point()`` before it exits. Again, it will see the
pending cancellation but will do nothing because the nesting level
decrements only to 1. Note that since ``poll()`` does the complete
tear-down and all of the drivers are left in a healthy state.
* Finally, ``select()`` cleans up all of its memory allocations and calls
``leave_cancellation_point()``. This time, the nesting level decrements
to zero and ``leave_cancellation_point()`` will call ``pthread_exit()``
with everything in a proper state.
If asynchronous pthread cancellation were selected, then the behavior would
be very different. The first two steps would be the same but then:
* When ``pthread_cancel()`` is called, the thread would be immediately killed.
The semaphore would be left in the locked state; the memory allocated by
``select()`` would be stranded; drivers would be left with stale pointers
to stranded memory. Not a good state to leave the system!
* ``select()`` will not return to the caller in either case.
Application Interfaces
======================
The following pthread application interfaces are available to manage cancellation:
* ``pthread_cancel()`` cancels a thread.
* ``pthread_setcancelstate()`` can enable or disable a cancellation.
* ``pthread_setcanceltype()`` can be used to switched between asynchronous
and deferred thread cancellation.
* ``pthread_testcancel()`` can be used to force a cancellation point.
Task Deletion
=============
NuttX also supports some non-standard interfaces for task deletion
(i.e., cancellation) of tasks as well. The cancellation point logic
is identical and the task application interfaces are very similar:
* ``task_delete()`` deletes (cancels) a task.
* ``task_setcancelstate()`` can enable or disable task cancellation.
* ``task_setcanceltype()`` can be used to switched between asynchronous
and deferred task cancellation.
* ``task_testcancel()`` can be used to force a cancellation point.
Design Issues
=============
.. note:: The issue addressed in this paragraph is covered by
`Issue 619 <https://github.com/apache/nuttx/issues/619>`_
has been corrected in the code base through a series of changes
culiminating in
`PR 733 <https://github.com/apache/nuttx/pull/733>`_.
This discussion still has value, however, as a warning
for things that one needs to take into consideration
when designing logic within the OS.
There is one logical problem in many parts of the system. The following
sequence of code appears in many places in the code base.
It will work not work with thread/task cancellation:
.. code-block:: c
void some_lock(FAR sem_t *sem)
{
while ((ret = nxsem_wait(sem)) < 0)
{
DEBUGASSERT(ret = -EINTR || ret = -ECANCELED);
}
}
That logic will makes it impossible to cancel the thread.
Let me explain why:
* The fact that ``-ECANCELED`` is occurring means that some other task
tried to cancel this one.
* The logic sequence is probably something like:
* OS Function ``A`` is called by the application. Function ``A`` is
a cancellation point. It calls ``enter_cancellation_point()`` on entry
and ``leave_cancellation_point()`` on exit. If the application task is
canceled, then the call to ``leave_cancellation_point()`` is where
the task actually ends (by simply calling exit()).
* OS Function ``A`` calls some internal Function ``B`` which has the above
killer logic in it. It calls ``some_lock()`` and the task is suspended
on waiting to get a count on the semaphore.
When the task is canceled, ``nxsem_wait()`` wakes up with the ``-ECANCELED``
error and returns that error to Function ``B``. But because Function ``B``
just loops and calls ``nxsem_wait()`` again, the task does not terminate;
it just goes back to waiting.
The end result is that Function ``B`` does not return to Function ``A`` so
``leave_cancellation_point()`` is never called and the task is not canceled.
The fix requires a little re-design. The locking function would
have to be like:
.. code-block:: c
int some_lock(FAR sem_t *sem)
{
while ((ret = nxsem_wait(sem)) < 0)
{
DEBUGASSERT(ret = -EINTR || ret = -ECANCELED);
if (ret != -EINTR)
{
break;
}
}
return ret;
}
It then returns a success/failure indication. Calling logic would also have
to be modified to detect the failure case and handle it appropriately.
In the case that the failure case, the calling logic (Function ``B`` in this
example) needs to clean up and return the failure upstream (to Function ``A``)
which must also clean up. Then, eventually, ``leave_critical_section()`` will
be called and the function will exit correctly.
Issue 619 Discussion
====================
.. note:: The
`Issue 619 <https://github.com/apache/nuttx/issues/619>`_
has been corrected in the code base through a series of changes
culiminating in
`PR 733 <https://github.com/apache/nuttx/pull/733>`_.
nxsem_wait_uninterruptible()
----------------------------
In NuttX, thread or tasks may be canceled using ``pthread_cancle()`` or
``task_delete()``. These can also be canceled via certain signals if default
signal actions are enabled.
When threads or tasks are canceled asynchronously, resources may be stranded.
For example, if memory is allocated, it will not be deallocated when the
task/thread is canceled in the FLAT build. These kind on leaks can be handled
by the application with functions registered with ``pthread_cleanup()`` or
``on_exit()``, but others cannot.
The generic solution for NuttX is through the use of cancellation points.
Cancellation points are a mechanism that assures that all resources used
internal by OS interface calls are cleaned-up automatically at designated
cancellation points. An interface is normally a cancellation point if
it has any possibility of blocking.
In NuttX, when threads are blocked waiting of a semaphore when they are
canceled, ``nxsem_wait()`` will return ``-ECANCELED`` and the correct behavior
to let the error ripple back to the cancellation point where the thread/task
will be canceled after all resources have been cleaned up.
Many internal OS functions use ``nxsem_wait_uninterruptible()`` for waiting.
``nxsem_wait_uninterruptible()`` is defined in ``include/nuttx/semaphore.h``.
``nxsem_wait_uninterruptible()`` wraps a call to ``nxsem_wait()`` in
a loop like:
.. code-block:: c
static inline int nxsem_wait_uninterruptible(FAR sem_t *sem)
{
int ret;
do
{
/* Take the semaphore (perhaps waiting) */
ret = nxsem_wait(sem);
}
while (ret == -EINTR || ret == -ECANCELED);
return ret;
}
The correct behavior is to continue waiting on ``-EINTR`` only.
If the task/thread is awakened by a signal just continue waiting.
The behavior for ``-ECANCELED`` is not correct. It should not loop.
Looping like this will prevent the cancellation from working.
Instead of returning through the cancellation point, the code will be stuck
in the above loop (in defferred cancellation mode) and cannot be cancelled.
The correct behavior is to return when ``-ECANCELED`` is encountered,
not to continue looping:
* GOOD: ``while (ret == -EINTR);``
* BAD: ``while (ret == -EINTR || ret == -ECANCELED);``
The only complexity to this suggested change is that the callers of
``nxsem_wait_uninterruptible()`` must also check the return value for any
errors and make sure that that error is returned to the caller.
The error must propogate all the way back up to the cancellation point.
These are other related inline functions in ``include/nuttx/semaphore.h``
that behave in this same way. This issue applies to them as well.
.. important:: If cancellation points are enabled, the default cancellation
mode should be deferred mode.
.. note:: There are three interfaces affected by this issue:
``nxsem_wait_uninterruptible()`` as discussed above, but also
``nxsem_timedwait_uninterruptible()`` and
``nxsem_tickwait_uninteruptible()`` which are closely related.
Mutual Exclusion Semaphores vs. Signaling Semaphores
----------------------------------------------------
Semaphore have two usages in NuttX:
* Mutual Exclusion Semaphores: Protect the shared data and force mutually
exclusive use of semaphores.
* Signling Semaphores: Used to wait for events to be signaled asynchronously.
Should we return ``-ECANCEL/-EINTR`` for both? Or only for ithe second?
I think both should work the same: For both the semaphore wait should be
terminated and correct error should be returned.
Mutual Exclusion Semaphores
---------------------------
If the API requires returning ``EINTR`` if a signal is received, we should
return the error. This is required in many POSIX interfaces and don't see
how you could avoid that. However, the mutual exclusion semaphores are
the just there for the correctness in the design. There is seldom any real
competitor for the exclusion semaphore so, in general, they do not block and,
hence, would never generate any error.
And even if taking the mutual exclusion semaphore does cause the thread
to block, it should block for only a very brief moment of time and the
likelihood of a signal received or of the task being canceled is very small.
But if there is even a small possibility of that event happening,
it WILL happen in an embedded system (Murphy's law).
But if during the that brief moment while the thread is blocked,
a signal is received, I think that it is correct to return ``-EINTR``.
This is not so critical only because such an event it is rare.
But the specification requires it so we should do it.
When to Return ECANCELED
^^^^^^^^^^^^^^^^^^^^^^^^
The ``ECANCELED`` error is **ABSOLUTELY** critical to return under any
circumstance. That cancellation notification is received only once and
it if is ignored, then the thread will not cancel it will continue to run.
It really must return immediately with ``ECANCELED`` for the cancellation
to work quickly and reliably, where ever ``ECANCELED`` is detected.
Ignoring it is a hard bug in any case.
Signaling Semaphores
^^^^^^^^^^^^^^^^^^^^
The signaling semaphores are a little different story. They may block
for a very long time, for example, waiting for the receipt of data that
may never come. For example, a read from a serial port will hang indefinitely
if nothing is received on the serial port. So if a signal is received or
if the thread is canceled, then it is essential to terminate the semaphore
wait and return the error condition.
We should have no difference of opinion on that.
There is really no difference in the actions that must be taken if a signal
is received of if the task is canceled. Both mutual exclusion semaphores
and signaling semaphores should wake up and return the error condition.
The only real differences is that mutual exclusion semaphores either
do not block or, if they do, the do not block as long.
Mutual Exclusion Semaphores and Serialization
---------------------------------------------
There are situations were the task may block for a very long time,
even indefinitely, on a mutual exclusion semaphore. Consider the following
scenario: A driver that reads data could have a sequence like this:
1. Get exclusive access to the driver by taking a semaphore.
2. Wait on a signaling semaphore for data (might be a long time).
3. Release the mutual exclusion semaphore.
Now suppose there are two applications, Task ``A`` and Task ``B``,
that open the driver and try to read from it. The first, Task ``A``, will get
the mutual exclusion semaphore at ``(1)`` and then will hang waiting
for data at (2). It may have to wait for a very long time to receive data.
When re-entered by the second, Task ``B``, it will hang at ``(1)``
also for very long time. Task ``B`` is effectively queued and cannot event
begin its wait on the signaling semphore until Task ``A`` receives its data
and releases the mutual exclusion semaphore. This is normal behavior
and exactly, how this kind of driver is supposed to work:
This use of mutual exclusion impelements serialization of I/O.
But now suppose that Task ``B`` receives a signal interrupt or a cancellation
event while waiting on the mutual exclusion semaphore. It MUST terminate the
wait and return ``-EINTR`` or ``-ECANCEL`` immediately. Task ``A`` may never
receive data and if that action is not taken, the signal interrup
or cancellation event is lost and the functionality has failed.
When to Return EINTR
^^^^^^^^^^^^^^^^^^^^
Returning ``EINTR`` really only applies to ``read()`` and ``write()``
(and ``open()``) functions as covered by Issue #669 . In other places,
``EINTR`` can be ignored. ``open()`` and ``close()`` should also
return ``EINTR``. But I don't think that is very meaningful for ``close()``
since most people ignore the return value from ``close()``.
But, on the other hand, ``ECANCELED`` can never be ignored under any
condition.
Detecting, Remembering, and Propagating Signal Interrupts and Cancellation Events
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Currently, the only way for an OS interface function to know if a signal
was received is to be awakened with an ``EINTR`` error. That is why
it is critical to always return the ``EINTR`` error and conform with
the POSIX requirements.
However, if a signal is received while the OS interface is NOT waiting,
then there is no way to know if the signal was received. I think that
is a limitation in the current design. There probably should be some latching
indication, perhaps in the TCB, that a signal interrupt has been received.
There is already a ``TCB_FLAG_CANCEL_PENDING`` in the TCB that will tell us
that the if the task has been canceled. That flag is tested in the
``leave_cancellation_point()`` function so I don't think that there is any
corresponding issue for thread cancellation. We just need to make sure that
all waits are aborted if ``ECANCELED`` is received and let the error
indication ripple all they back to the ``leave_cancellation_point()``
function. Then the function will exit cleanly, safely, and quickly.
Interestingly, the ``ECANCELED`` error will never be seen by the application.
It just triggers the return uwind sequence where all resources are recovered
and finally until ``leave_cancellation_point()`` is called. Then the thread
will exit before it returns to the applicaton.
You can see all of this working in the ``board/sim/sim/sim/configs/ostest``
configuration if you also enable::
CONFIG_CANCELLATION_POINTS=y
CONFIG_PTHREAD_CLEANUP=y
There is a ``cancel.c`` test within the OS test, but the more interesting test
is the ``pthread_cleanup.c`` test. You can see how all this works when the code
unwinds with the ``ECANCELED`` error and calls ``leave_cancellation_point()``.
Just single step through ``pthread_cond_wait()`` to see this.
``pthread_cond_wait()`` is a ``cancellation_point()`` and that is where
the thread exit will occur.
`PR#749 <https://github.com/apache/nuttx/pull/749>`_ adds those settings
to that ``sim:otest`` defconfig.
+125
View File
@@ -0,0 +1,125 @@
.. _chip-h:
======
chip.h
======
The purpose of the two chip.h files in each arm chip
====================================================
If you wonder about the purpose of the two ``chip.h`` files in each arm chip.
.. code:: sh
$ find arch/arm -name chip.h | grep stm32
arch/arm/include/stm32/chip.h
arch/arm/src/stm32/chip.h
The reason behind ``arch/arm/src/stm32/chip.h`` file was a bad idea
that happened a long time ago.
Right now, I believe that its only required when ``CONFIG_ARMV7M_CMNVECTOR``
is selected in the configuration. In that case, ``arch/arm/src/stm32/chip.h``
is included by ``arch/arm/src/armv7-m/up_vectors.c`` in order provide
the number of interrupt vectors. In stm32, ``arch/arm/src/stm32/chip.h``
provides the number of vectors indirectly by including the correct,
chip-specific vectors.h file.
This function is a little more obvious in ``arch/arm/srch/lpc43xx/chip.h``.
This ``arch/arm/src/xyz/chip.h`` is also a good way to export awkward
internal header files in a cleaner way. But that use is optional
and not required outside of the chip- and board-related directories.
For the ``arch/include/stm32/chip.h`` file, only set of definitions
is required there, the NVIC priorities:
* ``NVIC_SYSH_PRIORITY_MIN`` provides the lowest interrupt priority
supported by the system. This is the highest numeric value;
but the lowest interrupt priority.
* ``NVIC_SYSH_PRIORITY_DEFAULT`` provides the default priority of all
interrupts. Since truly nested interrupts are not supported, this is
the priority of all interrupts except for the SVCall interrupt.
* ``NVIC_SYSH_PRIORITY_MAX`` provides the maximum interrupt priority.
For all ARMv7-M's, this will be the value zero.
* ``NVIC_SYSH_PRIORITY_STEP`` is the value need to increment from one
priority level to next, lower priority level.
If ``CONFIG_ARMV7M_USEBASEPRI`` is selected, then interrupts will be disabled
by setting the ``BASEPRI`` register to ``NVIC_SYSH_DISABLE_PRIORITY`` so that
most interrupts will not have execution priority.
SVCall must have execution priority in all cases.
In the normal cases, interrupts are not nest-able and all interrupts run
at an execution priority between ``NVIC_SYSH_PRIORITY_MIN`` and
``NVIC_SYSH_PRIORITY_MAX`` (with ``NVIC_SYSH_PRIORITY_MAX`` reserved
for SVCall).
If, in addition, ``CONFIG_ARCH_HIPRI_INTERRUPT`` is defined, then special high
priority interrupts are supported. These are not "nested" in the normal sense
of the word. These high priority interrupts can interrupt normal interrupt
processing but execute outside of OS (although they can "get back
into the game" via a PendSV interrupt).
In the normal course of things, interrupts must occasionally be disabled
using the ``up_irq_save()`` inline function to prevent contention in use
of resources that may be shared between interrupt level and non-interrupt
level logic. Now the question arises, if we are using the ``BASEPRI``
to disable interrupts and have high priority interrupts enabled
(``CONFIG_ARCH_HIPRI_INTERRUPT=y``), do we disable all interrupts except
SVCall (we cannot disable SVCall interrupts)?
Or do we only disable the "normal" interrupts?
If we are using the ``BASEPRI`` register to disable interrupts, then
the answer is that we must disable ONLY the normal interrupts. That is
because we cannot disable SVCALL interrupts and we cannot permit SVCAll
interrupts running at a higher priority than the high priority interrupts.
Otherwise, they will introduce jitter in the high priority interrupt
response time.
Hence, if you need to disable the high priority interrupt, you will have to
disable the interrupt either at the peripheral that generates the interrupt
or at the interrupt controller (e.g., ``NVIC``). Disabling global interrupts
via the ``BASEPRI`` register cannot effect high priority interrupts.
* ``NVIC_SYSH_MAXNORMAL_PRIORITY`` is the maximum priority of "normal"
interrupts. Interrupt are disabled at the level
``NVIC_SYSH_MAXNORMAL_PRIORITY``, disabling all "normal" interrupt
but leaving the high priority and SVCALL interrupts enabled.
* ``NVIC_SYSH_HIGH_PRIORITY`` is the priority of the high priority interrupt.
* ``NVIC_SYSH_DISABLE_PRIORITY`` is the level at which interrupts will
be disabled. This will be equal to ``NVIC_SYSH_MAXNORMAL_PRIORITY``.
* ``NVIC_SYSH_SVCALL_PRIORITY`` is the priority of the SVCall interrupt.
This must be higher priority than ``NVIC_SYSH_DISABLE_PRIORITY`` so that
it is never disabled, but lower in priority than
``NVIC_SYSH_DISABLE_PRIORITY`` so that SVCall handling cannot interfere
with high priority interrupt handling.
These definitions only have to be here because NVIC implementations may differ
in the number of priority levels.
Now, by convention, the ``arch/xyz/include/chip/abc/chip.h`` file is also used
to provide all of the definitions that discriminate the features of the
different members of the chip family.
It is not technically necessary that those chip feature definitions exist
in this header file, but it is a good convention to follow so that
it is easier for people who have to work across the differ architectures.
There is another really big difference between the two chip header files:
They also different in scope:
1. Code in the ``arch/arm/src/xyz`` and in ``boards/arm/xyz/abc/src``
directories can both include the ``arch/arm/src/xyz/chip.h`` header file,
but nothing else can (only because nothing else is provided the header
file path in the build system). So that ``chip.h`` file is intended to be
used only in low-level board/chip logic.
2. The ``chip.h`` header file at ``arch/arm/include/xyz/chip.h``,
on the other hand, will be linked at include/arch/chip and, hence,
can be included anywhere in the system, even from applications.
It can be included like::
#include <arch/chip/chip.h>
So the ``arch/arm/include/xyz/chip.h`` header file is a place where you would
want to put chip-related stuff that can be made visible to application code.
Chip capabilities is a good example of the kind of knowledge that
applications might care about.
@@ -0,0 +1,87 @@
.. _context-switches:
================
Context Switches
================
Two Types of Context Switches
=============================
There are really two different kinds of context switches.
We refer to them as synchronous and asynchronous context switches (but there
might be better names):
* **Synchronous context switch** occurs when the system is interrupted and,
because of on actions within the interrupt handler, a context switch is
generated. In this case, the state of the interrupted task is saved when
the interrupt handler is entered but a different task state is restored
when the interrupt handler returns.
* **Synchronous context switch** in our terminology occurs when a task
explicitly suspends itself by calling some OS interface that causes
the task to block, such as ``usleep()``.
Synchronous Context Switches
============================
There are two ways to implement a synchronous context switch:
``up_savecontext()`` and ``up_fullcontextrestore()``.
You can implement the moral equivalent of ``setjmp()`` and ``longjmp()``
on steroids. Some architectures have a function called ``up_savecontext()``
that is the moral equivalent of ``setjmp()``, it saves the current state
of the task (and like ``setjmp()`` returns ``0`` or ``1`` to indicate
if the context is being restored.
Another function ``up_fullcontextrestore()`` is like ``longjmp()``,
it restores the context saved by either the interrupt handler during
a previous asynchronous context switch or by the ``up_savecontext()``.
The naming differences ``save`` vs ``fullrestore`` is because when
``up_savecontext()`` is called, it does not need to save all of registers.
Only a subset needs to be saved because the processor ABI provides that some
registers are volatile or caller-saved when ``up_savecontext()`` is called.
The are a couple of downsides to the this approach.
First, the ``up_savecontext()`` and ``up_fullcontextrestore()`` functions
are tricky to write. Second, they have limited usage.
They can be used only in the FLAT build mode where all tasks are running with
the same privileges. If you were to try to do ``up_fullcontextrestore()``
to get from an unprivileged task to a privileged task, you would get an
access violation exception of some sort.
System Calls
============
In order to the limitations of ``up_savecontext()`` and
``up_fullcontextrestore()``, you have to do something a little differently.
One way is to use a system call (a **software interrupt**, a **trap** in x86
or an **SVCALL** in ARM land). This generates a software interrupt and uses
the mechanization of the asynchronous context switch: The software interrupt
saves the context of the old task on entry (replacing the functionality of
``up_savecontext()``) and restores the new task context on return (replacing
the functionality of ``up_fullcontextrestore()``).
The tiny software interrupt handler just sets up the context switch.
The ARMv7-M does synchronous contest switches in this way. You can see how
this is done in the ARMv-7M the SVCALL software interrupt handler.
The advantages of this approach are:
1. It is trivially easy to implement. If interrupt level asynchronous context
switches work, then so will these synchronous context switches.
2. You an switch between privileged and unprivileged tasks.
That happens for free when the interrupt returns.
The downside is only that it causes significantly slower synchronous context
switching times. It adds the overhead of interrupt processing and interrupt
IRQ dispatching to each context switch.
I think this is still the correct way to go despite its worse performance.
Several modifications would be required to convert from the first to the
second type of synchronous context switches:
1. Creation of the software handlers for the context switch.
2. Converting all of the back-to-back calls to ``up_savecontext()`` and
``up_restorefullcontext()`` to a single call to a function that executes
the software interrupt, usually something like ``up_switchcontext()``.
@@ -0,0 +1,94 @@
.. _device-nodes:
============
Device Nodes
============
Linux Device Nodes
==================
I used to have good Linux expertise a decade or so ago.
But my current Linux knowledge is dated and rusty.
I don't know anything about udev, SystemD, devtmpfs, sysfs, or any of that.
So this is my simplified understanding.
Device files work quite a bit differently in Linux and NuttX.
A device node in Unix/Linux only really contains the only type of the device
and its major and minor device numbers, i.e., it just holds data.
So creating the device node does not install or create the driver;
it simply writes a tiny file containing some special data.
Nothing happens until you try to open the device.
If something in the operating system has not initialized and registered
a driver for that type and major/minor numbers,
then you fail to open the device.
So the device nodes and the device drivers are decoupled in Linux/Unix
and there is a rendezvous that must occur later for the device node
to actually refer to the device.
"(..) Linux maps the device special file passed in system calls
(say to mount a file system on a block device) to the device's
device driver using the major device number and a number of system
tables, ...The major number is actually the offset into the
kernel's device driver table, which tells the kernel what
kind of device it is (whether it is a hard disk or a serial
terminal) (..)"
-- Source:
www.linux-tutorial.info/modules.php?name=MContent&pageid=94.
Normally, when you create a Linux file system, you also create all of the
standard device nodes. But most of these do not map to real devices.
If you try to access most of the devices under ``/dev`` in Linux, they will
fail because the underlying driver that maps to that major/minor number
has not been initialized.
NuttX Device Nodes
==================
NuttX does not use major/minor device numbers and there are no device
"system tables" to associate major/minor numbers to a driver implementation.
NuttX simplifies this be removing the "man in the middle": When you register
the driver, you also create the device node.
.. important:: The device node IS the driver registry.
This is a tremendous simplification and one of the things that
makes NuttX usable in the constrained MCU environment.
In NuttX, device nodes are not really files at all.
They are special entries in the NuttX root pseudo-filesystem.
See :ref:`NuttX Pseudo File System <nuttx-pseudofs>` for more details.
Usage Differences
=================
.. important:: Only devices drivers can create device nodes and the existence
of the device node means that the device has been initialized,
registered, and is ready for use (with the exception of some
removable devices that may not actually be ready).
You cannot create device nodes from applications!
You could argue that this simplification is a deviation from my Unix/Linux
roadmap and would have to agree that you are right.
But it is also the kind of enabling simplification that makes a tiny
Unix-like operating system feasible on these lower end MCUs.
In Linux standard device drivers are initialized and registered as with NuttX.
A (privileged) application can create a device node, but cannot initialize
or register a device driver directly (as far as I know).
I believe that if you want to instantiate an uninitialized, unregistered
device driver you would have to install a kernel module containing
the driver (which would probably also create the device nodes corresponding
to the driver).
boardctl()
==========
NuttX does support a sneak interface to support interactions with board-level
OS logic. That sneak interface is ``boardctl()`` (see :ref:`board-ioctl` and
:ref:`nuttx-initialization-sequence` for more details).
That interface could potentially be used to force initialization of device
drivers by application code. That discussion is to be provided.
@@ -0,0 +1,231 @@
.. _file-descriptors:
================
File Descriptors
================
Drivers within Drivers
======================
Have you ever wanted to write a driver the uses another driver?
For example, suppose you want to develop a character driver for a module
that provides a serial interface to the host. Wouldn't you like to be able to
open and use the serial driver within your module driver?
Or suppose the you have a device with analog inputs such as joystick
and you would like to use the ADC character driver to sample the joystick?
Would you not want to contain the ADC character driver within the joystick
driver?
Drivers File Descriptors
========================
One obstacle to containing a driver instance within another driver
is the nature of the file descriptor. When you open a file or a driver from
any thread in a task, you receive a file descriptor that you then may use
in the task to access the device.
In the NuttX implementation, that file descriptor is really an index into
a pre-allocated array of type struct file.
It is really the underlying struct file instance referred to by the
file descriptor that use used to access the device.
The file descriptor/index is simply a portable way to reference the struct
file instance.
.. note:: In NuttX, threads are organized into task groups, see
:ref:`Tasks vs. Threads <tasks-vs-threads>` for more details.
Each task group has its own array of struct file. All of the threads within
the same task group share the same array allocation. Therefore, the same file
descriptor index is valid for all threads within the task group.
But, on the other hand, threads running within a different task group
use a different array of struct file and file descriptors opened
in this other task group have no relationship.
In short, file descriptors cannot be used by different tasks.
A driver must not be associated with any specific task group's resources.
That would make the driver dependent upon that task group. Any task should be
able top open and use the driver. Therefore, the driver cannot use
file descriptors to access the contained driver instance.
So, if you cannot use file descriptors within a device drivers, how can
one driver contain another driver instance? The answer is by detaching
the file descriptor from the open file instance.
Detaching Open Files
====================
There is an internal OS interface called ``file_detach()`` that is prototyped
and described in the header file ``include/nuttx/fs/fs.h``:
.. code-block:: c
/****************************************************************************
* Name: file_detach
*
* Description:
* This function is used to device drivers to create a task-independent
* handle to an entity in the file system. file_detach() duplicates the
* 'struct file' that underlies the file descriptor, then closes the file
* descriptor.
*
* This function will fail if fd is not a valid file descriptor. In
* particular, it will fail if fd is a socket descriptor.
*
* Input Parameters:
* fd - The file descriptor to be detached. This descriptor will be
* closed and invalid if the file was successfully detached.
* filep - A pointer to a user provided memory location in which to
* received the duplicated, detached file structure.
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned on
* any failure to indicate the nature of the failure.
*
****************************************************************************/
#if CONFIG_NFILE_DESCRIPTORS > 0
int file_detach(int fd, FAR struct file *filep);
#endif
In order to use ``file_detach()`` you would do the following:
* Allocate an instance of struct file either dynamically or statically.
This instance will represent the contained driver.
* Open the file or driver to get a file descriptor
* Call ``file_detach()`` in order to clone the underlying struct file to your
allocated instance. Detaching the file descriptor has the side-effect
of closing the original instance.
* You can then use the driver methods of the contained driver directly from
your driver. These driver methods are described in ``include/nuttx/fs/fs.h``.
There is also a ``file_close_detached()`` internal OS function that you can use
to recover resources if you no longer need the contained driver instance:
.. code-block:: c
/****************************************************************************
* Name: file_close_detached
*
* Description:
* Close a file that was previously detached with file_detach().
*
* Input Parameters:
* filep - A pointer to a user provided memory location containing the
* open file data returned by file_detach().
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned on
* any failure to indicate the nature of the failure.
*
****************************************************************************/
int file_close_detached(FAR struct file *filep);
This technique is not limited to character drivers but can also be used
with files in a file system or even block drivers.
Detached File Helpers
=====================
Once the file structure has been detached from its file descriptor,
you can no longer use the standard VFS functions ``read()``, ``write()``,
``ioctl()``, etc. Fortunately, there are a parallel set of interfaces
that can be used with detached files. These are decribed in detail
in ``include/nuttx/fs/fs.h`` and only listed here below:
.. code-block:: c
ssize_t file_read(FAR struct file *filep, FAR void *buf, size_t nbytes);
ssize_t file_write(FAR struct file *filep, FAR const void *buf, size_t nbytes);
ssize_t file_pread(FAR struct file *filep, FAR void *buf, size_t nbytes,
off_t offset);
ssize_t file_pwrite(FAR struct file *filep, FAR const void *buf,
size_t nbytes, off_t offset);
off_t file_seek(FAR struct file *filep, off_t offset, int whence);
int file_ioctl(FAR struct file *filep, int req, unsigned long arg);
int file_fsync(FAR struct file *filep);
int file_dup2(FAR struct file *filep1, FAR struct file *filep2);
int file_vfcntl(FAR struct file *filep, int cmd, va_list ap);
The SYLOG Device: A Case Study
==============================
This technique is used for the SYSLOG device. Originally, NuttX used
file descriptor ``1`` for SYSLOG output by default. For most task groups,
file descriptor ``1`` (``stdout``) mapped to ``/dev/console`` and this
solution worked well in most cases.
But using file descriptor ``1`` caused some very strange results when
``stdout`` was redirected such as when a USB or a Telnet console was used.
In those cases, SYSLOG output would go to different places depending upon
how stdout was re-directed within a given task group.
This was fixed using ``file_detach()``. In the default case, the SYSLOG
initialization logic now opens ``/dev/console`` then calls ``file_detach()``
to disassociate the open file instance from any task group.
Now, SYSLOG output consistently goes to ``/dev/console`` regardless of how
file descriptor ``1`` may be re-directed when SYSLOG output is generated.
Other Examples
==============
There are some other examples in analog joystick lower half drivers
that use the ADC character driver to read joystick positions::
boards/arm/stm32/nucleo-f4x1re/src/stm32_ajoystick.c:
ret = file_detach(fd, &g_adcfile);
boards/arm/stm32l4/nucleo-l476rg/src/stm32_ajoystick.c:
ret = file_detach(fd, &g_adcfile);
boards/arm/sama5/sama5d3-xplained/src/sam_ajoystick.c:
ret = file_detach(fd, &g_adcfile);
Socket Descriptors
==================
There is a similar story for socket descriptors but this is probably
not the place for that whole story. Instead, we will just summarize
that story here. First, let's compare file and socket descriptors here.
Socket descriptors are similar to file descriptors in that:
* They are a numeric index into a task-private array of structures.
For the case of socket descriptors, this is an array of type struct socket.
* Like the file descriptor, the socket descriptor is simply a portable way
to reference the underlying socket structure.
* And also like the file descriptor, the socket descriptor is meaningful
only in the context of task group where the socket descriptor was created.
As in the case for file descriptors, there are also a set of socket interfaces
that accept a reference to struct socket as an input parameter.
For each standard socket interface that accepts a socket descriptor,
there is a corresponding non-standard, OS-internal interface that accepts
a reference to struct socket as as an input parameters. These low-level socket
interfaces all have the prefix ``psock_`` and are all prototyped in the file
``include/nuttx/net/net.h``. They are not discussed further here.
These low-level socket interfaces are also independent of task groups
and may be used without regard to the thread that created the socket.
The primary usage difference is in how the struct socket instances are created:
* In the case of struct file, there is no special ``file_open()`` interface
to create the detached file reference. Instead, a two step procedure is used:
First the file is opened the standard ``open()`` function to obtain the
file descriptor, then the struct file is detached from the file descriptor
using ``file_detach()``.
* The struct socket instance, on the other hand, is created in the detached
state directly using ``psock_socket()``.
You can find a good example of a character driver that contains
a task-independent socket interface in the Telnet character driver at
``driver/net/telnet.c``. That driver is used to encapsulate a Telnet
session and to provide Telnet ``stdin``, ``stdout``, and ``stderr``
for remote NSH sessions.
+144
View File
@@ -0,0 +1,144 @@
.. _hardfaults:
==========
Hardfaults
==========
ARMv7
=====
Cortex-M3 and Cortex-M4
-----------------------
The most popular CPUs in current MCU designs are the Cortex-M3 (ARMv7-M) and
the Cortex-M4 (ARMv7E-M). Handling of these two architectures is almost
identical in NuttX (unless hardware floating point is enabled).
SVCALL
------
NuttX uses the SVCALL software interrupt in order to perform certain steps
in the context switching for the Cortex-M3 and Cortext-M4.
This sequence of logic appears in several places:
* Create a short critical section by disabling exceptions,
* Perform some set-up,
* Initiate the software exception / SVCALL, and
* When the software exception processing returns, re-enable exceptions.
.. note:: There is a technical difference between interrupts and exceptions.
In this section the term exception will be used. It is probably
more accurate since interrupts are really exceptions that result
from device interrupt lines. Furthermore, when we refer to
exceptions in this section, we are referring specifically to
ARMv7-M configurable exceptions.
Disabling Interrupts via the PRIMASK register
---------------------------------------------
The ARMv7-M architecture supports a register called the ``PRIMASK`` register.
The ``PRIMASK`` register contains a single valid bit. If that bit is set to
one, then exceptions are disabled. If that bit is zero, exceptions are enabled.
More correctly, when this bit is set to one, it prevents the activation of all
exceptions with configurable priority.
The original NuttX implementation used this ``PRIMASK`` register to enable
and disable exceptions. Things now get interesting in the sequence of logic
listed above because the ``PRIMASK`` bit also disables the SVCALL exception!
So, instead of taking the ``SVCALL`` exception vector, the Cortex-M3/4
generates a hardfault exception (see ARM.com's discussion of
`Activation Levels <https://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337g/Chdbdfjf.html>`_).
These hardfaults are not really a problem. The design of the NuttX hardfault
handler expects these exceptions and does the right thing.
However, the occurrence of hardfaults may come as a surprise to many people
and especially to some debuggers.
Hardfaults and Debuggers
------------------------
These hardfaults only become a technical issue when dealing with a debugger.
What does the debugger do when the hardfault occurs?
We need to back off and think about some system philosophy here.
The deep philosophical question here is: Who is in charge of system integrity?
The debugger or the RTOS?
If you are running a primitive NoOS program (like the famous blinky test
program), then you are running a barebones system and you need
all of the help you can get. So having the debugger make decisions about what
is the proper behavior of the blinky program and what is not is a good
thing for you.
But if you are using an advanced RTOS, then the RTOS will want to take
responsibility of the health of your system and now there is the possibility
of inconsistencies between the decisions that the RTOS makes and the decisions
that your debugger makes this hardfault handling is a perfect example here.
In NuttX, the hardfaults are controlled by the RTOS, but some debuggers will
break when the hardfault occurs and make debugging impossible.
Some people might take issue with this. Breaking on hardfault can make
debugging easier, since the break happens in the throwing context and you have
a hope of obtaining a backtrace and debugging the throwing side callstack.
However, I would suggest that putting a break point on ``up_assert()`` would
accomplish the same thing without being so intrusive.
How did the debugger know that the hardfault occurred? It knew because of
settings in the "ARM's Debug Exception and Monitor Control Register" or
``DEMCR``. Proper settings of the ``DEMCR`` register will allow debugger
to get break exceptions when a hardfault occrs.
So one workaround is to just reconfigure the ``DEMCR`` register so that break
exceptions are no longer generated when hardfaults occur.
Here is an example of such logic for the LPC43xx MCU.
Decoupling the hardfault from the break exception in this way does not work
with all debuggers, however. Presumably because some debuggers re-enable break
exceptions on hardfaults.
Disabling Interrupts via the BASEPRI register
---------------------------------------------
The ARMv7-M architecture supports another way to disable exceptions using
a register called the ``BASEPRI`` register. ARMv7-M exceptions are prioritized.
Each exception can be assigned an 8-bit priority. If the ``BASEPRI`` register
is set to a non-zero priority value, then it will filter exceptions in this sense:
* Exceptions with priority lower than or equal to the ``BASEPRI`` register
will be disabled.
* Exceptions with priority higher than the ``BASEPRI`` register will still
be enabled.
Normally this interrupt prioritization is used to support nested interrupt
handling, but it can also be used for disabling of all exceptions
if configured properly.
.. note:: In the ARMv7-M, higher values correspond to lower priority.
This can be really confusing!
NuttX supports a configuration option called ``CONFIG_ARMV7M_USEBASEPRI``.
If this option is selected, then the exception prioritization and control
logic will be configured to use the ``BASEPRI`` register instead of the
``PRIMASK`` register to disable exceptions.
This configuration includes the following changes in the behavior:
* Normal interrupts and exceptions are restricted to the range
``{ lowest priority ... (highest priority - 1) }``.
* The priority of the ``SVCALL`` exception is set to highest priority.
* When exceptions are enabled, the ``BASEPRI`` register is set to zero,
enabling exceptions of all priorities.
* When exceptions are disabled, the ``BASEPRI`` register is set to
``(highest priority - 1)``, disabling all exceptions except for the
``SVCALL`` exception.
In this way, the ``SVCALL`` exception remains enabled when exceptions
are disabled and no hardfault occurs.
.. note:: The above is inaccurate on several counts. It was simplified
to make the discussion sane. Not only do higher values correspond
to lower priorities, but the increment between consecutive 8-bit
priority values is probably not one. The supported maximum and
minimum priority values (as well as the step in each priority value)
may be different for each MCU. To handle this, these values are
exported in NuttX for each ARMv7-M architecture by header files at
``nuttx/arch/arm/include/<chip>/chip.h``.
+31 -7
View File
@@ -6,12 +6,36 @@ Implementation Details
:maxdepth: 2
:caption: Contents:
make_build_system.rst
drivers_design.rst
device_drivers.rst
processes_vs_tasks.rst
critical_sections.rst
interrupt_controls.rst
preemption_latency.rst
bottomhalf_interrupt.rst
cancellation_points.rst
chip_h.rst
context_switches.rst
critical_sections.rst
device_drivers.rst
device_nodes.rst
drivers_design.rst
file_descriptors.rst
hardfaults.rst
interrupt_controls.rst
ioctl.rst
kernel_modules_vs_shared_libraries.rst
kernel_threads_vs_pthreads.rst
make_build_system.rst
memory_configurations.rst
naming_arch_mcu_board_interfaces.rst
naming_os_internals.rst
nuttx_initialization_sequence.rst
nuttx_tasking.rst
oneshot_timers_and_cpu_load.rst
power_management.rst
preemption_latency.rst
processes_vs_tasks.rst
short_time_delays.rst
signal_handlers.rst
simulation.rst
smp.rst
syslog.rst
tasks_vs_threads.rst
tickless_os.rst
tls.rst
usb.rst
+137
View File
@@ -0,0 +1,137 @@
.. _ioctl:
=====
ioctl
=====
Not a Typewriter
================
"In computing, **Not a typewriter** or ``ENOTTY`` is an error code defined
in the ``errno.h`` found on many Unix systems.
This code is now used to indicate that
an invalid ``ioctl`` (input/output control) number was specified
in an ``ioctl`` system call."
--Source: https://en.wikipedia.org/wiki/Not_a_typewriter.
NuttX
=====
In ``ioctl()`` implementations in NuttX, ``-ENOTTY`` is always returned
if the ``ioctl()`` command is not recognized. You will often see driver
``ioctl()`` implement ions with a general structure similar to the following:
.. code:: c
int driver_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
int ret;
switch (cmd)
{
...
default:
ret = -ENOTTY;
break;
}
}
return ret;
}
Note that ``-ENOTTY`` is returned internally in NuttX.
This will subsequently be used to set the errno value to ``ENOTTY``
and to ``return -1`` to indicate the error condition.
ERRORS
------
These are the return values from a Linux ``ioctl()`` call:
* ``EBADF`` fd is not a valid file descriptor.
* ``EFAULT`` argp references an inaccessible memory area.
* ``EINVAL`` request or argp is not valid.
* ``ENOTTY`` fd is not associated with a character special device.
* ``ENOTTY`` The specified request does not apply to the kind of object
that the file descriptor fd references.
Reference: https://www.man7.org/linux/man-pages/man2/ioctl.2.html.
Linux Explanation
=================
On Jun 27 Linus Torvalds wrote:
"The correct error code for "I don't understand this ioctl" is ENOTTY.
The naming may be odd, but you should think of that error value as a
"unrecognized ioctl number, you're feeding me random numbers that I
don't understand and I assume for historical reasons that you tried to
do some tty operation on me".
...
The EINVAL thing goes way back, and is a disaster. It predates Linux
itself, as far as I can tell. You'll find lots of man-pages that have
this line in it:
EINVAL Request or argp is not valid.
and it shows up in POSIX etc. And sadly, it generally shows up
_before_ the line that says
ENOTTY The specified request does not apply to the kind of object
that the descriptor d references.
so a lot of people get to the EINVAL, and never even notice the ENOTTY.
(..)
At least glibc (and hopefully other C libraries) use a _string_ that
makes much more sense: strerror(ENOTTY) is "Inappropriate ioctl for
device"."
--Source: https://lore.kernel.org/patchwork/patch/258361.
How is this useful?
Knowing that no error occurred but the ``ioctl()`` command was not recognized
is a useful piece of information.
Suppose, for example, I have nfds open character drivers in an array ``fd[]``.
Then I could do something like this:
.. code:: c
int do_command(FAR int *fd, int nfds, int cmd)
{
int ret;
int i;
/* Try all file descriptors */
for (i = 0; i < nfds; i++)
{
ret = ioctl(fd[i], cmd, 0ul); /* No argument in this example */
if (ret < 0)
{
int errcode = errno;
/* Try the next file descriptor if this one return ENOTTY */
if (errcode != ENOTTY)
{
/* Other errors, including EINVAL, are fatal */
return -errcode
}
}
else if (ret >= 0)
{
return OK; /* Success! */
}
}
return -ENOENT;
}
@@ -0,0 +1,176 @@
.. _kernel-modules:
==================================
Kernel Modules vs Shared Libraries
==================================
Kernel Modules
==============
NuttX has had support for Kernel Modules for some time.
A kernel module allows you to extend the functionality of the OS
at runtime by installing ELF modules into the kernel.
A kernel module might be used, to example, to load device drivers
into RAM at runtime.
Here are some general properties of kernel modules:
* The first kernel module is loaded into the kernel address space
by ``insmod()`` using only a symbol table exported by the OS.
* Kernel modules can also (optionally) export a symbol table.
Such symbol tables are remembered and will be used by ``insmod()``
to resolved undefined symbols in subsequently loaded modules.
* This requires dependency checking logic: A module that imports symbols
from another module must be added after the modules that it depends upon.
And a module exports a symbol may not be removed while there are such
inter-module dependencies in place.
A module that imports symbols from another module must be removed before
the module that exports the symbol can be removed.
* There is a (non-standard) OS interface ``modsym()`` that will allow kernel
logic to look up symbols within a kernel module.
* Handles are used at the kernel module interfaces: ``insmod()`` returns
a handle to the module data structure. That handle can subsequently be used
to retrieve symbols with ``modsym()``.
* ``rmmod()`` uses the handle to remove the module.
* ``modhandle()`` is also available for backward compatibility:
Given the assigned module name, you can use this to retrieve
the module handle at any time.
* There is a test case at ``nuttx-apps/examples/module``.
In the FLAT build, ELF kernel modules are simply loaded into RAM and linked
with the base firmware. But things get a little more complex with PROTECTED
and KERNEL builds.
In those case, there are separate address spaces for the kernel
and for applications.
Kernel modules are only loaded in the kernel address space and, hence,
are not available to applications.
Shared Libraries
================
A shared library is another software module with these properties:
The ``.text`` address space is accessible to all applications.
The ``.data`` and ``.bss`` address space is in the same address space
as the application that uses the shared library.
So, they are shared in the sense that the ``.text`` is shared.
FLAT Build
==========
In the FLAT build environment, there is only one address space.
So what is the difference between a kernel module and a shared library
in this case? Certainly a kernel module meets all of the requirements
of a shared library in that environment.
In this case kernel modules really only differ from shared
libraries in their usage semantics:
For the FLAT build, I have added the standard ``include/dllfcn.h``
and have implemented the FLAT shared library support as a thin wrapper
around the kernel module support:
* ``dlopen()`` maps to ``insmod()``.
* ``dlclose()`` maps to ``rmmod()``.
* ``dlsym()`` maps to ``modsym()``.
* ``dlerror()`` is only a stub at the present time.
There is a shared library test case at ``nuttx-apps/examples/sotest``.
PROTECTED Build
===============
The PROTECTED build is equivalent to the FLAT build except that there are
two address spaces: The kernel address space and the user address space.
But all applications still share the same user address spaces.
As a result ``.text`` along with ``.data`` and ``.bss`` are naturally shared.
This requires using two copies of the the module logic: One residing in kernel
address space and using the kernel symbol table and one residing in user space
using the user space symbol table.
The first provides only kernel module support; the second only PROTECTED mode
shared library support.
This is accomplished by breaking the kernel module logic in two components
with OS kernel module interfaces in sched/module but with a sharable module
library at libc/modlib.
The shared library functions no longer call the kernel module logic but rather
implement their one top-level management logic using the lower-level routines
in the module library.
Better FLAT and PROTECTED Mode Shared Libraries
===============================================
A better implementation of shared libraries in the FLAT and PROTECTED builds
would, however, have a separate copy of the ``.bss`` and ``.data`` region
for each NuttX task group.
A task group is the moral equivalent of a Unix process.
That is how a shared library would have to work in uClinux, for example.
But that would be a substantial effort! For example, since each
``.bss``/``.data`` would lie at a different physical addres,
the ``.text`` section logic would need support
Position-Independent-Data (PID).
Embedded PID support, however, is pretty much broken on all current GCC
implementations. See NxFlat Compatibility Problem.
Perhaps the xFLAT work that I did for uClinux shared libraries could be
ported to Nuttx: `xFLAT Web Page <https://sourceforge.net/projects/xflat/>`_.
For more information about task groups see :ref:`nuttx-tasking`
or :ref:`tasks-vs-threads`.
The current implementation also assumes that all firmware resides in base
FLASH and hence is fully linked prior to loading any modules or shared
libraries. There is no support for loading programs into RAM and binding
them to symbols exported by modules or shared libraries.
This extension would, however, not be so difficult for the FLAT build;
it would be a simple matter of integrating the exported module symbol
tables into the symbol lookup.
That is already done in ``sched/module`` files, but not yet in the
very similar ``binfmt/libelf`` files.
In the PROTECTED build, this would require some special start-up logic
in the user address space as the initial steps of the newly started task.
Some kind of dynamic loader, such as ``ld.so``, would have to integrate
with ``crt0`` logic to automatically bind user space tasks to shared libraries
as they are loaded into and memory before the programs ``main()`` function
is called.
KERNEL Build
============
The KERNEL build, however, is a completely different creature.
In that build, the kernel and each process has its own adress space.
This means that a shared library in the kernel build has to be considerably
more complex: In order to be shared, the ``.text`` portion of the module
(1) must lie in a single shared memory region accessible from all processes
and (2) built for Position-Independent-Code (PIC) operation since
it must execute from an arbitrarily different virtual address in each process
address space.
The ``.data``/``.bss`` portion of the module must be allocated
in the user address space of each process, but either:
1. ``.data``/``.bss`` section must lie at a consistent virtual address
so that it can be referenced from the one copy of the ``.text`` in
the shared memory region, OR
2. The ``.text`` section logic must support Position-Independent-Data (PID).
The latter approach provides for a simpler build, but embedded PID support
is pretty much broken on all current GCC implementations.
See :ref:`nxflat` Compatibility Problem.
Some kind of dynamic loader, such as ``ld.so``, would have to integrate with
``crt0`` logic to automatically bind processes to shared libraries as they
are loaded into memory before the programs ``main()`` logic is called.
.. note:: There is not yet any shared library support in the KERNEL build mode.
This would be quite a large effort and not on the plan of record
at the present time.
@@ -0,0 +1,191 @@
.. _kernel-threads-vs-pthreads:
===========================
Kernel Threads vs. Pthreads
===========================
Why Can't Kernel Threads Have pthreads?
=======================================
Kernel threads are special "tasks" that reside within the OS.
They are similar to application tasks, so why can't they have pthreads
like application tasks?
The FLAT build
==============
In the FLAT build, kernel threads are, in fact, identical to application
threads except that:
1. They follow some slightly different syntax, AND
2. Can only use OS internal interfaces, never application interfaces.
Since they are otherwise identical, there is really no technical reason
why pthreads could not be supported.
The real reasons in this case is:
1. It is inappropriate, AND
2. It is incompatible with the PROTECTED and KERNEL builds where pthreads
cannot be supported.
Why inappropriate? Because..
pthread Interfaces Are User Interfaces
--------------------------------------
In all Unix systems, pthread interface support is not provided
by the operating system but rather by the user-facing C library.
The role of the OS is only to provide some low level hooks needed by
C library implementation of pthread interfaces.
But all of the implementation is in the C library and only for use
by applications.
That is not the current situation in NuttX.
Currently pthread support is deeply entangled in the OS.
This is problem that must be fixed someday and must not exploited as some
permanent feature of the OS. It is not.
It will go away some day when NuttX becomes a mature Unix-family system.
As user facing interfaces, pthread interfaces include some behaviors that
are not appropriate within the OS.
Inappropriate behaviors include modification of the ``errno`` value and
cancellation points. Those are user-only features.
While pthread interfaces do not, in general, modify the errno setting
they do create cancellation points which is not desirable within the OS.
PROTECTED and KERNEL Builds
---------------------------
The PROTECTED and KERNEL builds differ from the FLAT build in that they
segregate the memory into two regions:
A privileged kernel address space and unprivileged, user address space(s).
The primary difference is that the PROTECTED build uses a physical address
space and different regions of the physical address space have different
properties. This is usually accomplished using a Memory Protection Unit (MPU).
There is one protected kernel address space and one unprotected
user address space.
The KERNEL build, on the other hand, uses a Memory Management Unit (MMU)
to create a virtual address space in which there is still a separate
protected kernel address space, but many user address spaces for user
programs. These are usually called processes.
This is the familiar build model that you find with high-end Unix-like
systems such as Linux.
See Memory Configurations for additional information.
Address Spaces, Memory Allocators, and User Mode
------------------------------------------------
What would happen if you tried to create a pthread in the PROTECTED or KERNEL
build? The effect of these address space differences become very pronounced.
pthreads are, by their very definition, user-space threads.
That means that the OS will attempt to create a user-space environment
for the pthread: The thread's stack and other resources.
And when the pthread is started, it will be started in user mode,
not kernel mode.
It would require a significant change to the OS to alter that behavior
and that is not under consideration (because the change is also inappropriate).
Entry points
------------
Because the application space and the kernel space are separately built
and separately linked in the PROTECTED and KERNEL builds.
No application addresses are known by the kernel and no kernel addresses
are known by the application (applications interface via system call traps,
not C function calls). So what address would the kernel thread provide
for the pthread entry point? A known kernel space address?
Of course, the system would crash with a memory fault immediately
if a protected address were executed in user mode.
Mutexes
-------
Okay, so there are no kernel pthreads. But could other pthread resources
be usable in the kernel. The short answer is no.
Consider mutexes, for example. By definition, a mutex can only be used within
threads of the current process (or task groups as they are often called
in NuttX). They have no meaning outside of the task group.
Since the kernel thread "task group" can have no pthreads,
how could these mutexes be used under the proper standard definition
of what a mutex must be?
This is true of all pthread interfaces: None of the pthread interfaces were
intended from inter-process communications; only for inter-pthread
communications within the same process (task group).
Roadmap
=======
As alluded to before about the appropriateness of pthread in the OS.
There is a roadmap for these kinds of features.
That roadmap is to continue to conform strictly to the standard OS definitions
of <Opengroup.org>_ and to continue to evolve as a fully compliant,
very standard, tiny Unix-like operating system.
1. Part of this is getting all user interfaces out of the OS.
2. Another part is migrating all pthread support out of operating system
and into the user-facing C library.
3. An additional objective on the roadmap is to streamline the kernel threads
and to disconnect them from task groups.
Kernel threads should not be part of any task group.
In regard to this last point, the following is taken from Apache NuttX
Issue 1108: "Remove streams from Kernel Threads".
No Streams in Kernel Threads
----------------------------
Kernel threads are not permitted to use the C library buffer I/O, "stream",
interfaces. Those are interfaces like ``fopen()``, ``fread()``, ``fwrite()``,
``fclose()``, etc.
Those are strictly for use by user applications.
This is because these functions modify errno variables and create
cancellation points and perhaps other things that are undesirable
within the OS.
The thread create logic is largely the same for both kernel threads
and for application threads: They both allocate a large buffer
from the user memory pool for the stream ``FILE`` array.
Since kernel threads this is a waste of memory since the kernel threads
should not be using streams.
Remove Kernel Thread Stream Allocation
--------------------------------------
The proposal is to:
1. Verify that there is no use of streams within the OS,
2. Remove the stream allocation for kernel threads,
3. Assure that there are proper checks in place so that there are
no uses of the ``NULL`` stream array pointer.
For the most part, the OS is clean, there are essentially no use
of streams within the OS.
There are, however, a few violations of this that will need to be fixed;
``fopen()`` is called in some of the ``lc3850`` code.
Remove the File Descriptor Array Too
------------------------------------
A second phase would be to remove the file descriptor array from kernel
threads. File descriptors are, again, only for use by applications;
within the OS, file access is done using the struct file (aka, ``FILE``),
structure directly.
However, I suspect that there are many hidden uses of file descriptors
in the system.
For one, ``file_open()`` which opens the detached file, is not fully
implemented; it cheats and uses file descriptors.
So let's consider removal of the file descriptor allocation
as a second step after the stream allocations have been removed.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
=================================================
Naming of Architecture, MCU, and Board Interfaces
=================================================
What's the meaning of the prefix ``up_`` in NuttX?
Which functions should be prefixed with ``up_``?
``up_`` is supposed to stand for **microprocessor**;
the `u` is like the Greek letter micron: µ. So it would be **µP** which
is a common shortening of the word microprocessor.
I don't like that name very much. I wish I would have used ``arch_`` instead.
But now I think I am stuck with ``up_``.
Common Microcontroller Interfaces
=================================
Any interface that is common to all microcontroller and is used throughout
the system should be prefixed with ``up_`` and prototyped in
``include/nuttx/arch.h``. The definitions in that header file provide the
common interface between NuttX and the architecture-specific implementation
in ``arch/``.
Microcontroller-Specific Interfaces
===================================
An interface which is unique to a certain microcontroller should be prefixed
with the name of the microcontroller, for example ``stm32_``,
and be prototyped in some header file in the ``arch/`` directories.
These are interfaces used only by logic within the ``arch/`` and ``boards/``
directories.
Architecture-Specific Interfaces
================================
An interface which is unique to a CPU architecture, but shared among
microcontrollers that implement that CPU architecture should be prefixed
with the name of architecture. For example, the architecture-specific
interfaces used by STM32 would begin with ``arm_``.
These are interfaces used only by logic within the ``arch/`` and ``boards/``
directories.
There is also a ``arch/<architecture>/include/<chip>/chip.h`` header file
that can be used to communicate other microprocessor-specific information
between the board logic and even application logic.
Application logic may, for example, need to know specific capabilities
of the chip. Prototypes in that ``chip.h`` header file should follow
the microprocessor-specific naming convention.
Common Board Interfaces
=======================
Any interface that is common to all boards should be prefixed with ``board_``
and should also be prototyped in ``include/nuttx/arch.h``.
These ``board_`` definitions provide the interface between the board-level
logic and the architecture-specific logic.
There is also a ``boards/<arch>/<chip>/<board>/include/board.h`` header file
that can be used to communicate other board-specific information between
the architecture logic and even application logic.
Any definitions which are common between a single architecture and several
boards should go in this ``board.h`` header file;
``include/nuttx/arch.h`` is reserved for board-related definitions common
to all architectures.
Specific Interfaces
===================
Any interface which is unique to a board should be prefixed with the board
name, for example ``stm32f4discovery_``. Sometimes the board name is too long
so ``stm32_`` would be okay too. These should be prototyped in
``boards/<arch>/<chip>/<board>/src/<board>.h`` and should not be used outside
of that directory since board-specific definitions have no meaning outside
of the board directory.
@@ -0,0 +1,181 @@
===============================
Naming of OS Internal Functions
===============================
.. note:: Most of the naming examples used in this section no longer exist.
They have been converted to the naming conventions described
in this section. While they are good examples, they no longer
reflect the current state of the code base.
Legacy smooshed-together names
==============================
There has been some controvery lately about the consistency of naming of OS
internal functions. We should come to a consensus on the desired naming
of conventions for internal OS functions before we start arbitrarily
changing names.
NuttX function names are all lower case.
That is required by the naming standard.
Historically, has used a subsystem name, an underscore, then all of the
renaming words smooshed together. For example:
.. code-block:: c
void sched_mergeprioritized(FAR dq_queue_t *list1, FAR dq_queue_t *list2,
uint8_t task_state);
I would represent that form as::
<subsystem>_<everythingelsesmooshedaltogether>
Many of these names are impossible to read unless you stop and parse
the letters to find the word boundaries.
System-Object-Verb Naming
=========================
Most recent naming has broken out one more of the smooshed-together-words
and separated them with undersore characters like:
.. code-block:: c
unsigned int sched_timer_cancel(void);
void sched_timer_resume(void);
void sched_timer_reassess(void);
Which I would describe as::
<subsystem>_<object>_<verb>
System-Verb-Object Naming
=========================
Another form, which we have been using lately, is to switch the
``<object>`` and the ``<verb>`` like::
<subsystem>_<verb>_<object>
For example:
.. code-block:: c
int sched_get_stackinfo(pid_t pid, FAR struct stackinfo_s *stackinfo);
By the ``<subsystem>_<object>_<verb>`` naming this would have been:
.. code-block:: c
int sched_stackinfo_get(pid_t pid, FAR struct stackinfo_s *stackinfo);
And the timer interfaces would become the following under the
``<subsystem>_<verb>_<object>`` rule:
.. code-block:: c
unsigned int sched_cancel_timerl(void);
void sched_resume_timer(void);
void sched_reassess_timer(void);
And the "smooshed" name ``sched_mergeprioritized()`` would become:
.. code-block:: c
void sched_merge_prioritized(FAR dq_queue_t *list1, FAR dq_queue_t *list2,
uint8_t task_state);
We won't even consider the ``<subsystem>_<everythingelsesmooshedaltogether>``.
It has a history but is pretty much odious.
We should not consider, for example, the smooshed-together form:
.. code-block:: c
int sched_getstackinfo(pid_t pid, FAR struct stackinfo_s *stackinfo);
What an ugly mess! :-)
Unit Suffixes
=============
Often, you will have the functions that return the same value,
but in different units.
For example, these two functions return the system time:
.. code-block:: c
clock_t clock_systimer(void);
int clock_systimespec(FAR struct timespec *ts);
The first returns the time in system clock ticks.
The second returns the time as a struct timespec.
I have seen a naming practice The keeps the naming of the functions the same,
but appends the units, preceded by an underscore character, at the end
of the function name. For example, these could be:
.. code-block:: c
clock_t clock_systime_ticks(void);
int clock_systime_timespec(FAR struct timespec *ts);
Or should they include a ``<verb>``:
.. code-block:: c
clock_t clock_get_systime_ticks(void);
int clock_get_systime_timespec(FAR struct timespec *ts);
That form would then be::
<subsystem>_<verb>_<object>[_<units>]
Where the square bracket represent and optional unit suffix.
Implicit get
============
In many cases, I think that the ``_get_`` is implicit and need not appear
in the function name. But if there are multiple operations
on the ``<object>``, then the ``_get_`` would be necessary.
Hence, I think the naming:
.. code-block:: c
clock_t clock_systime_ticks(void);
int clock_systime_timespec(FAR struct timespec *ts);
is perfectly accepable without the ``_get_``.
And since ``_get_`` is the only operation on ``stackinfo``,
the following would be a perfectly acceptable renaming:
.. code-block:: c
int sched_stackinfo(pid_t pid, FAR struct stackinfo_s *stackinfo);
Other Naming
============
There is other naming that uses long names with many underscore characters
separating each word in the long name. We are not a fan of long names.
Conclusion
==========
Our preference would be the ``<subsystem>_<verb>_<object>`` form.
We have been using this form in creating all new internal interfaces.
Does anyone else have a thought? or a preference?
We should come to an understanding and stop the arbitrary name changes.
We hope that we can put an end to the smooshed-together naming
(except wheree required) in any case.
.. note:: This applies only to the internal naming of functions within the OS.
Other names are imposed on us by standards for application
interfaces to the OS which may follow other rules.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,380 @@
.. _nuttx-tasking:
=============
NuttX Tasking
=============
An RTOS as a library
====================
What is an RTOS? NuttX, as with all RTOSs, is a collection of various features
bundled as a library. It does not execute except when either:
1. The application calls into the NuttX library code, OR
2. An interrupt occurs.
There is no meaningful way to represent an architecture that is implemented
as a library of user managed functions with a diagram.
You can however, pick any subsystem of an RTOS and represent
that in some fashion.
Kernel Threads
==============
There are some RTOS functions that are implemented by internal threads,
for instance :ref:`kernel-threads-vs-pthreads`, :ref:`tasks-vs-threads`,
:ref:`kernel-modules`.
.. todo:: Provide more content here :-)
The Scheduler
=============
Schedulers and Operating Systems
--------------------------------
An operating system is a complete environment for developing applications.
One important component of an operating system is the scheduler.
That logic that controls when tasks or threads execute.
Actually, more than that; the scheduler really determines what a task
or a thread is! Most tiny operating systems are really not operating
“systems” in the sense of providing a complete operating environment.
Rather these tiny operating systems consist really only of a scheduler.
That is how important the scheduler is.
Task Control Block (TCB)
------------------------
In NuttX a thread is any controllable sequence of instruction execution
that has its own stack.
Each task is represented by a data structure called a task control block
or TCB. That data structure is defined in the header file
``include/nuttx/sched.h``.
Task Lists
----------
These TCBs are retained in lists. The state of a task is indicated both
by the ``task_state`` field of the TCB and by a series of task lists.
Although it is not always necessary, most of these lists are prioritized
so that common list handling logic can be used (only the ``g_readytorun``,
the ``g_pendingtasks``, and the ``g_waitingforsemaphore`` lists
need to be prioritized).
All new tasks start in an initial, non-running state:
.. code-block:: c
volatile dq_queue_t g_inactivetasks;
* This is the list of all tasks that have been initialized, but not yet
activated. NOTE: This is the only list that is not prioritized.
* When the task is initialized, it is moved to a ready-to-run list.
There are two lists representing ready-to-run threads and several
lists representing blocked threads. Here are the ready-to-run threads:
.. code-block:: c
volatile dq_queue_t g_readytorun;
* This is the list of all tasks that are ready to run.
The head of this list is the currently active task;
the tail of this list is always the idle task.
.. code-block:: c
volatile dq_queue_t g_pendingtasks;
* This is the list of all tasks that are ready-to-run, but cannot be placed
in the ``g_readytorun`` list because:
1. They are higher priority than the currently active task at the head
of the ``g_readytorun`` list, AND
2. the currently active task has disabled pre-emption.
These tasks will stay in this holding list until pre-emption is again
enabled (or until the currently active task voluntarily relinquishes
the CPU).
* Tasks in the ``g_readytorun`` list may become blocked.
In this cased, their TCB will be moved to one of the blocked lists.
When the block task is ready-to-run, its TCB will be moved back to either
the ``g_readytorun`` to ``the g_pendingtasks`` lists, depending up
if pre-emption is disabled and upon the priority of the tasks.
Here are the blocked task lists:
.. code-block:: c
volatile dq_queue_t g_waitingforsemaphore;
* This is the list of all tasks that are blocked waiting for a semaphore.
.. code-block:: c
volatile dq_queue_t g_waitingforsignal;
* This is the list of all tasks that are blocked waiting for a signal
(only if signal support has not been disabled).
.. code-block:: c
volatile dq_queue_t g_waitingformqnotempty;
* This is the list of all tasks that are blocked waiting for a message queue
to become non-empty (only if message queue support has not been disabled).
.. code-block:: c
volatile dq_queue_t g_waitingformqnotfull;
* This is the list of all tasks that are blocked waiting for a message queue
to become non-full (only if message queue support has not been disabled).
.. code-block:: c
volatile dq_queue_t g_waitingforfill;
* This is the list of all tasks that are blocking waiting for a page fill
(only if on-demand paging is selected).
Reference: ``nuttx/sched/sched/sched.h``.
State Transition Diagram
========================
The state of a thread can then be easily represented with this simple state
transition diagram.
.. todo:: Provide State Transition Diagram.
Scheduling Policies
===================
In order to be a real-time OS, an RTOS must support ``SCHED_FIFO``.
That is, strict priority scheduling. The thread with the highest priority
runs.. Period. The thread with the highest priority is always associated
with the TCB at the head of the ``g_readytorun`` list.
NuttX supports one additional real-time scheduling policy: ``SCHED_RR``.
The RR stands for **round-robin** and this is sometimes called
**round-robin scheduling**. In this case, NuttX supports timeslicing.
If a task with ``SCHED_RR`` scheduling policy is running, then when each
timeslice elapses, it will give up the CPU to the next task that is
at the same priority.
.. note::
1. If there is only one task at this priority, ``SCHED_RR`` and
``SCHED_FIFO`` are the same, AND
2. ``SCHED_FIFO`` tasks are never pre-empted in this way.
Task IDs
========
Each task is represented not only by a TCB but also by a numeric task ID.
Given a task ID, the RTOS can find the TCB.
Given a TCB, the RTOS can find the task ID.
So they are functionally equivalent.
Only the task ID, however, is exposed at the RTOS/application interfaces.
NuttX Tasks
===========
Processes vs. Threads
---------------------
In larger system OS such as BSD, Linux, or Windows you will often hear
the name process used to refer to threads managed by the OS.
A process is more than a thread as we have been discussing so far.
A process is a protected environment that hosts one or more threads.
By environment we mean the set of resources set aside by the OS but
in the case of the protected environment of the process we are specifically
referring its address space.
.. note::
In order to implement the process' address space, the CPU must support
a memory management unit (MMU).
**The MMU is used to enforce the protected process environment.**
However, NuttX was designed to support the more resource constrained,
lower-end, deeply embedded MCUs. Those MCUs seldom have an MMU and,
as a consequence, can never support processes as are supported by BSD, Linux,
or Windows.
.. important:: NuttX does not support processes.
NuttX will support an MMU but it will not use the MMU to support processes.
NuttX operates only in a flat address space.
NuttX will use the MMU to control the instruction and data caches and
to support protected memory regions.
This may change in future, but this is how things are right now.
NuttX Tasks and Task Resources
------------------------------
All RTOSs support the notion of a task. A task is the RTOS's moral equivalent
of a process. Like a process, a task is a thread with an environment
associated with it.
This environment is like environment of the process but does not include
a private address space.
This environment is private and unique to a task.
Each task has its own environment.
This task environment consists of a number of resources
(as represented in the TCB). Of interest in this discussion are the following.
Note that any of these task resources may be disabled in the NuttX
configuration to reduce the NuttX memory footprint:
1. **Environment Variables**. This is the collection of variable assignments
of the form: ``VARIABLE=VALUE``.
2. **File Descriptors**. A file descriptor is a task specific number
that represents an open resource (a file or a device driver, for example).
3. **Sockets**. A socket descriptor is like a file descriptor, but
the open resource in this case is a network socket.
4. **Streams**. Streams represent standard C buffered I/O.
Streams wrap file descriptors or sockets to provide a new set of interface
functions for dealing with the standard C I/O (like ``fprintf()``,
``fwrite()``, etc.).
In NuttX, a task is created using the interface ``task_create()``.
NuttX Task Exit Sequence
------------------------
.. figure:: task_exit_sequence.png
:alt: Task Exit Sequence diagram.
Task Exit Sequence diagram.
The Pseudo File System and Device Drivers
=========================================
A full discussion of the NuttX file system belongs elsewhere,
see :ref:`nuttx-filesystem` for more details.
But in order to talk about task resources, we also need to have
a little knowledge of the NuttX file system.
NuttX implements a Virtual Files System (VFS) that may be used to communicate
with a number of different entities via the standard ``open()``, ``close()``,
``read()``, ``write()``, etc, interfaces.
Like other VFSs, the NuttX VFS will support file system mount points,
files, directories, device drivers, etc.
Also, as with other VFSs, the NuttX file system will support
pseudo-file systems, that is, file systems that appear as normal media
but are really presented under programmatic control.
In Linux, for example, you have the ``/proc`` and the ``/sys``
psuedo-file systems.
There is no physical media underlying the pseudo-file system.
The NuttX root file system is always a psuedo-file system.
This is just the opposite from Linux. With Linux the root file system
must always be some physical block device (if only an initrd ram disk).
Then once you have mounted the physical root file system, you can mount
other file systems including Linux pseudo-filesystems like ``/proc``
or ``/sys``.
With NuttX, the root file system is always
a pseudo-file system that does not require any underlying block driver
or physical device.
Then you can mount real filesystem in the pseudo-filesystem.
This arrangement makes life much easier for the tiny embedded world (but also
has a few limitations — like where you can mount file systems).
**NuttX interacts with devices via device drivers** that is via software
that controls hardware and conforms to certain NuttX conventions
(see ``include/nuttx/fs/fs.h``). Device drivers are represented
by device nodes in the pseudo-file system.
By convention, these device nodes are created in the ``/dev`` directory.
Now that we have digressed a little to introduce the NuttX file system
and device nodes, we can return to our discussion of task resources.
``/dev/console`` and Standard Streams
-------------------------------------
There are three special cases of I/O: ``stdin``, ``stdout``, and ``stderr``.
These are type ``FILE*`` and correspond to file descriptors ``0``, ``1``,
and ``2`` respectively.
When the very first thread is created (called the IDLE thread),
the special device node ``/dev/console`` is opened. ``/dev/console`` provides
the ``stdin``, ``stdout``, and ``stderr`` for the initial task.
Inheritance of the Task Environment and I/O Redirection
=======================================================
When one task creates a new task, that new task inherits the task resources
of its parent. This includes all of the environment variables,
file descriptors, and sockets.
.. note::
Task resources inheritance can be limited by special options
in the NuttX configuration.
So, if nothing special is done, then every task will use ``/dev/console``
for the standard I/O. However, a task may close file descriptor
``0`` through ``2`` and open a new device for standard I/O.
Then any children tasks that are created will inherit that new re-directed
standard I/O as well.
This mechanism is used throughout NuttX.
For example in the THTTPD server to redirect socket I/O to standard I/O
for CGI tasks. In the Telnet server so that new tasks inherit the
Telnet session.
Tasks vs. Pthreads
==================
Systems like Linux also support POSIX pthreads.
In the Linux environment, the process is created with one thread running
in it. But by using interfaces like ``pthread_create()``, you can create
multiple threads that run and share the same process resources.
NuttX also supports POSIX pthreads and the NuttX pthreads also support
this behavior. That is, the NuttX POSIX pthreads also share the resources
of the parent task.
However, since NuttX does not support process address environments,
the difference is not so striking.
When a task creates a pthread, the newly create pthread will share
the environment variables, file descriptors, sockets, and streams
of the parent task.
.. note::
Task resources are reference counted and will persist
as long as a thread in the task group is still active.
See :ref:`tasks-vs-threads` for more details.
Process IDs / Task IDs / Pthread IDs
====================================
The term process ID is standard (usually abbreviated as pid) and used to
identify a task in NuttX. So, more technically, this number is a task ID
as was described above.
Pthreads are also described by a ``pthread_t`` ID.
In NuttX, the ``pthread_t`` ID is also the same task ID.
@@ -0,0 +1,126 @@
=======================================
Oneshot Timers and CPU Load Measurement
=======================================
Issues with CPU Load Measurement
================================
There is been support for CPU load measurement for a long time.
Enabled with:
.. code-block:: sh
CONFIG_SCHED_CPULOAD=y
CONFIG_SCHED_CPULOAD_TIMECONSTANT=2
The main problem with the existing logic is that it ran synchronously
with the system timer. That turns out to be useless for measurement of the
performance of some tasks.
Many tasks operate synchronously with the timer interrupt, i.e., timed event
is the stimulus for task execution.
Those tasks are always sampled at essentially the same point in the execution
profile leading to nonsense results.
In order to get believable results in all cases you would need to:
* Sample at times are completely random with respect to program behavior, AND
* Sample at a high rate or for a very long time.
External Clock
==============
In order to work around these things, an option to use an external clock
was added:
.. code-block:: sh
CONFIG_SCHED_CPULOAD_EXTCLK=y
CONFIG_SCHED_CPULOAD_TICKSPERSEC=77
This should work well if ``CONFIG_SCHED_CPULOAD_TICKSPERSEC`` is prime
and less than ``CONFIG_USEC_PER_TICK``.
However, you were completely on your own one how to implement this
external clock. But no longer.
Using a Oneshot Timer to Drive CPU Load Measurement
===================================================
Recently, a generic, platform-independent one-shot lower half interface
was developed. That interface is described in
``include/nuttx/timers/oneshot.h``. There are implementations of the one shot
timer lower half available for STM32, STM32L4, SAM4CM, SAMA5D3/4,
and SAMV71/SAME70.
And now there is also an OS component that can be initialized and used to
drive the CPU load sampling from a precision, high rate oneshot timer.
The implementation of that feature resides at
``sched/sched/sched_cpuload_oneshot.c`` and is enabled with:
.. code-block:: sh
CONFIG_ONESHOT=y
CONFIG_CPULOAD_ONESHOT=y
Perhaps with additional configuration options that are likely required
to enable board-specific oneshot timer lower-half support.
The oneshot timer must still be configured by board specific logic which
must call:
.. code-block:: c
void sched_oneshot_extclk(FAR struct oneshot_lowerhalf_s *lower);
To start the CPU load measurement.
``sched_oneshot_extclk()`` is prototyped in ``include/nuttx/clock.h``.
There is some example setup code in the NuttX simulation code at
``boards/sim/sim/sim/src/sim_bringup.c``.
Entropy
=======
Another recent addition to NuttX came from David Alessio.
David contributed support for ``/dev/urandom`` with a built-in XorShift128
pseduo-random number generator (PRNG).
We have detached the XorShift128 implementation from the ``/dev/urandom``
implementation and moved it to ``/libc/misc`` so that is it available
for other purposes.
In particular, there is an option to use the XorShift128 PRNG to add entropy
to the CPU load measurement of the oneshot timer.
That feature is enabled with:
.. code-block:: sh
CONFIG_CPULOAD_ONESHOT_ENTROPY=n
for ``n=1..30`` and disabled with ``CONFIG_CPULOAD_ONESHOT_ENTROPY=0``.
This value represents the number of bits of entropy that will be added
to the oneshot interval delays.
The oneshot timer will be set to the following interval each time
the oneshot timer is restarted is ``CONFIG_CPULOAD_ONESHOT_ENTROPY``:
.. code-block:: sh
CPULOAD_ONESHOT_NOMINAL \
- (CPULOAD_ONESHOT_ENTROPY / 2) \
+ error \
+ nrand(CPULOAD_ONESHOT_ENTROPY)
Where:
* ``CPULOAD_ONESHOT_NOMINAL`` is ``CONFIG_SCHED_CPULOAD_TICKSPERSEC``
in units of microseconds.
* ``CPULOAD_ONESHOT_ENTROPY`` is ``(1 << CONFIG_CPULOAD_ONESHOT_ENTROPY)``.
* ``error`` is an error value that is retained from interval to interval
so that although individual intervals are randomized,
the average will still be equal to ``CONFIG_SCHED_CPULOAD_TICKSPERSEC``.
If ``CONFIG_CPULOAD_ONESHOT_ENTROPY=0``, then the interval delay will
always be equal to ``CPULOAD_ONESHOT_NOMINAL``.
@@ -0,0 +1,221 @@
.. power-management:
================
Power Management
================
Power Management (PM) Subsystem
===============================
I would expect that the logic that performs clocking adjustments would utilize
the NuttX Power Management (PM) subsystem. That subsystem basically implements
a random walk. It collects and monitors system utilization information from
devices drivers. This utilization information comes from critical device
drivers via calls to::
void pm_activity(int domain, int priority);
This function is called by a device driver to indicate that it is performing
meaningful activities (non-idle). This increments an activity counter and will
prevent entering reduced power states.
For a system driver by a human interface, ``pm_activity()`` might be called
with touchscreen in puts and/or keypad inputs.
As long as the human is interacting with the device it remains non-idle.
When there is no further activity, the activity counter will decrease
and when it crosses a threshold, it will initiate a change to a lower power
usage state.
There are many actions that systems may take when the reduced power state
is entered. They may, for example, dim that backlight on a display or enable
MCU-specific reduced power consumptions modes. And for the purposes
of this discussion, they may also reduce the clocking to the CPU.
Alternative way to conserve power is using :ref:`tickless` with some
(dis)advantages you need to consider to know if it fits your needs.
.. _dynamic-clocking:
Dynamic Clocking
================
Most configurations are created with fixed clock configurations.
One strategy for power management is to use variable, dynamic clocking
where the CPU clock frequency is lowered during periods of inactivity.
Clock Update Function
---------------------
Variable clocking is not difficult to achieve but most ports are created
with fixed clock configuration using settings defined in the ``board.h``
header file. For example, Kinetis has::
void kinetis_clockconfig(void)
In order to make clocking variable a few things are needed:
* The initial board settings should still be used at power up,
but there needs to be a new clock initialization function,
perhaps ``kinetis_clock_update()`` that takes a structure containing
all of the clock settings.
* The existing ``kinetis_clockconfig()`` would need to be gutted,
most of the logic being moved to ``kinetis_clock_update()``.
``kinetis_clockconfig()`` would just create the structure using the constant
settings from the ``board.h`` header file then call
``kinetis_clock_update()`` to establish the initial clock configuration.
.. note:: Additional complexities may be associated with changing the clocking
other than just controlling dividers, PLLs, and clock sources. For example,
FLASH wait states. When increasing the clocking, the FLASH wait states must
be increased BEFORE reconfiguring the faster clocking.
But when reducing the clocking, FLASH wait states cannot be reduced until
AFTER reconfiguring the slower clocking.
Handling Driver Dependencies
----------------------------
The first part of the problem was modifying the clock configuration logic
to support variable configurations.
The second part of the problem is that now all of the devices that depend
on clocking such as the timer, the system timer, all MCU-specific timers,
UARTs, and perhaps other device need to be notified of the change
in clock frequency.
* The system timer, the SysTick in Cortex-M MCUs, needs to be notified
of the of the clock frequency change so that it can re-calculate
the system timer tick interrupt so that there is no disruption in timing.
* The same may be true of other MCU-specific timers. They may also need to
recalculate certain clocking. An MCU-specific timer may, for example,
be providing system time in Tickless mode.
* If serial devices are used, such as for a serial console, then
serial drivers must be notified of the clock change so that they can
recalculate their BAUD settings without lost of serial communications.
* Other devices such as SPI, I2C, I2S, SDIO, USB, etc. may also need to
be notified of the change in the clock configuration to behave correctly
across the clocking change.
Full functionality is not normally expected in low power consumption states.
So the most typical behavior for drivers in response to reduced power
consumption state changes is simply to shut themselves down (turn off
the peripheral, disable clocking to the peripheral, and other actions
as appropriate).
The above discussion only applies to peripherals that you expect
to continue normal operation in the reduced power state.
You may, as an example, want to keep timing accuracy throughout
the low power state or you may want to preserve serial debug output
in the reduced power state.
Driver PM Callback Functions
----------------------------
The NuttX PM subsystem provides the glue that integrates both parts
of the clock management problem.
The first part is managed by PM activity monitor and PM state changes.
The PM subsystem integrates the second part of the problem using PM driver
callback functions. The timing sensitive device drivers can be notified
of the change in clocking using the driver callbacks that are a port
of NuttX Power Management (PM) functionality.
Each timing sensitive device driver needs to register for a PM callback and,
on each callback, it must recalculate its frequency settings using the current
clock configuration.
To receive this callbacks, the driver logic needs to provide functions
with prototypes like:
.. code-block:: c
/* Power management callback function prototypes */
#ifdef CONFIG_PM
static void xyz_pm_notify(struct pm_callback_s *cb, int domain,
enum pm_state_e pmstate);
static int xyz_pm_prepare(struct pm_callback_s *cb, int domain,
enum pm_state_e pmstate);
#endif
Which are used to notify a callback vtable like:
.. code-block:: c
/* Power management callback function vtable */
#ifdef CONFIG_PM
static struct pm_callback_s g_xyz_pmcb =
{
.notify = xyz_pm_notify,
.prepare = xyz_pm_prepare,
};
#endif
The ``prepare()`` callback method gives the driver some advance notification
of the pending PM state change.
Some drivers may need to put the device in a safe state in the ``prepare()``
callback.
A serial driver, for example, may want to disable RX and TX so that garbage
is not generated or received during the clock change.
The ``notify()`` callback method signifies the actually state change
and it is in this method that the driver logic should recalculate its timing
parameters based on the new clock settings and also resume an operations
that were disabled by the ``prepare()`` callback.
The driver registers its PM callback vtable by calling pm_register() like:
.. code-block:: c
/* Register to receive power management callbacks */
int ret = pm_register(&g_xyz_pmcb);
Dynamic Clock Frequency Utility Functions
-----------------------------------------
Most timers and drivers in the system us the constant frequency values defined
in the ``board.h`` header file. In order to have variable time, the drivers
must be able to determine the current clock frequencies dynamically,
not via a fixed, constant definitions.
This means that there must be clock functions that derive various clock
firequencies in the clock distribution knowing only the input frequency
(i.e., crystal frequency of internal clock selection).
When the driver receives the PM ``notify()`` informing it that the clocking
may have changed, it must call these functions to get the new clock settings
instead of using the constant settings in board.h.
There is something similar in the source tree now for the case where NuttX
is started by a bootloader. In that case, the clock configuration is
set up by the bootloader, usually u-boot, and each driver that needs to know
clock frequencies must query the clock configuration to determine
the frequency.
For the SAMA5D4-EK, that is still handled by definitions in the ``board.h``
header file. See, for example, ``boards/arm/sama5/sama5d4-ek/include/*.h``.
``board.h`` includes another file that defines the clock setup based
on the configuration. If the SAMA5D4-EK is running out of SDRAM,
then it must have been started by a bootloader and the definitions appear in
``boards/ar/sama5/sama5d4-ek/include/board_sdram.h``.
In that case, the macros are redefined so that they map to a function call
to determine the clock frequency:
.. code-block:: c
#define BOARD_MAINCK_FREQUENCY BOARD_MAINOSC_FREQUENCY
#define BOARD_PLLA_FREQUENCY (sam_pllack_frequency(BOARD_MAINOSC_FREQUENCY))
#define BOARD_PLLADIV2_FREQUENCY (sam_plladiv2_frequency(BOARD_MAINOSC_FREQUENCY))
#define BOARD_PCK_FREQUENCY (sam_pck_frequency(BOARD_MAINOSC_FREQUENCY))
#define BOARD_MCK_FREQUENCY (sam_mck_frequency(BOARD_MAINOSC_FREQUENCY))
Something like this could also be done for other architectures to support
dynamic clock management.
You would probably have to do something similar if change the clocking.
``SysTick`` and each device would need a callback from the PM subsystem and each
would have to re-calculate is BAUD based on the new clock settings using
such functions calls to get the dynamic clock frequency.
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,300 @@
.. _short-time-delays:
=================
Short Time Delays
=================
System Timer Interrupt
======================
This section addresses some counter-intuitive properties of using very short
time delays.
This section assumes that timing is generated by a system timer
"tick" interrupt. In Tickless Mode there is no system timer interrupt.
Much of the discussion here would also apply in the Tickless mode, however,
the terminology used here assumes system timer interrupts.
Timer Resolution
================
When we talk about short delays, we are talking about delays that are
on the same order of magnitude as the system timer interrupt interval.
That interval is controlled the configuration setting
``CONFIG_USEC_PER_TICK``. The default value of ``CONFIG_USEC_PER_TICK``
is 10,000 microseconds.
That equivalent to a timer interrupt frequency of 100Hz:
.. code-block:: c
Ftimer ticks/sec = 1,000,000 usec/sec / CONFIG_USEC_PER_TICK;
There are many different OS interfaces that implement timed delays
(or function that have timeout values such as ``sem_timedwait()``).
These all behave in basically the same way.
.. _usleep:
``usleep()``
============
For simplicity of discussion let's focus on on ``usleep()``.
``usleep()`` is a standard but deprecated interface that simply delays
for a specified number of microseconds.
.. note:: ``usleep()`` is deprecated in favor of ``clock_nanosleep()``.
Requirements/Assumptions of ``usleep()``
----------------------------------------
The prototype for ``usleep()`` is:
.. code-block:: c
int usleep(useconds_t usec);
Where ``usec`` is the number microseconds to delay.
``usleep()`` will return zero unless an error occurs in which case
it will set the errno variable and return ``-1``.
Now the interesting questions:
Suppose that ``CONFIG_USEC_PER_TICK`` is set to 10,000 microseconds,
What will happen when you try any of the following?
.. code-block:: c
usleep(0);
usleep(1);
usleep(10000);
In order to predict such behavior, we will have to enumerate some of the
assumption and requirements of ``usleep()``:
1. The contract that ``usleep()`` makes is that it will suspend the calling
thread for at least usec microseconds. It will never, under any condition,
return with a delay smaller than the requestion usec delay.
2. ``usleep()`` may wait longer than the requested delay due to small system
processing overhead, task priorities, and quantization errors.
Task Priorities
---------------
When the requested delay expires, the calling task is ready to run but still
may not actually be able to run for some time due to higher priority tasks
that block execution of the ready to run task.
Quantization Errors
-------------------
``usleep()`` is only capable of waiting for multiples of the system timer
interrupt interval (``CONFIG_USEC_PER_TICK``) If the requested ``usec`` delay
is not an even multiple of the system timer interrupt interval,
then it will be rounded up as necessary to satisfy the first requirement above.
This means that the following are really equivalent delays:
.. code-block:: c
usleep(1);
usleep(10000);
And finally, the behavior that confuses most people..
.. note::
``usleep()`` has no knowledge of the the phase of the system timer
when it is started: The next timer interrupt may occur immediately
or may be delayed for almost a full cycle.
In order to meet the contract of the first requirement, the requested
time is also always incremented by one.
This means, for example, that the the delay:
.. code-block:: c
usleep(10000);
will not delay for one clock tick! That would be impossible!
There is no event exactly one clock tick after ``usleep()`` is called.
The next timer tick will always occur at some time strictly less than
the system timer interval.
Rather, ``usleep()`` will delay for two clock ticks resulting some
actual delay between 10 and 20 microseconds, exclusive.
See the following figure:
.. figure:: short_delay.png
:alt: Short Time Delays in NuttX.
Short Time Delays in NuttX.
For the most part, when delays are large hundreds or thousands
of microseconds this error is not significant.
It becomes noticeable only if you are using ``usleep()``
for delays very close to the the timer resolution when the error
can be relatively significant.
Finally, the easy one:
.. code-block:: c
usleep(0);
This will return immediately with no delay.
That satisfies all requirements and assumptions of the interface.
Using ``usleep()`` to implement periodic delays
-----------------------------------------------
The third assumption is necessary because ``usleep()`` has
no knowledge of the current system timer phase.
But it also makes ``usleep()`` a very bad choice for implementing
periodic behavior if the ``usleep()`` delay is close to the system
timer resolution.
For example, consider a loop such as the following
(again assuming that ``CONFIG_USEC_PER_TICK`` is set
to 10,000 microseconds):
.. code-block:: c
for (; ; )
{
usleep(10000);
/* Do some periodic stuff */
}
From the preceding discussion, we know that this will not work:
It will not (and cannot) create periodic processing at the rate
of the system timer interrupt rate but, rather, at half the system
time interrupt rate in this case.
In this case, that the third assumption of ``usleep()`` is not valid.
``usleep()`` does not start timing some random, unknown phase with respect
to the system timer interrupt.
In this case, ``usleep()`` will be started at a nearly constant phase
with respect to the system timer (at some some short delay after the
system timer interrupt) and will consistently show
near-worst case timing errors.
Of course, ``usleep()`` cannot know that and, hence, is a bad choice
for implementing such periodic behavior.
The user in this case is assuming that ``usleep()`` simply waits for the
next timer tick to occur. That is not the behavior or ``usleep()`` that
describes some non-existent interface that waits for the next timer interrupt
event, not for a fixed time delay.
.. note::
``usleep()`` behavior is to wait to assure that at least usec
microseconds has elapsed. And it does that job quite well.
A final note: The above periodic delay loop can be made to work well,
on the other hand, if the delay provided to ``usleep()`` is significantly
larger that the system timer resolution.
What can you do?
----------------
What can you do to improve the resolution for such high frequency processing
loop?
First, you might consider increasing the system timer interrupt rate.
You would do this by reducing the value of ``CONFIG_USEC_PER_TICK``.
For example a value of ``1000`` for ``CONFIG_USEC_PER_TICK`` would create
a timer interrupt rate of 1KHz and a minimum loop delay of
perhaps 2 milliseconds.
The trade-off here is that when you increase the timer interrupt rate,
the timer interrupt processing will then take a proportionately larger amount
of your CPU bandwidth.
The recommended way to get very high timer resolution without increasing
the timer interrupt rate (in most use cases) is to use a Tickless Mode OS.
For example, in the Tickless configuration, you could have
``CONFIG_USEC_PER_TICK`` set to ``1`` for a 1MHz timer resolution
(with no interrupts).
If your target periodic processing time is still 1Khz,
then this periodic processing could be met with good precision.
Another option is to abandon the system timer altogether and use
a dedicated timer peripheral to perform your timed operations with
high precision.
But if you really want to use the system timer than another thing
you should consider would be to implement a Timer Hook.
.. timer_hook:
Timer Hook
==========
A Timer Hook is a user provided function that is called from the OS
on each timer interrupt.
If you enable ``CONFIG_SYSTEMTICK_HOOK=y`` in your configuration,
then the OS timer interrupt handler will call out to a user-provided function,
``board_timerhook()``, on each timer interrupt.
The full prototype of this function is provided in ``included/nuttx/board.h``
as:
.. code-block:: c
void board_timerhook(void);
.. note::
The timer hook is only available when system timer interrupts
are used; it is not available in Tickless mode.
This timer hook could be used in a scenario where you would like to have
your task run at the each timer interrupt without the strange rounding
performed by the standard delay functions.
You might do something like the following as an example:
In your servicing task, implement a loop. At the top of the loop,
you would wait on a semaphore.
In the body of the loop you perform the periodic operation
then return to the wait at the top of the loop. Like:
.. code-block:: c
int ret;
while ((ret = sem_wait(&g_waitsem) >= 0)
{
/* Do periodic operations */
}
.. note::
If the return value from ``sem_wait()`` is negative then
some unusual event occurred. In normal cases the errno might either:
``EINTR`` meaning simply that the wait was awakened with a signal.
You can continue to wait in this case. Or ``ECANCELED`` meaning
that the thread has been cancelled and you should abort the periodic
operations.
In your ``boards/<arch>/<chip>/<board>/src/`` directory you would implement
``void board_timerhook(void)``.
The implementation of this function could consist of only a single line
of code: It could just post the semaphore on each timer interrupt,
waking of the loop in the servicing task. Like:
.. code-block:: c
void board_timerhook(void)
{
(void)sem_post(&g_waitsem);
}
This is very efficient. There is no context switch overhead at all
getting from the the timing interrupt to to the servicing task.
None at all other that the normal interrupt return logic.
The servicing task should be the highest priority task in the system
to assure that it is the one that runs immediately
when the timer interrupt returns.
@@ -0,0 +1,192 @@
.. _signal-handlers:
===============
Signal Handlers
===============
Signals are used to exchange information between sending and receiving thread.
Sending Thread
==============
Posting Signals
---------------
These are the actions that run on the thread that posts the signal.
Signals are initiated in these ways:
* ``signal/sig_kill.c: nxsig_kill()``. The standard function ``kill()`` is a
simple wrapper around ``nxsig_kill()``. ``nxsig_kill()`` can be used
to send a signal to any task group. It simply sets up the call
to ``nxsig_dispatch()``.
* ``signal/sig_queue.c: nxsig_queue()``. The standard function ``sigqueue()``
is a simple wrapper around ``nxsig_queue()``. ``nxsig_kill()`` can be used
to send a signal to any task group, passing more information than
is possible with ``kill()``.
It again simply sets up the call to ``nxsig_dispatch()``.
* ``signal/sig_notification.c: nxsig_notification()``. This logic can also
generate signals via a call to ``nxsig_dispatch()``.
But this is part of the internal, NuttX signal notification system.
It sends signals to tasks via the work queue.
signal/sig_dispatch.c: ``nxsig_dispatch()``
-------------------------------------------
.. code-block:: c
int nxsig_dispatch(pid_t pid, FAR siginfo_t *info);
This is the front-end for ``nxsig_tcbdispatch()`` that should be typically
be used to dispatch a signal. If ``HAVE_GROUP_MEMBERS`` is defined,
then this function will follow the group signal delivery algorithms:
This front-end does the following things before calling
``nxsig_tcbdispatch()``:
1. With ``HAVE_GROUP_MEMBERS`` defined:
1. Get the TCB associated with the ``pid``.
2. If the TCB was found, get the group from the TCB.
3. If the PID has already exited, lookup the group that that was
started by this task.
4. Use the group to pick the TCB to receive the signal.
5. Call ``nxsig_tcbdispatch()`` with the TCB.
2. With ``HAVE_GROUP_MEMBERS`` not defined:
1. Get the TCB associated with the ``pid``.
2. Call ``nxsig_tcbdispatch()`` with the TCB
group/group_signal.c: ``group_signal()``
----------------------------------------
.. code-block:: c
int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo);
Send a signal to the appropriate member(s) of the group.
This is typically called from ``nxsig_dispatch()`` as described above
but may also be called from ``task/task_exithook.c`` to handle
Death-of-Child (``SIGCHLD``) signals.
group/group_signal.c: ``group_signal_handler()``
------------------------------------------------
.. code-block:: c
static int group_signal_handler(pid_t pid, FAR void *arg)
Callback from ``group_foreachchild()`` that handles one member of the group.
signal/sig_dispatch.c: ``nxsig_tcbdispatch()``
----------------------------------------------
.. code-block:: c
int nxsig_tcbdispatch(FAR struct tcb_s *stcb, siginfo_t *info)
All signals received the task (whatever the source) go through this function
to be processed. This function is responsible for:
1. Determining if the signal is blocked.
2. Queuing and dispatching signal actions
3. Unblocking tasks that are waiting for signals
4. Queuing pending signals.
This function will deliver the signal to the specific task associated
with the specified TCB.
This function is also called when the OS needs to deliver a signal
to a specific task.
It is normally only called via ``group_signal_handler()`` so that is follows
the rules of signal deliver in multi-threaded tasks.
But it is also called from a few other places:
1. ``pthread/pthread_condtimedwait.c: pthread_condtimedout().``
Used to wake-up a specific task waiting on a condition.
2. ``task/task_exithook.c: task_sigchild()``
Used to send the Death-of-Child signal, ``SIGCHLD``, to the parent task.
For unmasked signals that have a signal handler attached,
``nxsig_tcbdispatch()`` will call the architecture-specific interface,
``up_schedule_sigaction()``.
arch/xxx/src/xxx/up_schedsigaction.c: ``up_schedule_sigaction()``
-----------------------------------------------------------------
.. code-block:: c
void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
Where sigdeliver is always the OS function ``nxsig_deliver()``.
This function is called by the OS when one or more signal handling actions
have been queued for execution for a specific task.
The architecture specific-code must configure things so that the sigdeliver
callback (i.e., ``nxsig_deliver()``) is executed on the thread specified
by tcb as soon as possible. This amounts to saving the signal handler state
information in the TCB so that when the receiving task next executes,
it will be the signal handler that runs, not the normal, uninterrupted thread.
One of the fixups performed by ``up_schedule_sigaction()`` is to force
the address of the signal handler to point to the trampoline function
``up_sigdeliver()``.
There are other special cases when the signal is generated from interrupt
handler or when the a task signals itself for some reason.
Those variations are not addressed here.
Receiving Thread
================
signal/sig_deliver.c: ``nxsig_deliver()``
-----------------------------------------
.. code-block:: c
void nxsig_deliver(FAR struct tcb_s *stcb)
This function is called on the thread of execution of the signal receiving task
when that task next runs again. It processes all queued signals then returns.
The mechanism by which a signal is deliver depends on the build configuration;
in PROTECTED and KERNEL build modes, it must go through a trampoline,
``up_signal_dispatch()`` to handle user-space signal actions.
``up_signal_dispatch()`` will drop from kernel mode to user mode,
then call the signal handler.
Otherwise, the signal handler is called directly from ``nxsig_deliver()``.
When the signal handler returns, the action is over and there is only
clean up to be done.
arch/xxx/src/xxx/up_sigdeliver.c: ``up_sigdeliver()``
-----------------------------------------------------
.. code-block:: c
void up_sigdeliver(void)
This is the a signal handling trampoline.
Logic in ``up_schedule_sigaction()`` forced the signal action to be
delivered to ``up_sigdeliver()``.
``up_sigdeliver()`` will do such things as:
1. Set up the state to return to the normal, uninterrupted thread
when the signal handler exits.
2. Make sure that the signal handler runs with interrupts enabled.
3. Invoke the signal handler.
When the signal handler returns this function will:
1. Free up resources committed by ``up_schedule_sigaction()``.
2. Restore the interrupt state.
3. And perform a context switch to return to the normal, uninterrupted thread.
When the uninterrupted thread is next suspended, the return will go back to
``nxsig_deliver()`` which will continue delivering signals.
(Hmmm.. shouldn't any other queued signal actions be handled first
before returning to the normal, uninterrupted thread?)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

@@ -0,0 +1,217 @@
.. _tasks-vs-threads:
=================
Tasks vs. Threads
=================
What is the difference between a thread and a task in Nuttx?
============================================================
Tasks and threads in NuttX try to emulate processes and threads
in the standard Unix environment.
I think of a process as a "container" of resources that are shared
by the threads that execute within the context of the process.
The process has one special thread, the "main" thread.
This is the special thread that is started when the process was created.
Since there are no processes in NuttX, the distinction is not so simple.
But in a similar way, tasks under NuttX have mostly private resources:
Their own file descriptors, their own ``FILE`` streams, their own ``errno``
variable, there own environment, etc.
Threads under NuttX, on the other hand, share task resources in the same way
that threads within the same process share the process resources.
The task resources were created when a task was created (``task_create()``,
``execv()``, ``posix_spawn()``, etc.).
When the task creates new ``pthreads``, those pthreads share the resources
of the parent task.
There is little to distinguish the main thread in NuttX.
All threads are essentially equal (as they were in old LinuxThreads library).
All task resources that are shared amongst threads reside in a "break-away",
reference-counted structure called struct ``task_group_s``.
The Task Control Block (TCB) of each thread that is a member of the task group
holds a reference to the same instance of this breakaway structure
(see ``include/nuttx/sched.h``).
The reference count of the shared,
task group resources is initialized to one when the task is created
(via ``task_create()``, for example); that task is the first member
of the task group.
The reference count is incremented when each additional thread is created
(via ``pthread_create()``) and joins task group.
The reference count equal to the number of members of the task group.
If any thread in the task group creates a new task (vs. thread),
that establishes a new task group but has no effect on the creator's
task group. The first task creates the task group;
the group membership only grows when threads are created via
``pthread_create()``.
As threads exit, they leave the task group and the reference count
on the struct ``task_group_s`` resources used by the thread is decremented.
When the reference count goes to zero, there are no members in the task group
and so the task group resources are finally deleted.
In this way, the life of a task resource begins when the task is created
and persists until the last thread in the task group exits.
Here is the definitive list of what is shared. Most of these resources
reside within the task group structure struct ``task_group_s``:
* Child task exit status.
* Pthread join data.
* Environment variables.
* File descriptors.
* FILE streams.
* Sockets.
* Opened message queues.
* ``pthread`` keys.
* Support data for ``atexit()``, ``on_exit()``, and/or ``waitpid()``.
The exception is the PIC address space used with NXFLAT.
It currently has its own data structure (struct ``dspace_s``,
but logically also belongs in ``task_group_s``):
* PIC data space and address environments.
.. note::
The ``errno`` is not one of the shared resources.
In NuttX ``errno`` is thread-private; each thread has
its own ``errno``. But for bug-for-bug compatibility, the ``errno``
variable really should also be moved into ``task_group_s``.
NuttX tasks and threads are discussed in much more detail in the
:ref:`nuttx-tasking` section.
When would I want to use a task? When would I want to use a thread?
===================================================================
Threads are very light weight; the memory cost for the thread is the cost
of the task control block plus the size of the stack.
That is perhaps 0.8-2KB, depending mostly on the configured stack size.
The memory cost of a task is higher because it includes most of the costs
of a pthread plus the cost of the task group structure.
The cost of the task group structure varies with such things as:
* The number of file descriptors you have configured (controlled by
``CONFIG_NFILE_DESCRIPTORS``),
* The number of streams that you have configured for C buffered I/O
(controlled by ``CONFIG_NFILE_STREAMS``),
* The size of the I/O buffer configured for for each stream
(controlled by ``CONFIG_STDIO_BUFFER_SIZE``). This setting is especially
important because there is an I/O buffer for each opened stream.
* The number of sockets that you have configured
(controlled by ``CONFIG_NSOCKET_DESCRIPTORS``).
* The size of dynamic data such as environment variables
(size is determined by your usage).
The size of task group can then become largely depending upon how these things
are configured, typically in the range 0.5-1KB.
So why would you ever use a task if pthreads use so much less memory?
**Pthreads and tasks share resources. So they are not independent.**
There is strong coupling between the threads in a task group
and what one thread does can affect the behavior of another thread.
Normally, each major block of functionality is implemented with a separate
task. With each of those ``_task group_s``, however, you may also want several
helper threads to implement asynchronous and concurrent behaviors.
How do signals work in a task group with many pthreads?
=======================================================
The behavior of signals in the multi-thread task group is complex.
NuttX emulates a process model with task ``group_s`` and follows the POSIX
rules for signalling behavior.
Normally when you signal the ``_task`` group you would signal using
the task ID of the main task that created the group (in practice, a different
task should not know the IDs of the internal threads created within
the task group); that ID is remembered by the task group
(even if the main task thread exits).
Here are some of the things that should happen when you signal
a multi-threaded task group:
* If a task group receives a signal then one and only one indeterminate thread
in the task group which is not blocking that signal will receive the signal.
* If a task group receives a signal and more than one thread is waiting
on that signal, then one and only one indeterminate thread out of that
waiting group will receive the signal.
You can mask out that signal using ``sigprocmask()``
(or ``pthread_sigmask()``). That signal will then be effectively disabled
and will never be received in those threads that have the signal masked.
On creation of a new thread, the new thread will inherit the signal mask
of the parent thread that created it.
So you if block signal signals on one thread then create new threads,
those signals will also be blocked in the new threads as well.
You can control which thread receives the signal by controlling
the signal mask.
You can, for example, create a single thread whose sole purpose
it is to catch a particular signal and respond to it:
Simply block the signal in the main task; then the signal will be blocked
in all of the pthreads in the group too.
In the one "signal processing" pthread, enable the blocked signal.
This thread will then be only thread that will receive the signal.
How do ``atexit()`` and ``on_exit()`` work with task groups?
============================================================
``atexit()`` and ``on_exit()`` callbacks must be registered using the task ID
of the main task (which makes sense since pthreads do not have task IDs
of type ``pid_t``).
The callback is not necessarily made when the task thread exits;
the ``atexit()`` and ``on_exit()`` callbacks will be executed when
the task group terminates, that is, when the final thread
of the task group terminates.
How does ``waitpid()`` work with task groups?
=============================================
In a single-thread task group, ``waitpid()`` will wait until the single,
main thread of the task group exits (i.e., the one created
by ``task_start()``). This is the intuitive behavior.
But the behavior may be less intuitive for multi-threaded task groups.
In a multi-threaded task group ``waitpid()`` will wait until
every thread in the task group exits.
Nothing special happens when the main thread of the task group exits.
What are privileged threads? How are threads handled differently when NuttX is built as a Kernel
================================================================================================
NuttX supports a build mode where it is built as a monolithic kernel.
This mode is selected with the configuration option
``CONFIG_BUILD_PROTECTED=y`` and is currently only supported
for a few architectures.
When used this way, NuttX is built as a separate kernel mode "blob"
and the applications are built as another separate user mode "blob".
The kernel runs in kernel mode and the applications run in user mode
(with the MPU restricting user mode accesses).
Access to the kernel from the user blob is only via system calls (SVCalls).
Thread and tasks that execute within the user mode "blob"
are all unprivileged, user mode threads. Exactly what "unprivileged"
means depends upon the memory protection architecture.
But it generally means that there are regions of memory where unprivileged
threads are prohibited from reading and/or writing data and/or from executing
code. Tasks created in the user-space are unprivileged;
all pthreads are unprivileged.
Certain threads are created within the kernel space to perform OS housekeeping
operations. Those are referred to as "kernel threads".
They are essentially the same as user-mode tasks but run in a privileged mode
and have full access to all of the restricted resources.
@@ -0,0 +1,375 @@
.. _tickless:
===========
Tickless OS
===========
Default System Timer
====================
By default, a NuttX configuration includes a periodic timer interrupt
that drives all system timing.
The timer is provided by architecture-specific code that calls into NuttX
at a rate controlled by ``CONFIG_USEC_PER_TICK``.
The default value of ``CONFIG_USEC_PER_TICK`` is 10000 microseconds which
corresponds to a timer interrupt rate of 100Hz.
On each timer interrupt, NuttX does these things:
* Increments a counter. This counter is the system time and has a resolution
of ``CONFIG_USEC_PER_TICK`` microseconds.
* Checks if it is time to perform time-slice operations on tasks that
have select round-robin scheduling.
* Checks for expiration of timed events.
What is wrong with this default system timer? Nothing really.
It is reliable and uses only a small fraction of the CPU band width.
But we can do better. Some limitations of default system timer are,
in increasing order of importance:
1. **Overhead:** Although the CPU usage of the system timer interrupt
at 100Hz is really very low, it is still mostly wasted processing time.
On most timer interrupts, there is really nothing that needs be done
other than incrementing the counter.
2. **Resolution:** Resolution of all system timing is also determined
by ``CONFIG_USEC_PER_TICK``. So nothing can be timed with resolution
finer than 10 milliseconds by default. If you want to increase this
resolution, then you can reduce ``CONFIG_USEC_PER_TICK``.
However, then the system timer interrupts use more of the CPU bandwidth
processing useless interrupts.
3. **Power Usage:** But the biggest issue is power usage.
When the system is IDLE, it enters a light, low-power mode (for ARMs,
this mode is entered with the ``wfi`` instruction for example).
But each interrupt awakens the system from this low power mode.
Therefore, higher rates of interrupts cause greater power consumption.
Tickless OS Advantages
======================
The so-called **Tickless OS** provides one solution to issue.
The basic concept here is that the periodic, timer interrupt is eliminated
and replaced with a one-shot, interval timer.
It becomes event driven instead of polled: The default system timer
is a polled design. On each interrupt, the NuttX logic checks if it needs
to do anything and, if so, it does it.
Using an interval timer, you can anticipate when the next interesting
OS event will occur, program the interval time and wait for it to fire.
When the interval time fires, then the scheduled activity is performed.
Platform Support
================
In order to use the Tickless OS, you must provide special support
from the platform-specific code. Just as with the default system timer,
the platform-specific code must provide the timer resources to support
the OS behavior.
Currently these timer resources are only provided on a few platforms.
You can see that implementation for the simulation in
``nuttx/arch/sim/src/up_tickless.c``. There is another implementation
for the Atmel SAMA5 at ``nuttx/arch/arm/src/sama5/sam_tickless.c``.
This Wiki will explain how you can provide the Tickless OS support
for your platform.
Configuration Options
=====================
* ``CONFIG_ARCH_HAVE_TICKLESS``: If your platform provides support
for the Tickless OS, then you should set this option in the ``Kconfig``
file for your board. Here is what the selection looks in the
``arch/Kconfig`` file for the simulated platform::
config ARCH_SIM
bool "Simulation"
select ARCH_HAVE_TICKLESS
---help---
Linux/Cywgin user-mode simulation.
* ``CONFIG_SCHED_TICKLESS``: If ``CONFIG_ARCH_HAVE_TICKLESS`` is selected,
then you will be able to use this option to enable the Tickless OS features
in NuttX.
* ``CONFIG_SCHED_TICKLESS_ALARM``: The tickless option can be supported either
via a simple interval timer (plus elapsed time) or via an alarm.
The interval timer allows programming events to occur after an interval.
With the alarm, you can set a time in the future and get an event when
that alarm goes off. This option selects the use of an alarm.
The advantage of an alarm is that it avoids some small timing errors;
the advantage of the use of the interval timer is that the hardware
requirement may be less.
* ``CONFIG_USEC_PER_TICK``: This is not a new option, but changes
its relevance when the Tickless OS is selected.
In the default configuration where system time is provided by a periodic timer
interrupt, the default system timer is configure the timer for 100Hz or
``CONFIG_USEC_PER_TICK=10000``. If ``CONFIG_SCHED_TICKLESS`` is selected,
then there are no system timer interrupt. In this case,
``CONFIG_USEC_PER_TICK`` does not control any timer rates.
Rather, it only determines the resolution of time reported by
``clock_systimer()`` and the resolution of times that can be set for certain
delays including watchdog timers and delayed work.
In this case there is still a trade-off:
It is better to have the ``CONFIG_USEC_PER_TICK`` as low as possible
for higher timing resolution. However, the the time is currently held
in ``unsigned int``. On some systems, this may be ``16-bit`` in width
but on most contemporary systems it will be ``32-bit``.
In either case, smaller values of ``CONFIG_USEC_PER_TICK`` will reduce
the range of values that delays that can be represented.
So the trade-off is between range and resolution (you could also modify
the code to use a 64-bit value if you really want both).
.. note:: Recently NuttX switched to ``int64_t`` for time stamps.
The default, 100 microseconds, will provide for a range of delays
up to 120 hours.
This value should never be less than the underlying resolution of the timer.
Error may ensue.
System Interfaces
=================
Implementation Notes
--------------------
Notice that with the ``CONFIG_SCHED_TICKLESS`` option, an implementation
might require two hardware timers:
(1) An interval timer to satisfy the requirements for ``up_timer_start()``
and ``up_timer_cancel()``, and
(2) a counter to handle the requirement of ``up_timer_gettime()``.
Since timers are a limited resource, the use of two timers could be an issue
on some systems. The job could be done with a single timer if, for example,
the single timer were kept in a free-running at all times.
Some timer/counters have the capability to generate a compare interrupt when
the timer matches a comparison value but also to continue counting without
stopping. If your hardware supports such counters, one might used the
``CONFIG_SCHED_TICKLESS_ALARM`` option and be able to simply set the
comparison count at the value of the free running timer PLUS the desired
delay. Then you could have both with a single timer:
An alarm and a free-running counter with the same timer!
Imported Intefaces
------------------
The interfaces that must be provided by the platform specified code
are defined in ``include/nuttx/arch.h`` and summarized below.
``up_timer_intialize()``
^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: c
void up_timer_initialize(void);
* **Description:** Initializes all platform-specific timer facilities.
This function is called early in the initialization sequence by
``up_intialize()``. On return, the current up-time should be available
from ``up_timer_gettime()`` and the interval timer is ready for use
(but not actively timing).
* **Input Parameters:** None.
* **Returned Value:** None.
* **Assumptions:** Called early in the initialization sequence before
any special concurrency protections are required.
``up_timer_gettime()``
^^^^^^^^^^^^^^^^^^^^^^
.. code:: c
int up_timer_gettime(FAR struct timespec *ts);
* **Description:** Return the elapsed time since power-up (or, more correctly,
since ``up_timer_initialize()`` was called). This function is functionally
equivalent to ``clock_gettime()`` for the clock ID ``CLOCK_MONOTONIC``.
This function provides the basis for reporting the current time and also
is used to eliminate error build-up from small errors
in interval time calculations.
* **Input Parameters:** ``*ts`` - Provides the location in which
to return the up-time.
* **Returned Value:** Zero (``OK``) is returned on success;
a negated ``errno`` value is returned on any failure.
* **Assumptions:** Called from the the normal tasking context. The implementation must provide whatever mutual exclusion is necessary for correct operation. This can include disabling interrupts in order to assure atomic register operations.
``up_alarm_cancel()``
^^^^^^^^^^^^^^^^^^^^^
.. code:: c
int up_alarm_cancel(FAR struct timespec *ts);
* **Description:** Cancel the alarm and return the time of cancellation
of the alarm. These two steps need to be as nearly atomic as possible.
``sched_timer_expiration()`` will not be called unless the alarm
is restarted with ``up_alarm_start()``. If, as a race condition,
the alarm has already expired when this function is called,
then time returned is the current time.
* **Input Parameters:** ``*ts`` - Location to return the expiration time.
The current time should be returned if the timer is not active.
``ts`` may be ``NULL`` in which case the time is not returned.
* **Returned Value:** Zero (``OK``) is returned on success;
a negated ``errno`` value is returned on any failure.
* **Assumptions:** May be called from interrupt level handling or from the
normal tasking level. Interrupts may need to be disabled internally
to assure non-reentrancy.
.. note:: This function is only required when ``CONFIG_SCHED_TICKLESS_ALARM``
is defined.
``up_alarm_start()``
^^^^^^^^^^^^^^^^^^^^
.. code:: c
int up_alarm_start(FAR const struct timespec *ts);
* **Description:** Start the alarm. ``sched_timer_expiration()`` will be
called at alarm expires (unless ``up_alarm_cancel()`` is called to stop
the alarm.
* **Input Parameters:** ``*ts`` - The time in the future at the alarm
is expected to occur. When the alarm occurs the timer logic will call
``ched_timer_expiration()`` is called.
* **Returned Value:** Zero (``OK``) is returned on success;
a negated ``errno`` value is returned on any failure.
* **Assumptions:** May be called from interrupt level handling
or from the normal tasking level. Interrupts may need to be disabled
internally to assure non-reentrancy.
.. note:: This function is only required when ``CONFIG_SCHED_TICKLESS_ALARM``
is defined.
``up_timer_cancel()``
^^^^^^^^^^^^^^^^^^^^^
.. code:: c
int up_timer_cancel(FAR struct timespec *ts);
* **Description:** Cancel the interval timer and return the time remaining
on the timer. These two steps need to be as nearly atomic as possible.
``sched_timer_expiration()`` will not be called unless the timer
is restarted with ``up_timer_start()``. If, as a race condition,
the timer has already expired when this function is called,
then that pending interrupt must be cleared so that
``up_timer_start()`` and the remaining time of zero should be returned.
* **Input Parameters:** ``*ts`` - Location to return the remaining time.
Zero should be returned if the timer is not active. ``ts`` may be ``NULL``
in which case the time remainging is not returned
* **Returned Value:** Zero (``OK``) is returned on success;
a negated ``errno`` value is returned on any failure.
* **Assumptions:** May be called from interrupt level handling
or from the normal tasking level. Interrupts may need to be disabled
internally to assure non-reentrancy.
.. note:: This function is only required when ``CONFIG_SCHED_TICKLESS_ALARM``
is not defined.
``up_timer_start()``
^^^^^^^^^^^^^^^^^^^^
.. code:: c
int up_timer_start(FAR const struct timespec *ts);
* **Description:** Start the interval timer. ``sched_timer_expiration()``
will be called at the completion of the timeout (unless
``up_timer_cancel()`` is called to stop the timing.
* **Input Parameters:** ``*ts`` - Provides the time interval until
``sched_timer_expiration()`` is called.
* **Returned Value:** Zero (``OK``) is returned on success;
a negated ``errno`` value is returned on any failure.
* **Assumptions:** May be called from interrupt level handling or from the
normal tasking level. Interrupts may need to be disabled internally
to assure non-reentrancy.
.. note:: This function is only required when ``CONFIG_SCHED_TICKLESS_ALARM``
is not defined.
Exported Interfaces
-------------------
In addition, the following interface is provided by the RTOS for use
by the platform specific code:
``sched_alarm_expiration()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: c
void sched_timer_expiration(FAR const timespec *tc);
* **Description:** if ``CONFIG_SCHED_TICKLESS`` is defined, then this
function is provided by the RTOS base code and called from
platform-specific code when the alaram used to implement
the tick-less OS expires.
* **Input Parameters:** ``*ts`` - The time when the alarm expired.
* **Returned Value:** None.
* **Assumptions:** Base code implementation assumes that this function
is called from interrupt handling logic with interrupts disabled.
.. note:: This function is only provided when ``CONFIG_SCHED_TICKLESS_ALARM``
is defined.
``sched_timer_expiration()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: c
void sched_timer_expiration(void);
* **Description:** if ``CONFIG_SCHED_TICKLESS`` is defined, then this
function is provided by the RTOS base code and called from
platform-specific code when the interval timer used to implement
the tick-less OS expires.
* **Input Parameters:** None.
* **Returned Value:** None.
* **Assumptions:** Base code implementation assumes that this function
is called from interrupt handling logic with interrupts disabled.
.. note:: This function is only provided when ``CONFIG_SCHED_TICKLESS_ALARM``
is not defined.
Issues
======
Let me describe an experience to illustrate some of the problems
that you may run into:
I implemented the tickless mode on an MCU with ``16-bit`` timers.
Most of the clock sources were too fast for use with the 16-bit timer,
so I tried using a 32.768 kHz clock source.
This resulted in a time resolution of about 30.518 microseconds.
That time, 30.518 microseconds, can not be represented accurately with
``CONFIG_USEC_PER_TICK`` so I used the value 31 which is in error
by about 0.6%.
What I found on a busy system is that the delay I get when executing
this NSH command was actually about 5.5 seconds::
nsh> sleep 10
This was very puzzling to me and cost me a lot of debug time.
Finally, I disabled all competing usage of the interval timer by disabling
features (the high priority work queue, by the way, is the most significant
user of the interval timer).
After disabling all competing usage, the NSH command sleep 10 resulted
in a delay of about 10.3 seconds. Still not accurate but not so bad.
I concluded that the gross inaccuracy in the first case was due to
the inaccuracies in the representation of the clock rate.
30.518 usec cannot be represented accurately.
Each timing calculation results in a small error.
When the interval timer is very busy, long delays will be divided
into many small pieces and each small piece has a large error
in the calculation. The cumulative error is the cause of the problem.
.. caution:: The moral of this story: Do not use timer sources
that cannot be represented by ``CONFIG_USEC_PER_TICK``.
+358
View File
@@ -0,0 +1,358 @@
.. _thread-local-storage:
=========================
TLS: Thread Local Storage
=========================
Historical Background
=====================
Thread Local Storage (TLS) was originally implemented to support
pthread-specific data and a per-thread ``errno`` variable.
``errno``
=========
The user ``errno`` value was originally kept in the TCB and could be accessed
via an OS system call called ``__errno()``:
.. code-block:: c
# define errno *__errno()
The ``errno`` value was kept in the thread's TCB in the ``pterrno`` field.
The ``__errno()`` function finds the TCB associated with the current thread
and returns a pointer to that errno storage location.
This worked in the FLAT build mode because it dereferenced the address
of the errno in TCB as both an ``RVALUE``:
.. code-block:: c
errcode = errno;
And as an ``LVALUE``:
.. code-block:: c
errno = errcode;
That works, however, it is rather inefficient.
However, that will not work at all in the PROTECTED or KERNEL build modes
because the memory holding the TCB is protected and cannot be directly
accessed from the user mode application.
Instead, it is accessed through accessor functions:
.. code-block:: c
void set_errno(int errcode);
int get_errno(void);
Accessing the ``errno`` via these functions in PROTECTED or KERNEL mode
is a huge performance hit, because the calls must go through a system call
which is implemented as a software interrupt.
And in this case, the errno is defined to be:
.. code-block:: c
# define errno get_errno()
This works fine as an ``RVALUE``:
errcode = errno;
But will cause a compilation error if used as an ``LVALUE``:
.. code-block:: c
errno = errcode;
Instead user code must explicitly call ``set_errno(errcode)``.
This a a violation of the accepted usage of the POSIX ``errno`` variable.
TLS-Based ``errno``
-------------------
The current solution has moved the ``errno`` storage location out of the
protected TCB memory and into the unprotected application thread's stack
memory. Then it can be accessed with the appropriate TLS
(Thread Local Storage) interfaces.
Task-Specific Globals in the FLAT and PROTECTED builds
======================================================
A special case is the use of TLS to support task-specific
(vs. thread specific) global data.
Task-specific differs from thread-specific in that all of the threads
in the task group share the global data, however, each task group has
its own set of task-specific global variables.
This feature permits a high-fidelity emulation of process global data
as you would see in the KERNEL build where each process has its own copy
of all global variables.
Task-specific data is implemented by keeping such globals in the stack
of the main thread. A single main thread is always present and, hence,
provides user-accessible, per-task storage.
Task-specific data is accessed like TLS except that the task-specific data
is accessed from the stack of the main thread,
rather than the stack stack of the main thread.
Re-Entrant ``getopt()``
=======================
One use of task-specific data is for global variables used by the ``getopt()``
function. ``getopt()`` is an important C library function used by applications
for parsing of task command line parameters.
It is, however, not re-entrant due to the use of global variables:
``optarg``, ``opterr``, ``optind``, and ``optopt``.
There are several, non-standard implementations for a re-entrant version
of called ``getopt_r()``, however, these are all wildly different
and do not conform to any standard.
.. important:: Non-standard interfaces are not desirable in NuttX.
In the FLAT and PROTECTED builds, this non-reentrant limitation
poses a problem. Unlike the KERNEL build mode, there is one instance
of of the globals ``optarg``, ``opterr``, ``optind``, and ``optopt``
shared across all task groups.
It is not a problem in the KERNEL build where there is a separate instance
of the globals in each process address space.
Using task-specific data, however, it is possible to make ``getopt()``
thread-safe. This amounts to keeping the ``getopt()`` globals in the main
thread's TLS so that there is an individual copy for each thread.
That would keep both the standard form of the ``getopt()`` interfaces
as well as making ``getopt()`` full re-entrant with respect to task groups.
Unaligned TLS
=============
Historically, TLS worked by aligning the stack base address then
simply AND'ing the current stack pointer to obtain the base address
of the stack where the TLS data can be found (as struct ``tls_info_s``).
This mechanism is very efficient because no OS system call is required,
as application logic can obtain the TLS data directly form its stack
via AND'ing and casting.
This works well in the KERNEL build mode where the stack resides at highly
aligned virtual address, but does not work well in FLAT and PROTECTED modes:
1. The alignment must be large. That is because it also determines
the maximum size of the thread's stack. If the size of the thread's stack
exceeds the maximum value determined by the alignment, then
the AND operation will alias to the wrong address.
2. If all of the addresses are highly aligned then
(1) you need to have much more memory available for stack allocation.
This is because (2) the large alignment causes bad memory fragmentation
and degraded use of memory.
An alternative way to get the stack base address is to call into the OS
to get the unaligned stack base address.
This involves a system call, but is more usable that other alternatives
in the FLAT and PROTECTED modes.
We have implemented the configuration: ``CONFIG_TLS_ALIGNED`` that selects
the legacy aligned stack for TLS access. If this is not defined, then
the new unaligned stack TLS logic is used.
We have also implemented aligned and unaligned TLS support
for every architecture.
Addition implementation steps:
1. Moved the ``errno`` storage location out of the TCB and in TLS
(into the struct ``tls_info_s``).
2. Modified the ``errno`` access definitions and logic that was
in ``sched/errno`` to use the TLS logic in user space.
That logic now resides in ``libs/libc/errno`` since it is now
a user library interface, not a core OS interface.
3. TLS is now enabled by default. It is enabled in the unaligned mode
for the FLAT and PROTECTED build modes but in the highly efficient
aligned mode for KERNEL build mode.
4. There are no longer an OS system calls related to the error
(with the exception of a call to get the struct ``tls_info_s``
in the unaligned TLS mode).
Push-Up vs. Push-Down Stacks
============================
TLS data is always located at the beginning thread's stack.
This is true for both CPUs with push-up stacks and CPUs with push-down stacks.
This location required in order to access the TLS by ANDing the aligned stack
pointer address.
The stack memory maps, differ only in the usage of the available stack::
Push Down Push Up
+-------------+ +-------------+ <- Stack memory allocation
| TLS Data | | TLS Data |
+-------------+ +-------------+
| Task Data* | | Task Data* |
+-------------+ +-------------+
| Arguments | | Arguments |
+-------------+ +-------------+ |
| | | | v
| Available | | Available |
| Stack | | Stack |
| | | |
| | | |
| | ^ | |
+-------------+ | +-------------+
\*) Task data is allocated in the main's thread's stack only
TLS interfaces
==============
TLS is a non-standard, but more general interface.
It differs from pthread-specific data only in that its semantics are general;
the semantics of the pthread-specific data interfaces are focused on pthreads.
But they really should share the same common underlying logic.
Currently, there are four TLS interfaces:
.. code-block:: c
int tls_alloc(void);
int tls_free(int tlsindex);
uintptr_t tls_get_value(int tlsindex);
int tls_set_value(int tlsindex, uintptr_t tlsvalue);
as prototyped and documented in ``include/nuttx/tls.h``.
These interfaces are basically adaptations from the Windows TLS interfaces
which are directly analogous to the POSIX pthread-specific data interfaces,
adapted to NuttX coding standards (see, for example, links available at
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsalloc
).
There are no POSIX TLS interfaces and Linux (actual GLIBC) does not provide
a good mechanism. It relies on a storage class that is specific
to ELF binaries. That is not useful in an embedded system where
no ELF information is present.
pthread-specific data
=====================
Pthread-specific data is another mechanism for accessing thread-specific data.
It consists of these POSIX standard interfaces:
.. code-block:: c
int pthread_key_create(FAR pthread_key_t *key,
CODE void (*destructor)(FAR void *));
int pthread_setspecific(pthread_key_t key, FAR const void *value);
FAR void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete(pthread_key_t key);
This, historically, was separate from TLS data and managed internal
to the OS. pthread-specific data was only accessible through OS system calls.
But it is a natural extension of the TLS logic to support both
non-standard TLS access and standard pthread-specific data access.
This would amount to:
1. Moving the pthread-specific storage out of the TCB
and into the TLS data.
2. Moving the pthread-specific data accessors out of ``sched/pthread``
and into ``libs/libc/pthread``.
3. Remove the pthread-specific data system calls.
Accessing the ``errno`` from kernel space
=========================================
.. important:: We should never access the errno from kernel space.
This is especially dangerous from kernel space because we can't be certain
which user address environment is in effect or which stack is being used
(if we were to use separate kernel mode stacks in the future as Linux does,
for security reasons, etc).
Most OS interfaces have two parts:
1. A user callable function, say ``osapi()`` that sets the ``errno`` value
is necessary (and may implement cancellation points) and
2. An internal OS version which might then be ``nx_osapi()`` which
does not modify the ``errno`` value.
Ideally, all of the user callable OS interfaces (the ``osapi()``) should be
moved to the location in ``libs/libc`` the system call (``sycall/``) should be
redirected to ``nx_osapi()``.
A complication to doing this is that the OS interfaces which are also
cancellation points call special internal interfaces,
``enter_cancellation_point()`` and ``leave_cancellation_point()``
that are not available from user space.
So some addition partitioning would be need to accomplish this.
This separation of user and OS interfaces has not been implemented
as of this writing.
Wild Ideas
==========
The primary beneficiary of TLS is the C library.
**The TLS interfaces are non-standard and should not be used directly
by any portable application code.**
However, these non-standard TLS interfaces can be used within the C library
to implement improved, standard user interfaces.
Local Process ID
----------------
Another data item that should, eventually, be included in the TLS data
is the process ID (``pid``) of the currently executing thread.
In some analysis of PROTECTED and KERNEL builds, it was found that
``getpid()`` was the most highly accessed OS interface.
By moving the PID into TLS, we could eliminate this system call overhead
(at least in the aligned TLS case).
The same mechanism would be used by ``pthread_self()`` which, in NuttX,
would be the equivalent function, but following pthread semantics.
Streams
-------
In NuttX, C buffered I/O streams are represented with a pre-group array
of type FILE. A stream is accessed from the OS via the
``nxched_get_streams()`` interface (system call).
The streams array is not used by OS and would be another candidate
to move into TLS.
.. note:: In retrospect, this is not such a good idea because the I/O streams
are not unique per-thread; they are common across all threads within
a task group: That includes main thread of the group and all child
pthreads whose parent, grandparent, of great-grandparent is the
main thread. They all share the same I/O stream array.
As a result, I/O streams must be one per task-group.
This works now because the I/O stream array is a part
of the internal OS group structure which is one per task group.
While it would be nice to get the I/O stream around out of the OS,
TLS is probably not the right solution to do that.
Code References
===============
Things to look at:
* ``include/errno.h`` - Defines current errno access.
* ``include/nuttx/tls.h`` - Defines the tls_info_s structure.
* ``include/nuttx/sched.h`` - Group related TLS structures.
* ``libs/libc/errno`` - The TLS-based errno logic.
* ``libs/libc/tls`` - The implementation of the most TLS interfaces.
* ``sched/group`` - Group-related implementation of certain TLS interfaces.
* ``libs/libc/pthread`` - The implementation of the pthread-specific dta interfaces.
+147
View File
@@ -0,0 +1,147 @@
==========================
USB (Universal Serial Bus)
==========================
NAKing USB OUT/IN Tokens
------------------------
USB supports a NAKing mechanism to pace incoming OUT data from the USB host.
The USB device class driver can control this NAKing only indirectly
by the manner in which it manages read requests.
Similarly, USB will refuse to perform the USB host's requests for IN data
by NAKing the host's IN tokens.
.. note:: USB direction naming is host-centric. This makes life difficult for
people working USB device drivers:
IN refers to data coming from the device INto the host.
But for the device, this is a write operation (write being
peripheral-centric: from-memory-to-peripheral).
OUT refers to data sent from the host OUT to the device.
For the device this is a read (peripheral-to-memory) operation.
NAKing USB OUT Tokens
^^^^^^^^^^^^^^^^^^^^^
Below is how the NAKing is implemented in the interaction between the USB
device class driver and USB Device Controller Drivers (DCDs):
1. At initialization time when the USB device class driver is enumerated
by the host, the class driver will allocates a number of read requests
(which contain request buffers).
2. The USB device class driver sends each allocated read request to the device
DCD via ``DRVR_EPSUBMIT()``.
3. Upon receipt, the DCD will keep all of these empty read requests in a list.
4. When the OUT token is received what happens next depends upon the state
of the list of empty read requests:
4a. If there are no requests in the list of empty read requests, the DCD
will have configured the endpoint to NAK the OUT token. This NAKing
will continue until the list of read requests is non-empty and the DCD
disables the NAKing to signal that it can accept the OUT packet data.
4b. If the list of read requests is not empty, the DCD will remove the next
read request from the list, receive the DATA accompanying the OUT
token, copy the received packet data into the read request's buffer,
and return the filled reqd request to the USB device class driver.
The data is returned using a callback function pointer that is
contained inside of the read request.
5. When the filled read request if returned, the USB device class driver will
add the newly filled read request to the TAIL of a list of filled read
requests. This will then be available to the the driver read logic when
the application requests more data.
This step will also wake up read logic that has been suspended waiting
for the availability of incoming data.
6. The receive logic starts when the application requests data (or it is
resumed after the receipt of new data) it will look at the read request
at the HEAD of the list of filled read requests. If will return the data
payload to the application by copying it from the read request's buffer
into the application-provided receive buffer:
6a. If the application's receive buffer becomes full and it cannot accept
more data, the receive logic will save the offset in the data buffer
where it left off. If the application asks for more data, it will
resume transferring at the point where it left off.
6b. If the receive logic transfers all of the data from the receive
buffer. It will remove the read request from the HEAD of the list
of filled read requests and return the receive buffer to the driver
via ``DRVR_EPSUBMIT()`` (See step 3).
The receive logic will then continue at step 6.
6c. If the receive logic needs more data in order to complete
the application read operation, it will wait until it is awakened
by the receipt of a newly filled.
CDC/ACM driver
~~~~~~~~~~~~~~
The CDC/ACM driver differs from all other drivers because it is REQUIRED
to discard data if there is no application ready to receive the data.
This is how a serial device works. So there is nothing like step 5
in the CDC/ACM driver and in step 6 the CDC/ACM driver will not remember
the point where it left off; if the application cannot accept any further
data, it will return the partially emptied read request to the DCD
immediately to be filled dropping an untransferred data into the bit bucket.
This is not an error or an oversight in the CDC/ACM. This is the intentional
design. This is the CORRECT behavior of the CDC/ACM driver. Data overrun can
only be prevented on a serial connection by using either XON/XOFF software
flow control or hardware flow control. A special endpoint 0 control request
is reserved for the CDC/ACM implementation.
.. note:: As of this writing, the logic that implements the CDC/ACM
_hardware flow control has not been implemented.
There is a place holder for such logic, but the implementation
is missing._
NAKing USB IN Tokens
^^^^^^^^^^^^^^^^^^^^
The DCD will NAK IN tokens whenever it has nothing to send to the host PC.
Without going into as much detail, suffice it to say that the logic flow
is very similar to the case off OUT tokens with the following differences:
1. The list of empty write requests are not provided to the DCD at
initialization time, but are instead are retained in a list of empty write
requests in the USB device class driver.
2. When the USB class driver has data to send, it will remove a write request
from this list, copy the outgoing application data into the write buffer,
and send the filled write request to the DCD via ``DRVR_EPSUBMIT()``.
If there are no available write requests, the application thread may block
waiting for the next write request to be returned from the DCD.
3. The DCD will keep a list of filled write requests for each endpoint.
When that list becomes non-empty, the DCD will disable the NAKing and
set up to transfer data to the host. Normally this involves copying
the IN data into an outgoing FIFO or setting up some write DMA transfers:
3a. After the data has been transferred to the USB device hardware,
the emptied write request will be returned to the USB device class driver.
This is done through a callback function pointer within the write request.
3b. If the DCD's list of write requests becomes empty, it will set up
the endpoint to NAK any further IN tokens (after the final IN transfer
completes).
4. When the USB device class driver receives the emptied write request from
the DCD, it will return the write request to its list of empty write
request and may, perhaps, wake up any thread that was waiting
for an available write request to send more data.
So the fundamental different between IN and OUT processing is that for
IN transfers, empty write requests are saved in the USB device class driver
and then given to the DCD when they are filled with outgoing data.
For OUT transfers empty read requests are retained in the DCD and returned
to the USB device class driver when they filled with incoming data.
Similarly, filled read requests are queued for application read processing
in the USB device class driver; filled write requests are queued for transfer
to the host in the DCD.
But in either case, NAKing occurs when associated DCD request queue is empty.
@@ -1,3 +1,5 @@
.. _board-ioctl:
===========
Board IOCTL
===========