HALFRED
0.4.0
|
A unified interface to OS tasks (threads).
The following chapter will guide you through the topics of creating and managing OS threads, that this API calls 'tasks'.
The HALFRED's task API delivers what is mostly expected as a standard when it comes to threads. However in addition it also goes a step further, delivering more convenient API calls than usually the OS itself. These extended API require some user collaboration and understanding in order to work. It may also not fit every purpose. Let's first explore the basic concepts.
Basic tasks.
The easiest way to create a task is to call the OSTASK_Create function. The user code is expected to provide:
The following example illustrates this basic concept:
Note, that the last two arguments of the OSTASK_Create function in the above example mean that the task will be created with a default (minimal) stack size and the argument passed to myTask function will be NULL.
The above simple code already exposes two issues with the basic task:
To address these issues the OSTask API includes the OSTASK_Join function. However, this API call can only be used on a task with the 'joinable' feature enabled.
Joinable task
A joinable task is a task, that someone waits for - meaning that there will always be other task waiting for the joinable task to end. Let us emphasize this: the resources taken by the a joinable thread will only be freed if some other task (or main thread) successfully calls the OSTASK_Join function against that task. Otherwise memory and resource leakage may happen.
To create a task with extended features (such as joinable) we use the OSTASK_Create function with HAL_OSTASK_JOINABLE mentioned in the task options argument. The following example illustrates the usage of joinable task.
Mortal task
Many times a task will be responsible for carrying out work in a loop. It is often feasible to have a mechanism that can break that loop, causing the task to reach its end, by the means of a signal, passed from another task. This is particularly easy to imagine in tasks following a single main loop architecture. If that is your case then you may consider using the 'mortal' feature of the OSTask API for this purpose.
If you create a thread with a 'mortal' feature enabled, two additional calls functions be available to you:
Here's the example:
As you can see, in the above example we have combined the 'mortal' feature with the 'joinable' feature discussed above. These two will often come in pair, however of course they can also be used separately. In the above example the task loop periodically checks the 'alive' condition by calling the OSTASK_IsAlive function. This function checks this condition against the calling task, so no task handle is needed for it to work. After receiving the kill signal from the main thread, the condition will become false, thus eventually ending the task loop. The main thread then joins with the task. Note, that the OSTASK_Join timeout should be set accordingly to the possible task loop inertion.
Sleepy task
Let's explore the task main loop even more. Many times it may happen, that the code inside the main task loop should only run is a specified condition occurs. Otherwise, the task may as well do nothing. We will call such tasks 'sleepy', meaning that the task becomes active when it is woken up. Other times it may sleep. As we will see, it will be possible to implement both the deep sleeping tasks that NEVER do anything until woken up and shallow sleeping task, that periodically wake up to do something but also wait for the wakeup.
The example below presents a sleepy task, that wakes up every 3000 system ticks, or on demand:
Expected result:
Note that each OSTASK_WakeUp call causes one wake up event in the task main loop. So why there are three 'Task woken up' events in the program output ? The third wake up event is related to the OSTASK_Kill which also wakes up the sleepy task to speed up its termination.
Checking task creation an task features
In order to check, if the task was successfully created you can always examine the task handle using the OSTASK_IsValid function:
You can alway check what features are enabled for any given task using the OSTASK_IsJoinable, OSTASK_IsMortal and OSTASK_IsSleepy functions.
Terminating tasks
You should always design your tasks in a way, that allows them to terminate in a safe and controlled way, freeing all acquired memory and other resources. This can be easily achieved using the 'mortal' feature of the tasks described above. The OSTask API provides the OSTASK_Destroy function, that terminates the given task using the termination feature available in the underlying OS. Most of the time this however is highly dangerous and definitely NOT RECOMMENDED way of terminating task.
Sometimes you may be tempted to use the OSTASK_Destroy function as the last call of your task. This may be a habit developed while using operating systems that require such step to end a thread (for example FreeRTOS). DON'T DO THAT. The OSTask API allows the task implementation function simply to return a result - no matter what is the underlying OS.
Task priorities
The OSTask abstraction allows to specify 5 priority levels for each task, as described by the OSTaskPriority enum.
The Task priority is set during task creation. It can later be changed by a call to OSTASK_SetPriority function.
At any time the task priority can be queried using the OSTASK_GetPriority function.
Suspending and resuming tasks
To suspend a task use the OSTASK_Suspend. To resume it again use OSTASK_Resume.
Macros | |
#define | HAL_OSTASK_BASIC 0 |
#define | HAL_OSTASK_JOINABLE 1 |
#define | HAL_OSTASK_MORTAL 2 |
#define | HAL_OSTASK_SLEEPY 4 |
Typedefs | |
typedef int(* | OSTaskFunction )(void *arg) |
Type that defines the task function. | |
typedef struct OSTaskDesc * | OSTask |
Type describing task handle that identifies a task. | |
Enumerations | |
enum | OSTaskPriority { HAL_OSTASK_PRIORITY_NORMAL = 0, HAL_OSTASK_PRIORITY_LOWEST, HAL_OSTASK_PRIORITY_LOW, HAL_OSTASK_PRIORITY_HIGH, HAL_OSTASK_PRIORITY_HIGHEST } |
Possible task priorities. More... | |
Functions | |
OSTask | OSTASK_Create (OSTaskFunction taskFunc, int features, OSTaskPriority priority, size_t stack_size, void *arg) |
bool | OSTASK_IsValid (OSTask task) |
bool | OSTASK_IsJoinable (OSTask task) |
bool | OSTASK_IsMortal (OSTask task) |
bool | OSTASK_IsSleepy (OSTask task) |
bool | OSTASK_Join (OSTask task, OSTime timeout, int *result) |
void | OSTASK_Kill (OSTask task) |
bool | OSTASK_IsAlive () |
void | OSTASK_Wakeup (OSTask task) |
bool | OSTASK_SleepIfNotWoken (OSTime maxSleepTime) |
void | OSTASK_Destroy (OSTask task) |
bool | OSTASK_Suspend (OSTask task) |
bool | OSTASK_Resume (OSTask task) |
void | OSTASK_Yield (void) |
OSTaskPriority | OSTASK_GetPriority (OSTask task) |
bool | OSTASK_SetPriority (OSTask task, OSTaskPriority priority) |
OSTask | OSTASK_GetCurrentTask (void) |
enum OSTaskPriority |
OSTask OSTASK_Create | ( | OSTaskFunction | taskFunc, |
int | features, | ||
OSTaskPriority | priority, | ||
size_t | stack_size, | ||
void * | arg | ||
) |
Creates a new task.
[in] | taskFunc | pointer to the task function |
[in] | features | bit field that defines task features |
[in] | priority | task priority |
[in] | stack_size | stack size (in bytes) |
[in] | arg | task argument |
bool OSTASK_IsValid | ( | OSTask | task | ) |
Checks if a task handle represents a valid task. Can be used to test if OSTASK_Create produced a valid task. Beware, that checking task handle of a task, that has been destroyed (OSTASK_Destroy) or successfully joined (OSTASK_Join) yields undefined behavior. On some ports such task handle may still be reported as valid.
[in] | task | handle of the task to check |
bool OSTASK_IsJoinable | ( | OSTask | task | ) |
Checks if the given task is a joinable task.
[in] | task |
bool OSTASK_IsMortal | ( | OSTask | task | ) |
Checks if the given task is a mortal task.
[in] | task |
bool OSTASK_IsSleepy | ( | OSTask | task | ) |
Checks if the given task is a sleepy task.
[in] | task |
bool OSTASK_Join | ( | OSTask | task, |
OSTime | timeout, | ||
int * | result | ||
) |
This call is only valid for joinable tasks.
It waits for the given task to terminate and retrieves its end result.
[in] | task | handle of the task to join |
[in] | timeout | maximum time allowed to wait for the task to terminate |
[out] | result | place to store the task result value or NULL if the result value should be ignored |
true | if the task is joinable and was terminated in the specified time |
false | if the task has not ended in the given time |
void OSTASK_Kill | ( | OSTask | task | ) |
Sends a kill signal to the given task. This call is only effective on tasks that have been created with the 'mortal' feature enabled.
[in] | task | handle |
bool OSTASK_IsAlive | ( | ) |
This call is only valid when called from within the task implementation function and only for mortal tasks.
It checks if the calling task can continue operation or if it has the kill signal set. If it does (this call returns false) then the task should terminate as soon as possible. It is the responsibility of the task implementation function to periodically check this condition using this function.
true | if the kill signal is not present and the task can still run |
false | if the kill signal for the calling task is set, which means that the task implementation function should terminate |
void OSTASK_Wakeup | ( | OSTask | task | ) |
This call is only valid for sleepy tasks.
It sends the wakeup signal the sleepy task, possibly waking it up.
[in] | task | handle of the task to wake up |
bool OSTASK_SleepIfNotWoken | ( | OSTime | maxSleepTime | ) |
This call is only valid when called from within the task implementation function and only for sleeping tasks.
It puts the current task to sleep for the time specified by the argument. However, this function will exit faster, if some other task wakes it up using OSTASK_Wakeup.
true | if the task was woken up by an external call to OSTASK_Wakeup |
false | if there was no external wake up event - just the sleep time passed |
void OSTASK_Destroy | ( | OSTask | task | ) |
Destroys a task
[in] | task | handle of the task to destroy |
bool OSTASK_Suspend | ( | OSTask | task | ) |
Suspends the execution of a task
[in] | task | handle of the task to suspend |
bool OSTASK_Resume | ( | OSTask | task | ) |
Resumes the execution of a task
void OSTASK_Yield | ( | void | ) |
Causes the calling task to relinquish the CPU
OSTaskPriority OSTASK_GetPriority | ( | OSTask | task | ) |
Gets the priority of a task.
[in] | task | handle of the task |
bool OSTASK_SetPriority | ( | OSTask | task, |
OSTaskPriority | priority | ||
) |
Sets the priority of a task
[in] | task | handle of the task |
[in] | priority | new task priority |
OSTask OSTASK_GetCurrentTask | ( | void | ) |
Retrieves the currently running task