How to Use Task Callbacks
Task callbacks add powerful tools to the embedded programmers toolbox. Each task can have its own callback function, or no callback function, as follows:
void cbfunA(u32 mode)
{
switch (mode)
{
case EXIT:
/* save extended state */
break
case ENTER:
/* restore extended state */
break;
case INIT:
/* initialize task */
break;
case DELETE:
/* task cleanup and release resources */
break;
case STOP:
/* save context */
break;
case START:
/* restore context if not first start */
break;
default:
/* report error */
}
}
cbfunA is the callback function for taskA. If enabled, it is called automatically by the scheduler and by certain task services. Task callback functions can be used in many ways, some of which are as follows.
Saving Extended Task Contexts
Most RTOSs provide task callbacks to save and restore extended task contexts. For example, when a task switch occurs, Cortex-M automatically saves the first 16 floating-point registers. The remaining floating-point registers, if used, must be saved by the application when the task is suspended and they must be restored when the task is resumed. Task callback functions make this easier to do:
cbfun(EXIT);
is called by the scheduler when the current task is being suspended and
cbfun(ENTER);
is called by the scheduler when it is being resumed.
These cases can be used to save additional floating point registers. On the other hand, if only a few floating point registers are actually being used, it may be desirable to turn off the Cortex-M automatic floating-point state preservation and use the EXIT and ENTER callbacks to save and restore just the floating-point registers actually being used. This reduces task switching times and memory usage.
Recommended by LinkedIn
In addition to the floating-point unit, other coprocessors may need their registers to be saved and restored during task switches. Also, task-specific variables may need to be saved and restored
Managing Task Resources
When a task first starts, it may need to be initialized and to acquire system resources, such semaphores, mutexes, and memory. This can be done with:
cbfun(INIT);
If it becomes necessary to delete the task due to its being breached by malware or due to something else, the resources can be automatically released with:
cbfun(DELETE);
The advantage of this approach is that the DELETE case is immediately below the INIT case in cbfun(). This helps to ensure that every resource acquired during task initialization is released during task deletion. Being able to shut down a task without resource leaks is important for disabling malware that has breached a task, before it can cause harm. cbfun(INIT) is called from TaskStart(), which normally is called during initialization. cbfun(DELETE) is called from TaskDelete(), which normally is called during task or system exit. So, neither adds to task switching time.
An alternate approach is for cbfun(DELETE) to send a message to an exchange where a recovery task waits to release the deleted task’s resources and to recover normal operation. The message would identify the deleted task and the recovery task would call:
cbfunA(RECOVER);
where cbfunA is the callback function for taskA and RECOVER is a case to release taskA’s resources, report the event causing taskA to be stopped, then attempt to restart taskA or start a replacement task. This is a good technique to rid a breached task of malware, if possible, or to shut it down until a fix is available, if not. In the latter case, the replacement task might be a stub that enables the system to continue running and performing its main functions.
For resources that may be acquired during run time, special handling is needed. For example, if semA may or may not have been acquired:
case DELETE:
if (semA)
SemDelete(semA);
Preserving One-Shot Task Contexts
The STOP and START cases are unique to SMX one-shot tasks. They serve the same purposes as the EXIT and ENTER cases do for normal tasks. Note: the first time a task is started, cbfun() is not called.
Controlling Task Callbacks
Not all tasks require callbacks and, for some tasks a callback may be necessary only in part of its code. This can be controlled as follows:
TaskSet(ct, SMX_ST_CBFUN, cbfun, 1);
// callback is active and all cases are enabled
TaskSet(ct, SMX_ST_CBFUN, cbfun, 0);
// callback is active and only INIT and DELETE are enabled
TaskSet(ct, SMX_ST_CBFUN, NULL, 0);
// callback is inactive
The INIT and DELETE cases may be needed for security, as previously discussed.
Callback functions can also be enabled and disabled while a task is running:
smx_TaskSet(ct, SMX_ST_CBFUN, (u32)fpfunA, 1);
// do floating-point operations
smx_TaskSet(ct, SMX_ST_CBFUN, cbfunA, 1);
// return to normal
Here fpfunA(), which preserves floating-point registers and extended task context, in the event of task suspension, is being enabled before a section of code that uses floating-point. After the floating-point section, cbfunA(), which preserves only extended task context, is enabled.
Conclusion
Task callbacks provide methods to deal with difficult problems. Callback code can be found in xtask.c and xsched.c at www.github.com/Micro-Digital/SecureSMX.
#SMX, #RTOS, #Callback Function, #Task, #Scheduler
Hi Ralph, I would like to suggest that in a truly event driven scheduling environment, invocation of the state machine associated with an event is in essence the most powerful and efficient 'callback' feature available. The only other situation where I am comfortable with the use of 'callbacks' is when implementing target RTOS/OS agnostic libraryies and a 'callback' function supplied by an application implements the correct mechanism for returning status/data back to the application using application specific OS primitives. It's nice to read and ponder your articles/posts - very relevant topic.
great explanation
When do task callbacks add clarity—and when do they add unnecessary complexity?
Great explanation! Quick question regarding performance, since these callbacks are performed during task transitions such as on task start, stop or switch, does this risk slowing down the system? How do we ensure that with regard to things like resource cleanup on task switches, the use of callbacks does not cause delays in the real-time processing of tasks?
Why only one call back ? A set of call-back is nice. Plus à state variable and a static table that will indicate which call-back is to be called. Then, when this is done, discover that the task is useless. Only the call-backs are useful. In fact, I took that path to discover that FSMs are a much wider concepts than task. So I get rid of tasks. And then came Viacess that required tasks. So I introduced tasks as limites cases of FSMs