HALFRED  0.4.0
Macros | Typedefs | Enumerations | Functions
OSTask interface

Detailed Description

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:

int myTask(void* arg) {
printf("My task is running.");
return 0;
}
int main(void) {
OSTask myTaskHandle = OSTASK_Create(myTask, HAL_OSTASK_BASIC, HAL_OSTASK_PRIORITY_NORMAL, 0, NULL);
return 0;
}

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.

int myTask(void* arg) {
printf("My task is running...\n");
OS_Sleep(1000);
printf("My task ends with result 123\n");
return 123;
}
int main(void) {
// create the joinable task
OSTask myTaskHandle = OSTASK_Create(myTask, HAL_OSTASK_JOINABLE, HAL_OSTASK_PRIORITY_NORMAL, 0, NULL);
// wait for the joinable task to end and get the end result
int result;
if (OSTASK_Join(myTaskHandle, 5000, &result) {
printf("My task ended with result %d\n", result);
}
return 0;
}

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:

int myTask(void* arg) {
int count = 0;
printf("My task is running in a loop\n");
while (OSTASK_IsAlive()) {
OS_Sleep(1000);
printf(".");
++count;
}
printf("My task went through %d loops\n", count);
return count;
}
int main(void) {
// create the joinable and mortal task
OSTask myTaskHandle = OSTASK_Create(myTask, HAL_OSTASK_JOINABLE | HAL_OSTASK_MORTAL, HAL_OSTASK_PRIORITY_NORMAL, 0, NULL);
// introduce some delay
OS_Sleep(3000);
// send the kill signal
OSTASK_Kill(myTaskHandle);
// wait for the task to end and get the end result
int result;
if (OSTASK_Join(myTaskHandle, 2000, &result) {
printf("My task ended with result %d\n", result);
}
return 0;
}

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:

int myTask(void* arg) {
while (OSTASK_IsAlive()) {
printf("Task woken up\n");
}
printf("Working...\n");
}
printf("Task ended\n");
return 0;
}
int main(void) {
// create the joinable, mortal and sleepy task
OSTask myTaskHandle = OSTASK_Create(myTask, HAL_OSTASK_JOINABLE | HAL_OSTASK_MORTAL | HAL_OSTASK_SLEEPY, HAL_OSTASK_PRIORITY_NORMAL, 0, NULL);
// introduce some delay
OS_Sleep(7000);
// wake up the task two times
OSTASK_WakeUp(myTaskHandle);
OSTASK_WakeUp(myTaskHandle);
// send the kill signal
OSTASK_Kill(myTaskHandle);
// wait for the task to end
OSTASK_Join(myTaskHandle, 1000, NULL);
return 0;
}

Expected result:

Working...
Working...
Task woken up
Task woken up
Task woken up
Task ended

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:

OSTask myTaskHandle = OSTASK_Create(myTask, HAL_OSTASK_PRIORITY_NORMAL, 0, NULL);
if (OSTASK_IsValid(myTaskHandle)) {
// the task was created successfully
} else {
// failed to create a task
}

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 OSTaskDescOSTask
 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)
 

Enumeration Type Documentation

Possible task priorities.

Enumerator
HAL_OSTASK_PRIORITY_NORMAL 

normal priority (default)

HAL_OSTASK_PRIORITY_LOWEST 

lowest priority

HAL_OSTASK_PRIORITY_LOW 

low priority

HAL_OSTASK_PRIORITY_HIGH 

priority higher than normal

HAL_OSTASK_PRIORITY_HIGHEST 

highest priority (possibly most real-time)

Function Documentation

OSTask OSTASK_Create ( OSTaskFunction  taskFunc,
int  features,
OSTaskPriority  priority,
size_t  stack_size,
void *  arg 
)

Creates a new task.

Returns
handle of a newly created task
Parameters
[in]taskFuncpointer to the task function
[in]featuresbit field that defines task features
[in]prioritytask priority
[in]stack_sizestack size (in bytes)
[in]argtask 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.

Parameters
[in]taskhandle of the task to check
Returns
true if task is valid or false if not
bool OSTASK_IsJoinable ( OSTask  task)

Checks if the given task is a joinable task.

Parameters
[in]task
Returns
true if the task is joinable, false otherwise
bool OSTASK_IsMortal ( OSTask  task)

Checks if the given task is a mortal task.

Parameters
[in]task
Returns
true if the task is mortal, false otherwise
bool OSTASK_IsSleepy ( OSTask  task)

Checks if the given task is a sleepy task.

Parameters
[in]task
Returns
true if the task is sleepy, false otherwise
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.

Parameters
[in]taskhandle of the task to join
[in]timeoutmaximum time allowed to wait for the task to terminate
[out]resultplace to store the task result value or NULL if the result value should be ignored
Return values
trueif the task is joinable and was terminated in the specified time
falseif 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.

Parameters
[in]taskhandle
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.

Return values
trueif the kill signal is not present and the task can still run
falseif 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.

Parameters
[in]taskhandle 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.

Return values
trueif the task was woken up by an external call to OSTASK_Wakeup
falseif there was no external wake up event - just the sleep time passed
void OSTASK_Destroy ( OSTask  task)

Destroys a task

Parameters
[in]taskhandle of the task to destroy
bool OSTASK_Suspend ( OSTask  task)

Suspends the execution of a task

Returns
true if the task was suspended, false otherwise
Parameters
[in]taskhandle of the task to suspend
bool OSTASK_Resume ( OSTask  task)

Resumes the execution of a task

Returns
true if the task was resumed, false otherwise param[in] task handle of the task to resume
void OSTASK_Yield ( void  )

Causes the calling task to relinquish the CPU

OSTaskPriority OSTASK_GetPriority ( OSTask  task)

Gets the priority of a task.

Returns
task priority
Parameters
[in]taskhandle of the task
bool OSTASK_SetPriority ( OSTask  task,
OSTaskPriority  priority 
)

Sets the priority of a task

Returns
true if the task priority was set, false otherwise
Parameters
[in]taskhandle of the task
[in]prioritynew task priority
OSTask OSTASK_GetCurrentTask ( void  )

Retrieves the currently running task

Returns
handle of the currently running task