Corrected a few typos and misspellings. Corrected grammar to add clarity to the section on task groups. Added cleaner headings to APIs to enhance readability. Change-Id: I6ff4d447775db67dc7008aa30bd2bbb4ab6c32de Signed-off-by: L.S. Cook <leonax.cook@intel.com>
537 lines
No EOL
18 KiB
ReStructuredText
537 lines
No EOL
18 KiB
ReStructuredText
.. _microkernel_tasks:
|
|
|
|
Task Services
|
|
#############
|
|
|
|
Concepts
|
|
********
|
|
|
|
A task is a preemptible thread of execution that implements a portion of
|
|
an application's processing. It is normally used for processing that
|
|
is too lengthy or too complex to be performed by a fiber or an ISR.
|
|
|
|
A microkernel application can define any number of application tasks. Each
|
|
task has a name that uniquely identifies it, allowing it to be directly
|
|
referenced by other tasks. For each microkernel task, the following
|
|
properties must be specified:
|
|
|
|
* A **memory region** to be used for stack and execution context information.
|
|
* A **function** to be invoked when the task starts executing.
|
|
* The **priority** to be used by the microkernel scheduler.
|
|
|
|
A task's entry point function takes no arguments, so there is no need to
|
|
define any argument values for it.
|
|
|
|
The microkernel automatically defines a system task, known as the *idle task*,
|
|
at the lowest priority. This task is used during system initialization,
|
|
and subsequently executes only when there is no other work for the system to do.
|
|
The idle task is anonymous and must not be referenced by application tasks.
|
|
|
|
.. note::
|
|
A nanokernel application can define only a single application task, known
|
|
as the *background task*, which is very different from the microkernel tasks
|
|
described in this section. For more information see
|
|
:ref:`Nanokernel Task Services <nanokernel_tasks>`.
|
|
|
|
Task Lifecycle
|
|
==============
|
|
|
|
The kernel automatically starts a task during system initialization if the task
|
|
belongs to the :c:macro:`EXE` task group; see `Task Groups`_ below.
|
|
A task that is not started automatically must be started by another task
|
|
using :c:func:`task_start()`.
|
|
|
|
Once a task is started it normally executes forever. A task may terminate
|
|
gracefully by simply returning from its entry point function. If it does,
|
|
it is the task's responsibility to release any system resources it may own
|
|
(such as mutexes and dynamically allocated memory blocks) prior to returning,
|
|
since the kernel does *not* attempt to reclaim them so they can be reused.
|
|
|
|
A task may also terminate non-gracefully by *aborting*. The kernel
|
|
automatically aborts a task when it generates a fatal error condition,
|
|
such as dereferencing a null pointer. A task can also be explicitly aborted
|
|
using :c:func:`task_abort()`. As with graceful task termination,
|
|
the kernel does not attempt to reclaim system resources owned by the task.
|
|
|
|
A task may optionally register an *abort handler function* to be invoked
|
|
by the kernel when the task terminates (including during graceful termination).
|
|
The abort handler can be used to record information about the terminating
|
|
task or to assist in reclaiming system resources owned by the task. The abort
|
|
handler function is invoked by the microkernel server fiber, so it cannot
|
|
directly call kernel APIs that must be invoked by a task; instead, it must
|
|
coordinate with another task to invoke such APIs indirectly.
|
|
|
|
.. note::
|
|
The kernel does not currently make any claims regarding an application's
|
|
ability to restart a terminated task.
|
|
|
|
Task Scheduling
|
|
===============
|
|
|
|
The microkernel's scheduler selects which of the system's tasks is allowed
|
|
to execute; this task is known as the *current task*. The nanokernel's scheduler
|
|
permits the current task to execute only when no fibers or ISRs are available
|
|
to execute; fiber and ISR executions always take precedence.
|
|
|
|
When prompted for a context switch to a different task, fiber, or ISR, the kernel
|
|
automatically saves the current task's CPU register values; these values get
|
|
restored when the task later resumes execution.
|
|
|
|
Task State
|
|
----------
|
|
|
|
Each microkernel task has an associated *state* that determines whether or not
|
|
it can be scheduled for execution. The state records factors that could prevent
|
|
the task from executing, such as:
|
|
|
|
* The task has not been started.
|
|
* The task is waiting (for a semaphore, for a timeout, ...).
|
|
* The task has been suspended.
|
|
* The task has terminated.
|
|
|
|
A task whose state has no factors that prevent its execution is said to be
|
|
*executable*.
|
|
|
|
Task Priorities
|
|
---------------
|
|
|
|
A microkernel application can be configured to support any number of task
|
|
priority levels using the :option:`NUM_TASK_PRIORITIES` configuration option.
|
|
|
|
An application task can have any priority from 0 (highest priority) down to
|
|
:option:`NUM_TASK_PRIORITIES`-2. The lowest priority level,
|
|
:option:`NUM_TASK_PRIORITIES`-1, is reserved for the microkernel's idle task.
|
|
|
|
A task's original priority can be altered up or down after the task has been
|
|
started.
|
|
|
|
Scheduling Algorithm
|
|
--------------------
|
|
|
|
The microkernel's scheduler always selects the highest priority executable task
|
|
to be the current task. When multiple executable tasks of the same priority are
|
|
available, the scheduler chooses the one that has been waiting longest.
|
|
|
|
Once a task becomes the current task it remains scheduled for execution
|
|
by the microkernel until one of the following occurs:
|
|
|
|
* The task is supplanted by a higher-priority task that becomes ready to
|
|
execute.
|
|
|
|
* The task is supplanted by an equal-priority task that is ready to execute,
|
|
either because the current task explicitly calls :c:func:`task_yield()`
|
|
or because the kernel implicitly calls :c:func:`task_yield()` after the
|
|
scheduler's time slice expired.
|
|
|
|
* The task is supplanted by an equal or lower-priority task that is ready
|
|
to execute because the current task called a kernel API that blocked its
|
|
own execution. For example, the task attempted to take a semaphore that
|
|
was unavailable.
|
|
|
|
* The task terminates itself by returning from its entry point function.
|
|
|
|
* The task aborts itself by performing an operation that causes a fatal error,
|
|
or by calling :c:func:`task_abort()`.
|
|
|
|
Time Slicing
|
|
------------
|
|
|
|
The microkernel's scheduler supports an optional time slicing capability
|
|
that prevents a task from monopolizing the CPU when other tasks of the
|
|
same priority are ready to execute.
|
|
|
|
The scheduler divides time into a series of *time slices*, where slices
|
|
are measured in system clock ticks. The time slice size is specified with
|
|
the :option:`TIMESLICE_SIZE` configuration option, but this size can also
|
|
be changed dynamically, while the application is running.
|
|
|
|
At the end of every time slice, the scheduler implicitly invokes
|
|
:c:func:`task_yield()` on behalf of the current task; this gives
|
|
any other task of that priority the opportunity to execute before the
|
|
current task can once again be scheduled. If one or more equal-priority
|
|
tasks are ready to execute, the current task is preempted to allow those
|
|
tasks to execute. If no tasks of equal priority are ready to execute,
|
|
the current task remains the current task, and it continues to execute.
|
|
|
|
Tasks with a priority higher than that specified by the
|
|
:option:`TIMESLICE_PRIORITY` configuration option are exempt from time
|
|
slicing, and are never preempted by a task of equal priority. This
|
|
capability allows an application to use time slicing only for lower
|
|
priority tasks that are less time-sensitive.
|
|
|
|
.. note::
|
|
The microkernel's time slicing algorithm does *not* ensure that a set
|
|
of equal-priority tasks will receive an equitable amount of CPU time,
|
|
since it does not measure the amount of time a task actually gets to
|
|
execute. For example, a task may become the current task just before
|
|
the end of a time slice and then immediately have to yield the CPU.
|
|
On the other hand, the microkernel's scheduler *does* ensure that a task
|
|
never executes for longer than a single time slice without being required
|
|
to yield.
|
|
|
|
Task Suspension
|
|
---------------
|
|
|
|
The microkernel allows a task to be *suspended*, which prevents the task
|
|
from executing for an indefinite period of time. The :c:func:`task_suspend()`
|
|
API allows an application task to suspend any other task, including itself.
|
|
Suspending a task that is already suspended has no additional effect.
|
|
|
|
Once suspended, a task cannot be scheduled until another task calls
|
|
:c:func:`task_resume()` to remove the suspension.
|
|
|
|
.. note::
|
|
A task can prevent itself from executing for a specified period of time
|
|
using :c:func:`task_sleep()`. However, this is different from suspending
|
|
a task since a sleeping task becomes executable automatically when the
|
|
time limit is reached.
|
|
|
|
Task Groups
|
|
===========
|
|
|
|
The kernel allows a set of related tasks, known as a *task group*, to be
|
|
manipulated as a single unit, rather than individually. This simplifies
|
|
the work required to start related tasks, to suspend and resume them, or
|
|
to abort them.
|
|
|
|
The kernel supports a maximum of 32 distinct task groups. Each task group
|
|
has a name that uniquely identifies it, allowing it to be directly referenced
|
|
by tasks.
|
|
|
|
The task groups a task belongs to are specified when the task is defined.
|
|
A task may belong to a single task group, to multiple task groups, or to
|
|
no task group. A task's group memberships can also be changed dynamically
|
|
while the application is running.
|
|
|
|
The task group designations listed below are pre-defined by the kernel;
|
|
additional task groups can be defined by the application.
|
|
|
|
:c:macro:`EXE`
|
|
This task group is started automatically by the kernel during system
|
|
intialization.
|
|
|
|
:c:macro:`SYS`
|
|
This task group is a set of system tasks that continues to execute
|
|
during system debugging.
|
|
|
|
:c:macro:`FPU`
|
|
This task group is a set of tasks that requires the kernel to save
|
|
x87 FPU and MMX floating point context information during context switches.
|
|
|
|
:c:macro:`SSE`
|
|
This task group is a set of tasks that requires the kernel to save SSE
|
|
floating point context information during context switches. (Tasks with
|
|
this group designation are implicitly members of the :c:macro:`FPU` task
|
|
group too.)
|
|
|
|
Usage
|
|
*****
|
|
|
|
Defining a Task
|
|
===============
|
|
|
|
The following parameters must be defined:
|
|
|
|
*name*
|
|
This specifies a unique name for the task.
|
|
|
|
*priority*
|
|
This specifies the scheduling priority of the task.
|
|
|
|
*entry_point*
|
|
This specifies the name of the task's entry point function,
|
|
which should have the following form:
|
|
|
|
.. code-block:: c
|
|
|
|
void <entry_point>(void)
|
|
{
|
|
/* task mainline processing */
|
|
...
|
|
/* (optional) normal task termination */
|
|
return;
|
|
}
|
|
|
|
*stack_size*
|
|
This specifies the size of the memory region used for the task's
|
|
stack and for other execution context information, in bytes.
|
|
|
|
*groups*
|
|
This specifies the task groups the task belongs to.
|
|
|
|
Public Task
|
|
-----------
|
|
|
|
Define the task in the application's MDEF using the following syntax:
|
|
|
|
.. code-block:: console
|
|
|
|
TASK name priority entry_point stack_size groups
|
|
|
|
The task groups are specified using a comma-separated list of task group names
|
|
enclosed in square brackets, with no embedded spaces. If the task does not
|
|
belong to any task group, specify an empty list; i.e. :literal:`[]`.
|
|
|
|
For example, the file :file:`projName.mdef` defines a system comprised
|
|
of six tasks as follows:
|
|
|
|
.. code-block:: console
|
|
|
|
% TASK NAME PRIO ENTRY STACK GROUPS
|
|
% ===================================================================
|
|
TASK MAIN_TASK 6 keypad_main 1024 [KEYPAD_TASKS,EXE]
|
|
TASK PROBE_TASK 2 probe_main 400 []
|
|
TASK SCREEN1_TASK 8 screen_1_main 4096 [VIDEO_TASKS]
|
|
TASK SCREEN2_TASK 8 screen_2_main 4096 [VIDEO_TASKS]
|
|
TASK SPEAKER1_TASK 10 speaker_1_main 1024 [AUDIO_TASKS]
|
|
TASK SPEAKER2_TASK 10 speaker_2_main 1024 [AUDIO_TASKS]
|
|
|
|
A public task can be referenced by name from any source file that includes
|
|
the file :file:`zephyr.h`.
|
|
|
|
Private Task
|
|
------------
|
|
|
|
Define the task in a source file using the following syntax:
|
|
|
|
.. code-block:: c
|
|
|
|
DEFINE_TASK(PRIV_TASK, priority, entry, stack_size, groups);
|
|
|
|
The task groups are specified using a list of task group names separated by
|
|
:literal:`|`; i.e. the logical OR operator. If the task does not belong to any
|
|
task group specify NULL.
|
|
|
|
For example, the following code can be used to define a private task named
|
|
``PRIV_TASK``.
|
|
|
|
.. code-block:: c
|
|
|
|
DEFINE_TASK(PRIV_TASK, 10, priv_task_main, 800, EXE);
|
|
|
|
To utilize this task from a different source file use the following syntax:
|
|
|
|
.. code-block:: c
|
|
|
|
extern const ktask_t PRIV_TASK;
|
|
|
|
Defining a Task Group
|
|
=====================
|
|
|
|
The following parameters must be defined:
|
|
|
|
*name*
|
|
This specifies a unique name for the task group.
|
|
|
|
Public Task Group
|
|
-----------------
|
|
|
|
Define the task group in the application's .MDEF file using the following
|
|
syntax:
|
|
|
|
.. code-block:: console
|
|
|
|
TASKGROUP name
|
|
|
|
For example, the file :file:`projName.mdef` defines three new task groups
|
|
as follows:
|
|
|
|
.. code-block:: console
|
|
|
|
% TASKGROUP NAME
|
|
% ========================
|
|
TASKGROUP VIDEO_TASKS
|
|
TASKGROUP AUDIO_TASKS
|
|
TASKGROUP KEYPAD_TASKS
|
|
|
|
A public task group can be referenced by name from any source file that
|
|
includes the file :file:`zephyr.h`.
|
|
|
|
.. note::
|
|
Private task groups are not supported by the Zephyr kernel.
|
|
|
|
Example: Starting a Task from Another Task
|
|
==========================================
|
|
|
|
This code shows how the currently-executing task can start another task.
|
|
|
|
.. code-block:: c
|
|
|
|
void keypad_main(void)
|
|
{
|
|
/* begin system initialization */
|
|
...
|
|
|
|
/* start task to monitor temperature */
|
|
task_start(PROBE_TASK);
|
|
|
|
/* continue to bring up and operate system */
|
|
...
|
|
}
|
|
|
|
Example: Suspending and Resuming a Set of Tasks
|
|
===============================================
|
|
|
|
This code shows how the currently-executing task can temporarily suspend
|
|
the execution of all tasks belonging to the designated task groups.
|
|
|
|
.. code-block:: c
|
|
|
|
void probe_main(void)
|
|
{
|
|
int was_overheated = 0;
|
|
|
|
/* continuously monitor temperature */
|
|
while (1) {
|
|
now_overheated = overheating_update();
|
|
|
|
/* suspend non-essential tasks when overheating is detected */
|
|
if (now_overheated && !was_overheated) {
|
|
task_group_suspend(VIDEO_TASKS
|
|
AUDIO_TASKS);
|
|
was_overheated = 1;
|
|
}
|
|
|
|
/* resume non-essential tasks when overheating abates */
|
|
if (!now_overheated && was_overheated) {
|
|
task_group_resume(VIDEO_TASKS
|
|
AUDIO_TASKS);
|
|
was_overheated = 0;
|
|
}
|
|
|
|
/* wait 10 ticks of system clock before checking again */
|
|
task_sleep(10);
|
|
}
|
|
}
|
|
|
|
Example: Offloading Work to the Microkernel Server Fiber
|
|
========================================================
|
|
|
|
This code shows how the currently-executing task can perform critical section
|
|
processing by offloading it to the microkernel server. Since the critical
|
|
section function is being executed by a fiber, once the function begins
|
|
executing it cannot be interrupted by any other fiber or task that wants
|
|
to log an alarm.
|
|
|
|
.. code-block:: c
|
|
|
|
/* alarm logging subsystem */
|
|
|
|
#define MAX_ALARMS 100
|
|
|
|
struct alarm_info alarm_log[MAX_ALARMS];
|
|
int num_alarms = 0;
|
|
|
|
int log_an_alarm(struct alarm_info *new_alarm)
|
|
{
|
|
/* ensure alarm log isn't full */
|
|
if (num_alarms == MAX_ALARMS) {
|
|
return 0;
|
|
}
|
|
|
|
/* add new alarm to alarm log */
|
|
alarm_info[num_alarms] = *new_alarm;
|
|
num_alarms++;
|
|
|
|
/* pass back alarm identifier to indicate successful logging */
|
|
return num_alarms;
|
|
}
|
|
|
|
/* task that generates an alarm */
|
|
|
|
void XXX_main(void)
|
|
{
|
|
struct alarm_info my_alarm = { ... };
|
|
|
|
...
|
|
|
|
/* record alarm in system's database */
|
|
if (task_offload_to_fiber(log_an_alarm, &my_alarm) == 0) {
|
|
printf("Unable to log alarm!");
|
|
}
|
|
|
|
...
|
|
}
|
|
|
|
APIs
|
|
****
|
|
|
|
All of the following Microkernel APIs are provided by :file:`microkernel.h`.
|
|
|
|
APIs Affecting the Currently-Executing Task
|
|
===========================================
|
|
|
|
:cpp:func:`task_id_get()`
|
|
Gets the task's ID.
|
|
|
|
:c:func:`isr_task_id_get()`
|
|
Gets the task's ID from an ISR.
|
|
|
|
:cpp:func:`task_priority_get()`
|
|
Gets the task's priority.
|
|
|
|
:c:func:`isr_task_priority_get()`
|
|
Gets the task's priority from an ISR.
|
|
|
|
:cpp:func:`task_group_mask_get()`
|
|
Gets the task's group memberships.
|
|
|
|
:c:func:`isr_task_group_mask_get()`
|
|
Gets the task's group memberships from an ISR.
|
|
|
|
:cpp:func:`task_abort_handler_set()`
|
|
Installs the task's abort handler.
|
|
|
|
:cpp:func:`task_yield()`
|
|
Yields CPU to equal-priority tasks.
|
|
|
|
:cpp:func:`task_sleep()`
|
|
Yields CPU for a specified time period.
|
|
|
|
:cpp:func:`task_offload_to_fiber()`
|
|
Instructs the microkernel server fiber to execute a function.
|
|
|
|
APIs Affecting a Specified Task
|
|
===============================
|
|
|
|
:cpp:func:`task_priority_set()`
|
|
Sets a task's priority.
|
|
|
|
:cpp:func:`task_entry_set()`
|
|
Sets a task's entry point.
|
|
|
|
:c:func:`task_start()`
|
|
Starts execution of a task.
|
|
|
|
:c:func:`task_suspend()`
|
|
Suspends execution of a task.
|
|
|
|
:c:func:`task_resume()`
|
|
Resumes execution of a task.
|
|
|
|
:c:func:`task_abort()`
|
|
Aborts execution of a task.
|
|
|
|
:cpp:func:`task_group_join()`
|
|
Adds a task to the specified task group(s).
|
|
|
|
:cpp:func:`task_group_leave()`
|
|
Removes a task from the specified task group(s).
|
|
|
|
APIs Affecting Multiple Tasks
|
|
=============================
|
|
|
|
:cpp:func:`sys_scheduler_time_slice_set()`
|
|
Sets the time slice period used in round-robin task scheduling.
|
|
|
|
:c:func:`task_group_start()`
|
|
Starts execution of all tasks in the specified task groups.
|
|
|
|
:c:func:`task_group_suspend()`
|
|
Suspends execution of all tasks in the specified task groups.
|
|
|
|
:c:func:`task_group_resume()`
|
|
Resumes execution of all tasks in the specified task groups.
|
|
|
|
:c:func:`task_group_abort()`
|
|
Aborts execution of all tasks in the specified task groups. |