mirror of
https://github.com/apache/nuttx.git
synced 2026-10-03 12:18:06 +00:00
381 lines
13 KiB
ReStructuredText
381 lines
13 KiB
ReStructuredText
|
|
.. _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.
|