Task Priorities in FreeRTOS – Part 3: The Importance of Low-Priority Tasks
In our previous articles (part 1, part 2), we established a foundation for task prioritization in FreeRTOS, outlining a multi-tier system for effective task management. To further refine our task hierarchy and optimize system performance, we will now introduce a new priority level for tasks that are critical but do not require immediate execution.
The Need for Low Priority Tasks
While many tasks require timely execution, there are instances where immediate response is not essential. Examples include logging, user interface updates, or background data processing. These tasks, although critical to the system’s functionality, can often be deferred without significant impact. Even better, deferring those activities to low priority tasks often improves the overall responsiveness of the system.
To accommodate these tasks, we define a low priority level: configPRIORITY_BACKGROUND
. We had already introduced it in the first part of the series, but without too much details. This level is positioned below normal priority tasks, ensuring they are executed only when system resources are available.
Important: Understanding Priority vs. Criticality
It’s crucial to differentiate between priority and criticality. All tasks within a system are essential in some way and contribute to the overall functionality. However, their timing requirements vary significantly.
Priority directly correlates to a task’s response time expectations. A higher priority task will be given precedence when multiple tasks are ready to execute.
Criticality refers to the impact of a task’s failure on the system. While all tasks are critical to some extent, some tasks have more severe consequences if they are not executed or delayed.
It’s tempting to equate high criticality with high priority, but this is often a misconception. For instance, logging tasks are typically critical for system analysis and debugging, but they don’t require immediate execution. Assigning them too high a priority would be counterproductive and could starve other, more time-sensitive tasks.
In Summary
- Priority determines when a task executes relative to other tasks.
- Criticality defines the importance of a task to the system’s overall function.
By understanding this distinction, we can make informed decisions about task prioritization and optimize system performance.
Implementing Low Priority Tasks: A Log Example
Consider a logging system. To avoid impacting the performance of time-critical tasks, we can adopt the following approach:
- Log Message Generation: Application tasks can generate log messages and push them onto a queue. This operation is typically fast and doesn’t require a priority higher than normal.
- Log Processing Task: A low-priority task continuously reads from the log queue. Most of the time there is nothing to read, so that this task is put to sleep and does not consume CPU time. When new messages arrive on the queue, this task wakes-up, processes the messages and writes them to the desired log medium (e.g., console, file, network). This happens only when there is actually something to read and the CPU has nothing more urgent to do (e.g. no higher priority task is scheduled).
By offloading log processing to a low-priority task, we prevent log generation from interfering with higher-priority activities. This approach ensures that critical tasks are not delayed due to logging operations. This is especially true if the log messages are published on a low-speed medium (UART anyone?).
Another Low Priority Task: Feeding the Watchdog
A hardware watchdog is like a digital babysitter for your microsystem. It’s a special piece of hardware that counts down continuously from an initial value. If your software doesn’t “feed” the watchdog (reset the counter) before it reaches zero, the watchdog assumes something has gone wrong and resets the system. This helps prevent your system from getting stuck in an endless loop, CPU overload, system crash or other problematic states.
To ensure the watchdog is fed regularly, it’s common practice to assign this task to a low-priority process. This is because feeding the watchdog is typically not time-critical, and handling it in a low-priority task prevents it from interfering with more important operations.
The watchdog service is an excellent example of the difference between priority and criticality. Feeding the watchdog is low priority, but high criticality since it takes care of monitoring the system’s health.
By making watchdog feeding a low-priority task, we balance system reliability with efficient resource utilization.
Challenges and Considerations
While low-priority tasks offer several advantages, it’s essential to address potential challenges:
- Starvation: Ensure that low-priority tasks have sufficient opportunities to execute. Consider using task priorities carefully and avoid excessive blocking of higher-priority tasks.
- Priority Inversion occurs when a lower-priority task holds a resource (semaphore, mutex, etc) that a higher-priority task requires, causing the higher-priority task to block. This scenario must be avoided at any cost. FreeRTOS provides mechanisms to ensure that priority inversion can’t happen. Proper system architecture and resource management are key.
Pro Tip: Troubleshooting Low Priority Task Issues
A common challenge when working with low-priority tasks is determining why they aren’t executing as expected (or aren’t executing at all!). It’s tempting to simply increase the task’s priority to resolve the issue, but this often masks the underlying problem rather than fixing it.
Before adjusting task priorities, carefully investigate the following:
- CPU Hogging: A high-priority task might be monopolizing the CPU, preventing lower-priority tasks from getting execution time. Typical cases include infinite loops without yielding. Use profiling tools to identify potential culprits.
- Task Blocking: A low-priority task might be blocked indefinitely due to resource contention or waiting for events. Typical cases include blocking I/O operations without a time-out. Analyze task behavior and resource usage to pinpoint the issue.
Consider employing advanced debugging tools to gain deeper insights into task behavior and system performance. Software instrumentation tools like Percepio’s Tracealyzer ($) offer comprehensive analysis capabilities. Alternatively, hardware-based debuggers with FreeRTOS awareness, such as Lauterbach‘s ($$$), provide non-intrusive high-speed tracing for in-depth investigations.
By systematically addressing these potential causes, you can often resolve low-priority task issues without resorting to priority adjustments. Remember, altering task priorities can have unintended consequences and should be a last resort.
Wrap-Up
By introducing low-priority tasks into your FreeRTOS system, you can significantly enhance system performance and responsiveness. These tasks are ideal for handling activities that do not require immediate attention, such as logging, background processing, and watchdog feeding.
At this stage the C source that we have used to define task priorities lists as follows:
// Idle task has lowest priority (0) and is already defined somewhere else
#define configPRIORITY_BACKGROUND (1) // Low priority tasks, lowest urgency
#define configPRIORITY_NORMAL (2) // Normal application tasks, mid-range prio
#define configPRIORITY_PROTOCOL (3) // Protocol layer tasks
#define configPRIORITY_DRIVER (4) // Device driver tasks
#define configPRIORITY_RT (5) // Hard real-time tasks, strictest deadlines
#define configMAX_PRIORITIES (6)
Remember, this is just a framework considered as a general good practice. Please make sure you fully understand what it means and feel free to adjust as required.
In the next article of the series we will make a little provision for future developments. We’ll also revisit Max’s original question to demonstrate how the principles outlined in this series can be applied to practical scenarios.
