373 lines
16 KiB
ReStructuredText
373 lines
16 KiB
ReStructuredText
.. _kernel_fundamentals:
|
|
|
|
Kernel Fundamentals
|
|
###################
|
|
|
|
This section provides a high-level overview of the concepts and capabilities
|
|
of the Zephyr kernel.
|
|
|
|
Organization
|
|
************
|
|
|
|
The central elements of the Zephyr kernel are its *microkernel* and underlying
|
|
*nanokernel*. The kernel also contains a variety of auxiliary subsystems,
|
|
including a library of device drivers and networking software.
|
|
|
|
Applications can be developed using both the microkernel and the nanokernel,
|
|
or using the nanokernel only.
|
|
|
|
The nanokernel is a high-performance, multi-threaded execution environment
|
|
with a basic set of kernel features. The nanokernel is ideal for systems
|
|
with sparse memory (the kernel itself requires as little as 2 KB!) or only
|
|
simple multi-threading requirements (such as a set of interrupt
|
|
handlers and a single background task). Examples of such systems include:
|
|
embedded sensor hubs, environmental sensors, simple LED wearables, and
|
|
store inventory tags.
|
|
|
|
The microkernel supplements the capabilities of the nanokernel to provide
|
|
a richer set of kernel features. The microkernel is suitable for systems
|
|
with heftier memory (50 to 900 KB), multiple communication devices
|
|
(like WIFI and Bluetooth Low Energy), and multiple data processing tasks.
|
|
Examples of such systems include: fitness wearables, smart watches, and
|
|
IoT wireless gateways.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`common`
|
|
* :ref:`nanokernel`
|
|
* :ref:`microkernel`
|
|
|
|
Multi-threading
|
|
***************
|
|
|
|
The Zephyr kernel supports multi-threaded processing for three types
|
|
of execution contexts.
|
|
|
|
* A **task context** is a preemptible thread, normally used to perform
|
|
processing that is lengthy or complex. Task scheduling is priority-based,
|
|
so that the execution of a higher priority task preempts the execution
|
|
of a lower priority task. The kernel also supports an optional round-robin
|
|
time slicing capability so that equal priority tasks can execute in turn,
|
|
without the risk of any one task monopolizing the CPU.
|
|
|
|
* A **fiber context** is a lightweight and non-preemptible thread, typically
|
|
used for device drivers and performance critical work. Fiber scheduling is
|
|
priority-based, so that a higher priority fiber is scheduled for execution
|
|
before a lower priority fiber; however, once a fiber is scheduled it remains
|
|
scheduled until it performs an operation that blocks its own execution.
|
|
Fiber execution takes precedence over task execution, so tasks execute only
|
|
when no fiber can be scheduled.
|
|
|
|
* The **interrupt context** is a special kernel context used to execute
|
|
:abbr:`ISRs Interrupt Service Routines`. The interrupt context takes
|
|
precedence over all other contexts, so tasks and fibers execute only
|
|
when no ISR needs to run. (See below for more on interrupt handling.)
|
|
|
|
The Zephyr microkernel does not limit the number of tasks or fibers used
|
|
in an application; however, an application that uses only the nanokernel
|
|
is limited to a single task.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`Nanokernel Fiber Services <nanokernel_fibers>`
|
|
* :ref:`Microkernel Task Services <microkernel_tasks>`
|
|
|
|
Interrupts
|
|
**********
|
|
|
|
The Zephyr kernel supports the handling of hardware interrupts and software
|
|
interrupts by interrupt handlers, also known as ISRs. Interrupt handling takes
|
|
precedence over task and fiber processing, so that an ISR preempts the currently
|
|
scheduled task or fiber whenever it needs to run. The kernel also supports nested
|
|
interrupt handling, allowing a higher priority ISR to interrupt the execution of
|
|
a lower priority ISR, should the need arise.
|
|
|
|
The nanokernel supplies ISRs for a few interrupt sources (IRQs), such as the
|
|
hardware timer device and system console device. The ISRs for all other IRQs
|
|
are supplied by either device drivers or application code. Each ISR can
|
|
be registered with the kernel at compile time, but can also be registered
|
|
dynamically once the kernel is up and running. Zephyr supports ISRs that
|
|
are written entirely in C, but also permits the use of assembly language.
|
|
|
|
In situations where an ISR cannot complete the processing of an interrupt in a
|
|
timely manner by itself, the kernel's synchronization and data passing mechanisms
|
|
can hand off the remaining processing to a fiber or task.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`Nanokernel Interrupt Services <nanokernel_interrupts>`
|
|
|
|
Clocks and Timers
|
|
*****************
|
|
|
|
Kernel clocking is based on time units called :dfn:`ticks` which have a
|
|
configurable duration. A 64-bit *system clock* counts the number of ticks
|
|
that have elapsed since the kernel started executing.
|
|
|
|
Zephyr also supports a higher-resolution *hardware clock*, which can be used
|
|
to measure durations requiring sub-tick interval precision.
|
|
|
|
The nanokernel allows a fiber or thread to perform time-based processing
|
|
based on the system clock. This can be done either by using a nanokernel API
|
|
that supports a *timeout* argument, or by using a *timer* object that can
|
|
be set to expire after a specified number of ticks.
|
|
|
|
The microkernel also allows tasks to perform time-based processing using
|
|
timeouts and timers. Microkernel timers have additional capabilities
|
|
not provided by nanokernel timers, such as a periodic expiration mode.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`common_kernel_clocks`
|
|
* :ref:`Nanokernel Timer Services <nanokernel_timers>`
|
|
* :ref:`Microkernel Timers Services <microkernel_timers>`
|
|
|
|
Synchronization
|
|
***************
|
|
|
|
The Zephyr kernel provides four types of objects that allow different
|
|
contexts to synchronize their execution.
|
|
|
|
The microkernel provides the object types listed below. These types
|
|
are intended for tasks, with limited ability to be used by fibers and ISRs.
|
|
|
|
* A :dfn:`semaphore` is a counting semaphore, which indicates how many units
|
|
of a particular resource are available.
|
|
|
|
* An :dfn:`event` is a binary semaphore, which simply indicates if a particular
|
|
resource is available or not.
|
|
|
|
* A :dfn:`mutex` is a reentrant mutex with priority inversion protection. It is
|
|
similar to a binary semaphore, but contains additional logic to ensure that
|
|
only the owner of the associated resource can release it and to expedite the
|
|
execution of a lower priority thread that holds a resource needed by a
|
|
higher priority thread.
|
|
|
|
The nanokernel provides the object type listed below. This type
|
|
is intended for fibers, with only limited ability to be used by tasks and ISRs.
|
|
|
|
* A :dfn:`nanokernel semaphore` is a counting semaphore that indicates
|
|
how many units of a particular resource are available.
|
|
|
|
Each type has specific capabilities and limitations that affect suitability
|
|
of use in a given situation. For more details, see the documentation for each
|
|
specific object type.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`Microkernel Synchronization Services <microkernel_synchronization>`
|
|
* :ref:`Nanokernel Synchronization Services <nanokernel_synchronization>`
|
|
|
|
Data Passing
|
|
************
|
|
|
|
The Zephyr kernel provides six types of objects that allow different
|
|
contexts to exchange data.
|
|
|
|
The microkernel provides the object types listed below. These types are
|
|
designed to be used by tasks, and cannot be used by fibers and ISRs.
|
|
|
|
* A :dfn:`microkernel FIFO` is a queuing mechanism that allows tasks to exchange
|
|
fixed-size data items in an asychronous :abbr:`First In, First Out (FIFO)`
|
|
manner.
|
|
|
|
* A :dfn:`mailbox` is a queuing mechanism that allows tasks to exchange
|
|
variable-size data items in a synchronous, "first in, first out" manner.
|
|
Mailboxes also support asynchronous exchanges, and allow tasks to exchange
|
|
messages both anonymously and non-anonymously using the same mailbox.
|
|
|
|
* A :dfn:`pipe` is a queuing mechanism that allows a task to send a stream
|
|
of bytes to another task. Both asynchronous and synchronous exchanges
|
|
can be supported by a pipe.
|
|
|
|
The nanokernel provides the object types listed below. These types are
|
|
primarily designed to be used by fibers, and have only a limited ability
|
|
to be used by tasks and ISRs.
|
|
|
|
* A :dfn:`nanokernel FIFO` is a queuing mechanism that allows contexts to exchange
|
|
variable-size data items in an asychronous, first-in, first-out manner.
|
|
|
|
* A :dfn:`nanokernel LIFO` is a queuing mechanism that allows contexts to exchange
|
|
variable-size data items in an asychronous, last-in, first-out manner.
|
|
|
|
* A :dfn:`nanokernel stack` is a queuing mechanism that allows contexts to exchange
|
|
32-bit data items in an asynchronous first-in, first-out manner.
|
|
|
|
Each of these types has specific capabilities and limitations that affect
|
|
suitability for use in a given situation. For more details, see the
|
|
documentation for each specific object type.
|
|
|
|
Related sections:
|
|
|
|
* :ref:`Microkernel Data Passing Services <microkernel_data>`
|
|
* :ref:`Nanokernel Data Passing Services <nanokernel_data>`
|
|
|
|
Dynamic Memory Allocation
|
|
*************************
|
|
|
|
The Zephyr kernel requires all system resources to be defined at compile-time,
|
|
and therefore provides only limited support for dynamic memory allocation.
|
|
This support can be used in place of C standard library calls like
|
|
:c:func:`malloc()` and :c:func:`free()`, albeit with certain differences.
|
|
|
|
The microkernel provides two types of objects that allow tasks to dynamically
|
|
allocate memory blocks. These object types cannot be used by fibers or ISRs.
|
|
|
|
* A :dfn:`memory map` is a memory region that supports the dynamic allocation and
|
|
release of memory blocks of a single fixed size. An application can have
|
|
multiple memory maps, whose block size and block capacity can be configured
|
|
individually.
|
|
|
|
* A :dfn:`memory pool` is a memory region that supports the dynamic allocation and
|
|
release of memory blocks of multiple fixed sizes. This allows more efficient
|
|
use of available memory when an application requires blocks of different
|
|
sizes. An application can have multiple memory pools, whose block sizes
|
|
and block capacity can be configured individually.
|
|
|
|
The nanokernel does not provide any support for dynamic memory allocation.
|
|
|
|
For additional information see:
|
|
|
|
* :ref:`Microkernel Memory Maps <microkernel_memory_maps>`
|
|
* :ref:`Microkernel Pools <microkernel_memory_pools>`
|
|
|
|
Public and Private Microkernel Objects
|
|
**************************************
|
|
|
|
Microkernel objects, such as semaphores, mailboxes, or tasks,
|
|
can usually be defined as a public object or a private object.
|
|
|
|
* A :dfn:`public object` is one that is available for general use by all parts
|
|
of the application. Any code that includes :file:`zephyr.h` can interact
|
|
with the object by referencing the object's name.
|
|
|
|
* A :dfn:`private object` is one that is intended for use only by a specific
|
|
part of the application, such as a single device driver or subsystem.
|
|
The object's name is visible only to code within the file where the object
|
|
is defined, hiding it from general use unless the code which defined the
|
|
object takes additional steps to share the name with other files.
|
|
|
|
Aside from the way they are defined, and the resulting visibility of
|
|
the object's name, a public object and a private object of the same type
|
|
operate in exactly the same manner using the same set of APIs.
|
|
|
|
In most cases, the decision to make a given microkernel object a public
|
|
object or a private object is simply a matter of convenience. For example,
|
|
when defining a server-type subsystem that handles requests from multiple
|
|
clients, it usually makes sense to define public objects.
|
|
|
|
.. note::
|
|
Nanokernel object types can only be defined as private objects. This means
|
|
a nanokernel object must be defined using a global variable to allow it to
|
|
be accessed by code outside the file in which the object is defined.
|
|
|
|
|
|
.. _microkernel_server:
|
|
|
|
Microkernel Server
|
|
******************
|
|
|
|
The microkernel performs most operations involving microkernel objects
|
|
using a special *microkernel server* fiber, called :c:func:`_k_server`.
|
|
|
|
When a task invokes an API associated with a microkernel object type,
|
|
such as :c:func:`task_fifo_put()`, the associated operation is not
|
|
carried out directly. Instead, the following sequence of steps typically
|
|
occurs:
|
|
|
|
#. The task creates a *command packet*, which contains the input parameters
|
|
needed to carry out the desired operation.
|
|
|
|
#. The task queues the command packet on the microkernel server's
|
|
*command stack*. The kernel then preempts the task and schedules
|
|
the microkernel server.
|
|
|
|
#. The microkernel server dequeues the command packet from its command
|
|
stack and performs the desired operation. All output parameters for the
|
|
operation, such as the return code, are saved in the command packet.
|
|
|
|
#. When the operation is complete the microkernel server attempts
|
|
to fetch a command packet from its now empty command stack
|
|
and becomes blocked. The kernel then schedules the requesting task.
|
|
|
|
#. The task processes the command packet's output parameters to determine
|
|
the results of the operation.
|
|
|
|
The actual sequence of steps may vary from the above guideline in some
|
|
instances. For example, if the operation causes a higher-priority task
|
|
to become runnable, the requesting task is not scheduled for execution by
|
|
the kernel until *after* the higher priority task is first scheduled.
|
|
In addition, a few operations involving microkernel objects do not require
|
|
the use of a command packet at all.
|
|
|
|
While this indirect execution approach may seem somewhat inefficient,
|
|
it actually has a number of important benefits:
|
|
|
|
* All operations performed by the microkernel server are inherently free
|
|
from race conditions; operations are processed serially by a single fiber
|
|
that cannot be preempted by tasks or other fibers. This means the
|
|
microkernel server can manipulate any of the microkernel objects in the
|
|
system during any operation without having to take additional steps
|
|
to prevent interference by other contexts.
|
|
|
|
* Microkernel operations have minimal impact on interrupt latency;
|
|
interrupts are never locked for a significant period to prevent race
|
|
conditions.
|
|
|
|
* The microkernel server can easily decompose complex operations into two or
|
|
more simpler operations by creating additional command packets and queueing
|
|
them on the command stack.
|
|
|
|
* The overall memory footprint of the system is reduced; a task using microkernel
|
|
objects only needs to provide stack space for the first step of the above sequence,
|
|
rather than for all steps required to perform the operation.
|
|
|
|
For additional information see:
|
|
|
|
* :ref:`Microkernel Server Fiber <microkernel_server_fiber>`
|
|
|
|
Standard C Library
|
|
******************
|
|
|
|
The Zephyr kernel currently provides only the minimal subset of the
|
|
standard C library required to meet the kernel's own needs, primarily
|
|
in the areas of string manipulation and display.
|
|
|
|
Applications that require a more extensive C library can either submit
|
|
contributions that enhance the existing library or substitute
|
|
a replacement library.
|
|
|
|
C++ Support for Applications
|
|
****************************
|
|
|
|
The Zephyr kernel supports applications written in both C and C++. However, to
|
|
use C++ in an application, you must configure your kernel to include C++
|
|
support and the build system must select the correct compiler.
|
|
|
|
The build system selects the C++ compiler based on the suffix of the files.
|
|
Files identified with either a **cxx** or a **cpp** suffix are compiled using
|
|
the C++ compiler. For example, :file:`myCplusplusApp.cpp`.
|
|
|
|
The Zephyr kernel currently provides only a subset of C++ functionality. The
|
|
following features are not supported:
|
|
|
|
* Dynamic object management with the **new** and **delete** operators
|
|
* :abbr:`RTTI (run-time type information)`
|
|
* Exceptions
|
|
* Static global object destruction
|
|
|
|
While not an exhaustive list, support for the following functionality is
|
|
included:
|
|
|
|
* Inheritance
|
|
* Virtual functions
|
|
* Virtual tables
|
|
* Static global object constructors
|
|
|
|
Static global object constructors are initialized after the drivers are
|
|
initialized but before the application :c:func:`main()` function. Therefore,
|
|
use of C++ is restricted to application code.
|
|
|
|
.. note::
|
|
Do not use C++ for kernel, driver, or system initialization code.
|