Resource Management in FreeRTOS

Resource Management

Resource Management in FreeRTOS

Problem: When a task is using a resource and is preempted before it has finished using it, the resource may be left in an incomplete or corrupted state. If another task or interrupt tries to access this resource at that time, it can lead to data corruption or processing errors.

That is why we need “critical sections”, “atomic operations”, and “mutexes”.

Prerequisite content:

Resource usage relies on code calls, and functional code is encapsulated in functions. Whether a function can be interrupted or called again depends on its reentrancy.

  • • The only criterion for reentrancy: A function must only depend on the parameters passed to it and local variables on its own stack. If it uses global or static local variables, it is not reentrant.
  • • Each task maintains its own stack and CPU registers. If a function only uses local variables, it is thread-safe.
  • • Once a function uses global variables, static variables, or other peripherals, it is no longer “reentrant”. If that function is being called, other tasks or interrupts must be prevented from calling it again.

For non-reentrant functions and code blocks, we can place them inside a critical section to achieve mutual exclusion.

Basic Critical Section

A basic critical section refers to the code area between the macros taskENTER_CRITICAL() and taskEXIT_CRITICAL().

/* To ensure that access to the PORTA register is not interrupted, the access operation is placed in a critical section.
Entering critical section */
taskENTER_CRITICAL();
/* Between taskENTER_CRITICAL() and taskEXIT_CRITICAL(), no other tasks will be switched. Interrupts can execute, and nesting is allowed, but only for interrupts with a priority higher than configMAX_SYSCALL_INTERRUPT_PRIORITY – and these interrupts are not allowed to access FreeRTOS API functions. */
PORTA |= 0x01;
/* We have completed access to PORTA, so we can safely exit the critical section. */
taskEXIT_CRITICAL();

Underlying implementation:

Resource Management in FreeRTOS
Resource Management in FreeRTOS

Entering Critical Section:

  • • portDISABLE_INTERRUPTS(): It immediately disables most interrupts. No task switching will occur until you re-enable interrupts, and no other interrupts will interfere with your code execution (except for those high-priority, non-FreeRTOS managed urgent interrupts: interrupts with a priority higher than <span>configMAX_SYSCALL_INTERRUPT_PRIORITY</span>).
  • • uxCriticalNesting++: Supports nesting. FreeRTOS critical sections can be nested. Scenario: You may have a function FuncA that internally calls vTaskEnterCritical; and FuncA is called by another function FuncB, which also calls vTaskEnterCritical before calling FuncA. The purpose of the counter: The uxCriticalNesting counter is used to record the level of nesting. Each time you enter a level, the counter increases by 1.
  • • portASSERT_IF_IN_ISR(): Prevents misuse. This is a safety measure. vTaskEnterCritical is a task-level API and must not be called in an interrupt.

Exiting Critical Section:

  • • Each time you exit, the nesting counter decreases by 1, and interrupts can only be re-enabled when it reaches 0.

The core protection mechanism is implemented through atomic instructions provided by the CPU to disable/enable interrupts. During the interrupt-disabled period, the system operates in a “single-threaded” mode (only considering the RTOS-managed part), avoiding all concurrency issues. It supports nesting: by maintaining a nesting counter (uxCriticalNesting) in each task’s TCB, safe nesting of critical sections is achieved.

The rule is: Disable interrupts on the first Enter, and only re-enable them on the last Exit. Intermediate Enter/Exit only increments or decrements the counter.

The obvious drawback: It increases system interrupt latency. During the execution of the critical section, all masked interrupts cannot be responded to. Therefore, the code segment protected by the critical section must be as short as possible; otherwise, it will severely affect the system’s real-time performance.

Suspending (Locking) the Scheduler

Creating a critical section by suspending the scheduler The critical section implemented by suspending the scheduler can only protect a segment of code from being interrupted by other tasks, while interrupts remain enabled. If a critical section is too long and not suitable for simply disabling interrupts, consider using the scheduler suspension method. However, waking up (resuming, or unsuspending) the scheduler is a relatively lengthy operation.

void vTaskSuspendAll( void ); If an interrupt requests a context switch while the scheduler is suspended, that request will also be suspended until the scheduler is awakened.

For protecting time-consuming resources, it is recommended to use mutexes (Mutex) because mutexes block tasks while waiting, rather than disabling the entire system’s interrupts.

Mutual Exclusion

When accessing a resource shared by multiple tasks or shared between tasks and interrupts, the “mutual exclusion” technique is used to ensure data consistency at all times. It ensures that a task has exclusivity from the moment it starts accessing the resource until the resource is restored to a complete state.

Mutexes are used to achieve mutual exclusion, also known as mutex locks, and the usage process is as follows:

Resource Management in FreeRTOS

Defects of Mutual Exclusion Demand – Priority Inversion

Priority inversion can occur when using binary semaphores or mutex semaphores, but mutexes provide a priority inheritance mechanism. Therefore, in synchronous applications (between tasks or between interrupts and tasks), binary semaphores are most suitable, while mutex semaphores are suitable for applications that require mutual exclusion.

Resource Management in FreeRTOS
Resource Management in FreeRTOS

High-priority task 2 must wait for low-priority task 1 to release its hold on the mutex. The behavior of high-priority tasks being delayed by low-priority tasks is called “priority inversion”.

If this behavior is further amplified

When a high-priority task is waiting for a semaphore, a medium-priority task that does not need the mutex resource takes control of the CPU,

A medium-priority task that is between the two tasks’ priorities starts executing

Leading to a high-priority task waiting for a low-priority task, while the low-priority task cannot execute! Only after the medium-priority task completes will the low-priority task execute, and after executing, it releases the mutex, allowing the high-priority task to execute.

This completely violates the priority mechanism.

Resource Management in FreeRTOS

Mutexes provide a mechanism forPriority Inheritance

When a low-priority task holds a resource and blocks a high-priority task, the low-priority task’s priority is temporarily raised to that of the blocked high-priority task. This prevents medium tasks from preempting low tasks, allowing low tasks to complete quickly and release resources, after which high tasks can continue executing. After releasing the resource, the low task returns to its original priority.

In other RTOSs like (RT_Thread, Azure RTOS), another solution is used –Priority Ceiling Protocol

The Priority Ceiling Protocol specifies a “priority ceiling” for each resource, which is the highest priority of tasks that can access that resource. When a task acquires a resource, its priority is temporarily raised to the resource’s ceiling priority. This prevents other intermediate priority tasks from preempting, avoiding inversion.

Defects of Mutexes – Deadlock

Deadly embrace

Resource Management in FreeRTOS

Two tasks mutually require each other to release the mutex to execute, resulting in both tasks being blocked and unable to resolve.

There can also be self-deadlock, where a task needs to use this resource multiple times internally. In this case, a recursive lock can be used to resolve:

  • • Task A can acquire the recursive lock M multiple times after obtaining it.
  • • It must “take” N times and “give” N times for the lock to be released.

Moreover, it internally resolves “who locks, who unlocks”, while mutexes have this defect, where a task that has not locked can also unlock.

Resource Management in FreeRTOS
Resource Management in FreeRTOS

if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() ) This checks whether the task handle calling the recursive lock is the current owner of the recursive lock.

Resource Management in FreeRTOS

Besides mutexes, the concept of mutual exclusion can also be implemented usingGuardian Tasks

Guardian Tasks

Guardian tasks organize resource sharing from “chaos” to “order”.

  1. 1. Centralization of resource demand All access rights to a shared resource (such as serial ports, SPI buses, file systems, displays) are consolidated from a chaotic state of “everyone has a share” into a single, dedicated task.

This eliminates data corruption issues caused by resource competition and concurrent access.

  1. 2. Request Queuing All other tasks no longer directly operate on shared resources but encapsulate their “operation requests” into messages and send them to a unified queue. (The queue’s message receiving and sending functions are implemented within a critical section.)

The working logic of the guardian task is a very simple, never-interrupted loop: “Take a request from the queue -> Process the request -> Take the next one”.

It ensures that every operation on shared resources is logically complete and uninterrupted.

🎆🎇🧨✨🎉🎊🎃🎄🎋🎍🎎🎏🎐🎑🧧🎀🎁🎗️🎞️🎟️🎫🎠🛝

Leave a Comment