Understanding the FreeRTOS Task Scheduler

Concept of Task Scheduler

FreeRTOS provides a priority-based preemptive task scheduler: with the exception of interrupt handler functions, the code that locks the scheduler, and code that disables interrupts, all other parts of the system can be preempted.

The system theoretically supports an unlimited number of priorities (0 ~ N), the lower the priority value, the lower the task’s priority, with 0 being the lowest priority assigned to idle tasks. Users are generally discouraged from using this priority. If the macro configUSE_PORT_OPTIMISED_TASK_SELECTION is enabled (defined in the FreeRTOSConfig.h file), the maximum available priority is typically limited to 32. In resource-constrained systems, it may be configured to support only 8 or 32 priorities based on actual conditions. When a higher priority task becomes ready, the current task will be immediately swapped out, and the high-priority task will preempt the processor to run.

If an operating system only allows high-priority tasks to “immediately” gain access to the processor and execute, it still cannot be considered a real-time operating system. This is because the process of finding the highest priority task determines whether the scheduling time is deterministic. For example, in a system with n ready tasks, if we only search from the beginning to the end, the time will be directly related to n, and the length of time taken to decide the next ready task will greatly affect the system’s real-time performance.

The FreeRTOS kernel uses two methods to find the highest priority task. The first method is a general approach that searches the ready list from high to low priority uxTopPriority. Since priorities are sorted when tasks are created, the first uxTopPriority found is the task we need, and we can retrieve the corresponding task control block using uxTopPriority.

The second method is a specialized approach that utilizes the count leading zeros instruction CLZ to directly derive uxTopPriority from the uxTopReadyPriority variable, which is 32 bits wide. This allows us to know which priority task can run, and this scheduling algorithm is faster than the ordinary method but is platform-dependent (for example, we use this method in STM32).

The FreeRTOS kernel also allows the creation of tasks with the same priority. Tasks with the same priority are scheduled using round-robin scheduling (also known as time-slicing), which is only effective when there are no higher priority ready tasks in the current system. To ensure the system’s real-time performance, the system strives to allow high-priority tasks to run as much as possible.

The principle of task scheduling is that once a task’s state changes and the currently running task’s priority is lower than the highest priority in the queue, a task switch is performed immediately (unless the current system is in an interrupt handler or in a state where task switching is prohibited).

Task States

Each task in the FreeRTOS system has multiple running states. After system initialization, created tasks can compete for certain resources, which are scheduled by the kernel.

Task states are typically divided into the following four types:

  • Ready (Ready): The task is in the ready list and is capable of executing, waiting for the scheduler to schedule it. Newly created tasks are initialized to the ready state.
  • Running (Running): This state indicates that the task is currently executing and occupies the processor. The FreeRTOS scheduler always chooses the highest priority ready task to run. When a task is running, its state changes to running.
  • Blocked (Blocked): If the task is currently waiting for a timer or external interrupt, it is in a blocked state and not in the ready list. This includes tasks that are suspended, delayed, waiting for a semaphore, reading/writing a queue, or waiting for read/write events.
  • Suspended (Suspended): A suspended task is invisible to the scheduler. The only way to put a task into a suspended state is to call the vTaskSuspend() function; the only way to resume a suspended task is to call vTaskResume() or vTaskResumeFromISR().

The difference between suspended and blocked states is that when a task is not allowed to run for a long time, it can be suspended, so the scheduler will not manage any information about this task until the API function to resume the task is called. In contrast, when a task is blocked, the system still needs to determine whether the blocked task has timed out and whether it can be unblocked.

Task State Transition

We know that each task in the FreeRTOS system has multiple running states. Let’s take a look at the transition relationship between states. The task migration diagram is shown below:

Understanding the FreeRTOS Task Scheduler
  • ① Create Task → Ready (Ready): After the task is created, it enters the ready state, indicating that the task is ready to run and is waiting for the scheduler to schedule it.
  • ② Ready (Ready) → Running (Running): When a task switch occurs, the highest priority task in the ready list is executed and enters the running state.
  • ③ Running (Running) → Ready (Ready): When a higher priority task is created or resumed, a task scheduling occurs, and the highest priority task in the ready list becomes the running task. The previously running task changes from running to ready and remains in the ready list, waiting for the highest priority task to finish before continuing its execution (this can be seen as the CPU usage being preempted by a higher priority task).
  • ④ Running (Running) → Blocked (Blocked): When the running task becomes blocked (suspended, delayed, waiting for a semaphore), it is removed from the ready list, changing its state from running to blocked, and a task switch occurs to run the current highest priority task in the ready list.
  • ⑤ Blocked (Blocked) → Ready (Ready): When a blocked task is resumed (task resumed, delay time expired, semaphore read timeout or signal received), it is added back to the ready list, changing its state from blocked to ready. If the resumed task’s priority is higher than that of the currently running task, a task switch occurs, and the task changes from ready to running.
  • ⑥⑦⑧ Ready (Ready), Blocked (Blocked), Running (Running) → Suspended (Suspended): A task can be suspended by calling the vTaskSuspend() function, making it invisible to the scheduler. A suspended task does not gain CPU usage and will not participate in scheduling unless it is resumed.
  • ⑨ Suspended (Suspended) → Ready (Ready): The only way to resume a suspended task is to call vTaskResume() or vTaskResumeFromISR(). If the resumed task’s priority is higher than that of the currently running task, a task switch occurs, and the task changes from ready to running.

Task Design Considerations

As an embedded developer, it is crucial to have a deep understanding of the embedded system you design, including task priority information, task and interrupt handling, task runtime, logic, and state, to create a good system. Therefore, during the design phase, it is essential to establish a framework based on requirements. The following factors should be considered from the outset: the context environment in which tasks run and the reasonable design of task execution time.

The context in which programs run in FreeRTOS includes:

  • Interrupt service routines
  • Regular tasks
  • Idle tasks

Interrupt Service Routines

Interrupt service routines are a special context environment that needs careful attention. They run in a non-task execution environment (usually a special operating mode of the chip, also known as privileged mode). In this context, operations that suspend the current task cannot be used, and any APIs that would block execution are not allowed to be called.

Additionally, it is important to keep interrupt service routines short and efficient. Generally, only event marking is done in interrupt service routines, notifying tasks to perform related processing because the priority of interrupt service routines is higher than that of any task. If the interrupt handling time is too long, it will prevent the entire system’s tasks from running normally. Therefore, when designing, it is crucial to consider the frequency of interrupts and the time taken to handle them in order to coordinate with the corresponding interrupt handling tasks.

Regular Tasks

Tasks seem to have no restrictions on program execution, as all operations can be executed. However, as a real-time system with clear priorities, if a task enters a deadlock (where the task loop has no blocking mechanism), then tasks with lower priority than this task cannot execute, including idle tasks, because during the deadlock, the task will not voluntarily yield the CPU. Lower priority tasks cannot gain CPU access, while higher priority tasks can preempt the CPU. This situation must be noted in real-time operating systems, so deadlocks are not allowed in tasks.

If a task is only in the ready state and not in the blocked state, it will inevitably affect the execution of other lower-priority tasks. Therefore, during task design, it should be ensured that when a task is inactive, it can enter the blocked state to yield the CPU usage. This requires a clear understanding of when to block tasks to ensure that lower-priority tasks can run normally. In actual design, urgent event handling tasks are generally assigned higher priority.

Idle Tasks

Idle tasks (idle tasks) are system tasks that FreeRTOS automatically enters when no other work is being performed.

Since the processor always needs code to execute, at least one task must be in the running state. FreeRTOS ensures this by automatically creating an idle task when vTaskStartScheduler() is called. The idle task is a very short loop.

Users can hook their own functional functions into the idle task through the idle task hook. This idle task hook can typically perform some additional special functions, such as indicating the system’s running status or entering a power-saving mode. In addition to the idle task hook, the FreeRTOS system uses the idle task for other functions. For example, when a task is deleted or a dynamic task has finished running, the memory space of the task is not released during the deletion process but is added to the end list. The actual resource recovery work is completed in the idle task, which is the only task that cannot enter a blocked state because FreeRTOS needs to ensure that there is always at least one runnable task in the system.

The idle task hook function should meet the following conditions:

  • It should never suspend the idle task;
  • It should not enter a deadlock; it needs to leave some time for the system to handle resource recovery.

Task Execution Time

Task execution time generally refers to two aspects: the time from the start to the end of a task and the task’s cycle.

During system design, both of these times need to be considered. For example, for event A corresponding to service task Ta, the required real-time response indicator is 10ms, while the maximum running time of Ta is 1ms. Therefore, 10ms is the cycle of task Ta, and 1ms is the running time of the task. In simple terms, task Ta must complete its response to event A within 10ms.

If there is another task Tb in the system with a cycle of 50ms and a maximum running time of 100us, even if the priority of task Tb is raised above that of Ta, it will have no impact on the system’s real-time performance metrics. This is because even if task Tb preempts the resources of Ta, the time consumed is only 100us, which is still within the response time of event A (10ms), allowing Ta to safely complete its response to event A. However, if there is a task Tc in the system with a running time of 20ms and its priority is set higher than that of Ta, if Tc interrupts Ta while it is running, once Tc finishes executing, Ta will have already missed the response to event A(10ms), which is not acceptable.

Therefore, during design, we must consider task timing. Generally, tasks with shorter processing times should be assigned higher priorities.

Original text: http://t.csdnimg.cn/wnX8x

Likes and views are the greatest support for me👇

Leave a Comment

×