zephyr/doc/object/fibers.rst

143 lines
5.5 KiB
ReStructuredText

.. _fibers:
Fibers
######
A fiber is an execution thread and a lightweight alternative to a task. It can
use nanokernel objects but not microkernel objects. A runnable fiber will
preempt the execution of any task but it will not preempt the execution of
another fiber.
Defining Fibers
***************
A fiber is defined as a routine that takes two 32-bit values as
arguments and returns a void within the application, for example:
.. code-block:: c
void fiber ( int arg1, int arg2 );
.. note::
A pointer can be passed to a fiber as one of the parameters but it
must be cast to a 32-bit integer.
Unlike a nanokernel task, a fiber cannot be defined within the project
file.
Fibers can be written in assembly. How to code a fiber in assembly is
beyond the scope of this document.
Starting a Fiber
****************
A nanokernel fiber must be explicitly started by calling
:c:func:`fiber_fiber_start()` or :c:func:`task_fiber_start()` to create
and start a fiber. The function :c:func:`fiber_fiber_start()` creates
and starts a fiber from another fiber, while
:c:func:`task_fiber_start()` does so from a task. Both APIs use the
parameters *parameter1* and *parameter2* as *arg1* and *arg2* given to
the fiber . The full documentation on these APIs can be found in the
:ref:`code`.
When :c:func:`task_fiber_start()`is called from a task, the new fiber
will be immediately ready to run. The background task immediately stops
execution, yielding to the new fiber until the fiber calls a blocking
service that de-schedules it. If the fiber performs a return from the
routine in which it started, the fiber is terminated, and its stack can
then be reused or de-allocated.
Fiber Stack Definition
**********************
The fiber stack is used for local variables and for calling functions or
subroutines. Additionally, the first locations on the stack are used by
the kernel for the context control structure. Allocate or declare the
fiber stack prior to calling :c:func:`fiber_fiber_start()`. A fiber
stack can be any sort of buffer. In this example the fiber stack is
defined as an array of 32-bit integers:
.. code-block::cpp
int32_t process_stack[256];
The size of the fiber stack can be set freely. It is recommended to
start with a stack much larger than you think you need, say 1 KB for a
simple fiber, and then reduce it after testing the functionality of the
fiber to optimize memory usage. The number of local variables and of
function calls with large local variables determine the required stack
size.
Stopping a Fiber
****************
There are no APIs to stop or suspend a fiber. Only one API can influence
the scheduling of a fiber, :c:func:`fiber_yield()`. When a fiber yields
itself, the nanokernel checks for another runnable fiber of the same or
higher priority. If a fiber of the same priority or higher is found, a
context switch occurs. If no other fibers are ready to execute, or if
all the runnable fibers have a lower priority than the currently
running fiber, the nanokernel does not perform any scheduling allowing
the running fiber to continue. A task or an ISR cannot call
:c:func:`fiber_yield()`.
If a fiber executes lengthy computations that will introduce an
unacceptable delay in the scheduling of other fibers, it should yield
by placing a :c:func:`fiber_yield()` call within the loop of a
computational cannot call :c:func:`fiber_yield()`.
Scheduling Fibers
*****************
The fibers in the Zephyr Kernel are priority-scheduled. When several fibers
are ready to run, they run in the order of their priority. When more
than one fiber of the same priority is ready to run, they are ordered
by the time that each became runnable. Each fiber runs until it is
unscheduled by an invoked kernel service or until it terminates. Using
prioritized fibers, avoiding interrupts, and considering the interrupts
worst case arrival rate and cost allows the kernel to use a simple
rate-monotonic analysis techniques with the nanokernel. Using this
technique an application can meet its deadlines.
When an external event, handled by an ISR, marks a fiber runnable, the
scheduler inserts the fiber into the list of runnable fibers based on
its priority. The worst case delay after that point is the sum of the
maximum execution times between un-scheduling points of the earlier
runnable fibers of higher or equal priority.
The nanokernel provides three mechanisms to reduce the worst-case delay
for responding to an external event:
Moving Computation Processing to a Task
=======================================
Move the processing to a task to minimize the amount of computation that
is performed at the fiber level. This reduces the scheduling delay for
fibers because a task is preempted when an ISR makes a fiber that
handles the external event runnable.
Moving Code to Handle External Event to ISR
===========================================
Move the code to handle the external event into an ISR. The ISR is
executed immediately after the event is recognized, without waiting for
the other fibers in the queue to be unscheduled.
Adding Yielding Points to Fibers
================================
Add yielding points to fibers with :c:func:`fiber_yield()`. This service
un-schedules a fiber and places it at the end of the ready fiber list
of fibers with that priority. It allows other fibers at the same
priority to get to the head of the queue faster. If a fiber executes
code that will take some time, periodically call
:c:func:`fiber_yield()`. Multi-threading using blocking fibers is
effective in coding hard real-time applications.