yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
ESP32 Core Board📖
1. ESP32 Introduction
  • 1. ESP32S3 Overview
  • 2. Environment Setup Introduction
  • 3. Dev Board Introduction
2. ESP-IDF Development Tutorial
1. Before Development
  • 1. Environment Setup (Windows)
  • 2. Environment Setup (Linux)
  • 3. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. DMA
  • 8. ADC
  • 9. PWM Breathing LED
  • 10. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. PSRAM External Memory Experiment
  • 5. Flash Read and Write Experiment
  • 6. USB Emulated Mass Storage Experiment
4. FreeRTOS operating system
  • 1.FreeRTOS Core Concepts Theory
  • 2.Task Creation and Management
  • 3.Queue
  • 4.Binary Semaphore
  • 5.Mutex
  • 6.Event Groups
  • 7.Software Timer
  • 8.Task Scheduler
  • 9.Low Power Mode
  • 10.Deadlock
  • 11.Priority Inversion
5. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. Socket Programming Basics
  • 9. HTTP Client Experiment
  • 10. HTTP Server Experiment
  • 11. MQTT IoT Communication Protocol
  • 12. WIFI OTA Upgrade
  • 13. Bluetooth Fundamentals
  • 14. Bluetooth Slave Mode
  • 15. Bluetooth Master Mode
6. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
7. Xiaozhi AI Voice
  • 1. Burn Xiaozhi Firmware
  • 2. Network and Configuration
  • 3. AI Agent Settings
  • 4. Program Analysis
  • 5. Free Chat
3. Arduino Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. ADC
  • 8. PWM Breathing LED
  • 9. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication
  • 2. I2C OLED Display
  • 3. SPI Bus Communication
  • 4. Flash Read and Write
  • 5. USB Emulated Mass Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Server Experiment
  • 5. UDP Client Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT IoT Communication Protocol
  • 11. WIFI OTA Upgrade
  • 12. Bluetooth Fundamentals
  • 13. Bluetooth Slave Mode
  • 14. Bluetooth Master Mode
5. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
4. MicroPython Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
  • 3. Thonny IDE
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. TIMER_IT
  • 5. Watchdog Timer
  • 6. ADC
  • 7. PWM Breathing LED
  • 8. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. Flash Read and Write Experiment
  • 5. Print Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT
  • 11. Bluetooth Fundamentals
  • 12. Bluetooth Slave Mode
  • 13. Bluetooth Master Mode
5. Audio Applications
  • 1. I2S Audio Playback Experiment
  • 2. Repeater Experiment
Download
  • Code
  • Hardware_Info
  • 3D_Model_File
  • Software_Tools
  • VMware
  • VM_File
Download ZIP

Welcome to ESP32 Core Board repository

11.Priority Inversion

Priority Inversion


Priority Inversion1. Overview of Priority Inversion1.1 Why Learn This?1.2 Real-World Horror Stories1.3 What This Project Demonstrates2. Core Concepts2.1 Priority Inversion Scenario2.2 Why This Is a Problem2.3 Priority Inheritance for Mutexes2.4 Task Priority API3. Project Architecture and Flow4. Hardware Dependencies5. Source Code Explanation5.1 Header Files and Macro Configuration5.2 vTaskA() — High Priority Task (Prio 3)5.3 vTaskB() — Medium Priority Task (Prio 2)5.4 vTaskC() — Low Priority Task (Prio 1)5.5 app_main() — Main Entry6. Operation Flow6.1 Build and Flash Process6.2 Wiring Overview6.3 Detailed Operation Flow6.4 Complete Log Example6.5 Quantifying Priority Inversion Damage6.6 How Much Can FreeRTOS's Priority Inheritance Help?7. Common Issues


1. Overview of Priority Inversion

1.1 Why Learn This?

Priority Inversion is the most notorious scheduling anomaly in real-time systems. Unlike deadlock—which at least "kills cleanly" and lets you detect it immediately—priority inversion is "half-dead": a high-priority task that should respond immediately gets indefinitely delayed by an unrelated medium-priority task.

Its danger lies in deception—when you set task A to the highest priority (3), you naturally expect A to respond immediately. But priority inversion reveals: A's response time = C's (low priority) lock-holding time + B's (medium priority) execution time. A's priority is effectively meaningless.

1.2 Real-World Horror Stories

EventYearConsequence
NASA Mars Pathfinder1997Priority inversion caused the weather data collection task to be indefinitely blocked by the communication task. System bus timeout → watchdog reset → repeated restarts. The lander nearly became scrap metal on Mars due to a single software bug. The NASA JPL team spent three days remotely debugging to identify the priority inversion, and performed a remote hotfix by changing the mutex to priority inheritance mode. This is the most famous priority inversion case in embedded software history.
A certain automotive ADAS system2018Image recognition task (high priority) was preempted by UI rendering task (medium priority) while waiting for a lock. Collision warning delayed by 300ms → at 120km/h, the car traveled an additional 10 meters → equivalent to emergency braking 10 meters too late
A certain medical infusion pump2015Infusion rate monitoring task priority inversion → abnormal flow detected with a 2-second delay → excessive infusion → FDA recall

NOTE: The Mars Pathfinder story teaches us one thing: Priority inversion doesn't require complex code—just three tasks and one lock. You don't even need to write any logic incorrectly—FreeRTOS's default mutex behavior alone can cause inversion.

1.3 What This Project Demonstrates

This project uses 3 tasks (A/B/C) + 1 mutex + 1 button + 2 GPIO pins, with WS2812 LED colors to visually demonstrate the entire priority inversion process: Green light (C holds lock) → Blue light (B preempts C) → A is now indirectly blocked by B, red light doesn't turn on.


2. Core Concepts

2.1 Priority Inversion Scenario

​x
Priority:  A(3) > B(2) > C(1)
Shared Resource: Mutex xMutex (shared by A and C)

Expected Behavior (B doesn't exist):
  C takes lock → executes → releases lock → A takes lock → executes

Actual Behavior (B preempts midway):
  C takes lock ──► B preempts C ──► A waits for lock (held by C)
                (green light)      (blue light)       (red light off)
  → A is indirectly blocked by B → Priority Inversion:
    High-priority A's actual blocking time = B's execution time (unpredictable)

2.2 Why This Is a Problem

In real-time systems, high-priority tasks have strict deadlines. Priority inversion causes a high-priority task's response time to be determined not by itself, but by when an unrelated medium-priority task releases the CPU. In extreme cases, this can cause system crashes.

2.3 Priority Inheritance for Mutexes

FreeRTOS's mutex has a built-in Priority Inheritance mechanism:

xxxxxxxxxx
Normal mutex behavior:
  C(1) takes lock → B(2) preempts C → A(3) tries to take lock → kernel detects
  → temporarily elevate C's priority to 3 → C runs to completion and releases lock
  → A takes lock → C restores to priority 1

But priority inheritance cannot eliminate inversion, it can only shorten the duration of inversion. A more fundamental solution is to prevent low-priority tasks from holding locks for extended periods.

2.4 Task Priority API

xxxxxxxxxx
#define PRIO_A  3   // A high priority
#define PRIO_B  2   // B medium priority
#define PRIO_C  1   // C low priority

// Create task with specified priority (higher=higher)
xTaskCreatePinnedToCore(vTaskA, "TaskA", 2048, NULL, PRIO_A, NULL, 1);

3. Project Architecture and Flow

xxxxxxxxxx
On Core 1, 3 tasks run:
  Task A (Prio 3) — High priority, Red LED (holds lock)
  Task B (Prio 2) — Medium priority, Blue LED (button-triggered preemption)
  Task C (Prio 1) — Low priority, Green LED (holds lock)

  Mutex (xMutex): Shared by A and C

Flow (Priority Inversion):
  C takes lock (green) → button triggers B to preempt C (blue)
              → A waits for lock (held by C, C preempted by B)
              → A is indirectly blocked by B → Priority Inversion!
              → B releases → C resumes (green) → C releases lock → A takes lock (red)

During A's lock-holding, B cannot intervene (g_a_has_mutex flag blocks it)

GPIO Roles:

PinFunctionDefault LevelDescription
GPIO 0BOOT buttonPulled up=highPress=trigger B to preempt C
GPIO 2A release conditionPulled up=highFloating=1=hold lock; GND=0=release
GPIO 4C release conditionPulled down=lowFloating=0=release; 3.3V=1=hold lock
GPIO 48WS2812—Red=A, Blue=B, Green=C

4. Hardware Dependencies

PinFunctionDescription
GPIO 0BOOT buttonTrigger B preemption
GPIO 2A release conditionPulled up — connect to GND to release A's lock
GPIO 4C release conditionPulled down — connect to 3.3V to hold C's lock
GPIO 48WS2812 RGB LEDRed=A holds lock, Blue=B preempts, Green=C holds lock

Required components: freertos/FreeRTOS, freertos/task, freertos/semphr, driver/gpio, led_strip, esp_log

This project uses a third-party library again: For details, refer to (Detailed third-party library installation process: 2.Basic Peripherals→10.RGB LED Effects)

Note: led_strip is a third-party component provided by the ESP-IDF Component Registry, not built into IDF. Before use, you must declare the dependency in main/idf_component.yml, otherwise the compiler won't find the header file:

xxxxxxxxxx
## IDF Component Manager Manifest File
dependencies:
idf:
 version: '>=4.1.0'
espressif/led_strip: ^3.0.3

After declaration, executing idf.py build will automatically download the component to the managed_components/ directory.


5. Source Code Explanation

Complete source code: Source Code → ESP-IDF → 4.FreeRTOS → 7. RTOS_Priority_inversion

5.1 Header Files and Macro Configuration

This section defines the priorities of three tasks, mutex handle, LED pin, button pin, conditional release pins, and global state flags—these are the configuration core of the entire demonstration project.

xxxxxxxxxx
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "led_strip.h"
#include "esp_log.h"

/* ==================== Log Tag ==================== */

// Log tag
static const char *TAG = "PRIO_INV";

/* ==================== Mutex ==================== */

// Mutex handle
SemaphoreHandle_t xMutex = NULL;

/* ==================== Priority Definitions ==================== */
#define PRIO_A      3
#define PRIO_B      2
#define PRIO_C      1

/* ==================== Hardware Pin Definitions ==================== */
#define BLINK_GPIO      48
#define LED_NUM          1

// Button pin (active-low)
#define KEY_GPIO         0
// Debounce time (ms)
#define KEY_DEBOUNCE_MS 50
#define COND_GPIO_A      2
#define COND_GPIO_C      4

/* ==================== Global State ==================== */
static led_strip_handle_t led_strip;
static volatile bool g_b_holding = false;
static volatile bool g_a_has_mutex = false;

5.2 vTaskA() — High Priority Task (Prio 3)

Task A has the highest priority and shares the same mutex with C. It intentionally delays at the start to let C grab the lock first; afterward, it polls GPIO2 to decide whether to hold/release the lock—red light on when holding.

xxxxxxxxxx
void vTaskA(void *pv)
{
    // Let C grab the mutex first
    vTaskDelay(pdMS_TO_TICKS(500));

    while (1) {
        // [A] waiting for mutex
        ESP_LOGI(TAG, "[A] waiting for mutex...");
        xSemaphoreTake(xMutex, portMAX_DELAY);

        // Flag A holding mutex
        g_a_has_mutex = true;
        // [A] Got mutex, RED on
        ESP_LOGI(TAG, "[A] Get mutex, RED ON");
        set_led(150, 0, 0);

        // Poll GPIO2, GND=release
        while (gpio_get_level(COND_GPIO_A) == 1) {
            vTaskDelay(1);
        }

        // [A] Release mutex
        ESP_LOGI(TAG, "[A] GPIO2 == 0, Release mutex");
        xSemaphoreGive(xMutex);
        // Clear flag
        g_a_has_mutex = false;

        // Give C a chance
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

5.3 vTaskB() — Medium Priority Task (Prio 2)

Task B is the "troublemaker" in this demonstration—when the button is pressed, it continuously occupies the CPU, simulating a medium-priority task indirectly blocking a high-priority task on an unrelated lock. When A holds the lock, B skips everything (no LED, no logs), ensuring B only preempts when C holds the lock.

xxxxxxxxxx
void vTaskB(void *pv)
{
    while (1) {
        if (!g_a_has_mutex && gpio_get_level(KEY_GPIO) == 0) {

            // Debounce
            vTaskDelay(KEY_DEBOUNCE_MS / portTICK_PERIOD_MS);

            // Re-check
            if (!g_a_has_mutex && gpio_get_level(KEY_GPIO) == 0) {

                // [B] Key pressed, BLUE on, preempt C
                ESP_LOGI(TAG, "[B] Key pressed, BLUE ON (preempt C)");
                set_led(0, 0, 150);
                // Flag B preempting
                g_b_holding = true;

                // Keep running while held; exit if A grabs lock
                while (!g_a_has_mutex && gpio_get_level(KEY_GPIO) == 0) {
                    // [B] holding...
                    ESP_LOGI(TAG, "[B] holding...");
                    vTaskDelay(pdMS_TO_TICKS(200));
                }

                // Clear preempt flag
                g_b_holding = false;

                // Restore green only if A hasn't taken over
                if (!g_a_has_mutex) {
                    // [B] released, restore green
                    ESP_LOGI(TAG, "[B] Key released, restore GREEN");
                    set_led(0, 150, 0);
                }
            }
        }
        vTaskDelay(20 / portTICK_PERIOD_MS);
    }
}

5.4 vTaskC() — Low Priority Task (Prio 1)

Task C grabs the lock first and lights green, but intentionally yields during lock-holding—this is the core trigger point for priority inversion. At this point, high-priority A is waiting for the lock, and medium-priority B seizes the opportunity to occupy the CPU without letting go. A's priority becomes meaningless.

xxxxxxxxxx
void vTaskC(void *pv)
{
    while (1) {
        // [C] waiting for mutex
        ESP_LOGI(TAG, "[C] waiting for mutex...");
        xSemaphoreTake(xMutex, portMAX_DELAY);

        // [C] Got mutex, GREEN on
        ESP_LOGI(TAG, "[C] Get mutex, GREEN ON");
        set_led(0, 150, 0);
        bool prev_b = false;
        while (gpio_get_level(COND_GPIO_C) == 1) {
            if (g_b_holding) {
                if (!prev_b) {
                    // [C] Preempted!
                    ESP_LOGW(TAG, "[C] Preempted by B, holding mutex but blocked!");
                }
            } else {
                if (prev_b) {
                    // [C] B released, back running
                    ESP_LOGI(TAG, "[C] B released, back running, still holding mutex");
                }
            }
            prev_b = g_b_holding;
            // Intentional yield — let B have a chance to preempt
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }

        // [C] Release mutex
        ESP_LOGI(TAG, "[C] GPIO4 != 1, Release mutex");
        xSemaphoreGive(xMutex);

        // Give A a chance
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

5.5 app_main() — Main Entry

The main function initializes four hardware modules in order (LED, button, two conditional pins), creates the mutex, and finally pins all three tasks to Core 1 to demonstrate single-core scheduling.

xxxxxxxxxx
void app_main(void)
{
    rgb_init();
    // KEY init
    key_init();
    cond_gpio_a_init();
    cond_gpio_c_init();

    // Create mutex
    xMutex = xSemaphoreCreateMutex();

    // All pinned to Core 1 — single-core demo
    xTaskCreatePinnedToCore(vTaskA, "TaskA", 2048, NULL, PRIO_A, NULL, 1);
    xTaskCreatePinnedToCore(vTaskB, "TaskB", 2048, NULL, PRIO_B, NULL, 1);
    xTaskCreatePinnedToCore(vTaskC, "TaskC", 2048, NULL, PRIO_C, NULL, 1);
}

6. Operation Flow

6.1 Build and Flash Process

For detailed flash process: 1.Development Basics→3.Build and Flash Process→Build and Flash Process

6.2 Wiring Overview

PinFunctionDefault LevelRole in Demo
GPIO0BOOT buttonPulled up=high, pressed=lowStage 4 hold to trigger B preemption; Stage 7 hold to verify B is blocked
GPIO2A release conditionPulled up=high (floating=1=hold)Stage 7 floating keeps A holding lock
GPIO4C release conditionPulled down=low (floating=0=release)Before Stage 1 connect to 3.3V → C holds lock; Stage 6 connect to GND → C releases lock
GPIO48WS2812 LED—Red=A holds lock, Blue=B preempts, Green=C holds lock

6.3 Detailed Operation Flow

The entire demonstration is divided into the following stages in chronological order, each stage annotating the required wiring operations and corresponding code logic.


Stage 1: Startup and Task Creation

Wiring requirement: GPIO4 to 3.3V, GPIO2 floating.

GPIO4 to 3.3V wiring diagram

GPIO4 to 3.3V wiring diagram (back)

app_main() initializes LED, button, GPIO2, GPIO4 in sequence, then creates the mutex, and finally creates all three tasks pinned to Core 1.

After the three tasks are created, they immediately enter the ready state, and the scheduler selects the highest priority ready task to run:

xxxxxxxxxx
Ready tasks:  A(Prio3)  B(Prio2)  C(Prio1)
           ──► Scheduler selects A (highest priority) ──► executes vTaskA()

Stage 2: A Voluntarily Yields, C Takes Lock

Although task A has the highest priority, its first line of code is vTaskDelay(pdMS_TO_TICKS(500))—A blocks itself for 500ms. The scheduler selects B(Prio2) from the remaining ready tasks to run.

B enters its main loop, checks the button—the button is not pressed (gpio_get_level(KEY_GPIO) == 1), skips the if block, executes vTaskDelay(20/portTICK_PERIOD_MS) and enters brief blocking.

At this point, C(Prio1) becomes the only ready task and gets the CPU:

xxxxxxxxxx
C calls xSemaphoreTake(xMutex, portMAX_DELAY)
  → Lock is free → C immediately gets the mutex
  → ESP_LOGI prints "[C] Get mutex, GREEN ON"
  → set_led(0, 150, 0) → WS2812 lights green

C enters while (gpio_get_level(COND_GPIO_C) == 1) loop—since GPIO4 is connected to 3.3V, the condition is true, and C stays in the loop, yielding once every 100ms via vTaskDelay.

System state at this moment:

  • A: blocked (waiting for 500ms delay to expire)

  • B: briefly blocked (20 ticks)

  • C: running, holding mutex, green LED on, yielding every 100ms in while loop


Stage 3: A Wakes Up, Waits for Lock

After 500ms, A's delay expires and A becomes ready. The scheduler sees A(Prio3) > C(Prio1), A preempts C's CPU:

xxxxxxxxxx
A executes: xSemaphoreTake(xMutex, portMAX_DELAY)
       → Mutex already held by C → A enters blocked state (waiting for lock)
       → Scheduler switches back to C to continue running

Although A has the highest priority, the lock is held by C, so A can only wait. System state at this moment:

xxxxxxxxxx
A(Prio3): blocked on xSemaphoreTake — waiting for lock
B(Prio2): ready, briefly blocked
C(Prio1): running, holding lock, green LED, yielding every 100ms in while loop

C holds lock, A waiting, green LED

Log output: [A] waiting for mutex...

C holds lock, A waiting

This moment the system runs normally—A waits for C to release the lock, C runs as expected, no anomaly.


Stage 4: Button Pressed—Priority Inversion Occurs

Wiring operation: Hold the BOOT button (GPIO0 to GND), do not release.

The user holds the BOOT button (GPIO0 pulled low). At this moment, B happens to wake up from vTaskDelay(20/portTICK_PERIOD_MS) and detects the button press.

B's detection flow:

xxxxxxxxxx
gpio_get_level(KEY_GPIO) == 0 → button pressed ✓
g_a_has_mutex == false        → A not holding lock ✓  ← conditions met
→ Debounce 50ms → re-confirm button pressed
→ Conditions still met → enter preemption logic

B executes:

xxxxxxxxxx
set_led(0, 0, 150) → blue LED on
g_b_holding = true → flag B preempting
ESP_LOGI prints "[B] Key pressed, BLUE ON (preempt C)"

B enters while (!g_a_has_mutex && gpio_get_level(KEY_GPIO) == 0) loop—button still held, A hasn't taken lock, condition met, prints [B] holding... every 200ms.

Key pressed, B preempts C, blue LED

Key point: B(Prio2) > C(Prio1), scheduler lets B run, C is preempted!

B preempts C, blue LED

When C returns from vTaskDelay(100/portTICK_PERIOD_MS), although it wants to continue the while loop, B has higher priority and the scheduler won't return the CPU to C.

xxxxxxxxxx
Blocking chain at this moment:
  A(Prio3) ──waiting for lock──► C(Prio1) holding lock but CPU ──preempted──► B(Prio2) occupying CPU

  A can't get lock because C has no CPU to run and release lock
  C can't release lock because B has higher priority and stole the CPU

C's next loop iteration detects g_b_holding == true:

xxxxxxxxxx
ESP_LOGW prints "[C] Preempted by B, holding mutex but blocked!"

This is the core scenario of priority inversion: high-priority A is indirectly blocked by unrelated B.


Stage 5: Button Released, B Exits, C Resumes

Wiring operation: Release the BOOT button, GPIO0 returns to high level.

The user releases the BOOT button (GPIO0 returns to high level).

B's while loop condition gpio_get_level(KEY_GPIO) == 0 becomes false, exiting the loop:

xxxxxxxxxx
g_b_holding = false → clear preempt flag
→ A not holding lock → restore green set_led(0, 150, 0)
→ ESP_LOGI prints "[B] Key released, restore GREEN"

B returns to the outer vTaskDelay(20/portTICK_PERIOD_MS) and blocks.

CPU finally returns to C(Prio1):

KEY released, B exits, A blocked, C continues

xxxxxxxxxx
C returns from vTaskDelay, g_b_holding == false
→ ESP_LOGI prints "[C] B released, back running, still holding mutex"
→ continue while (gpio_get_level(COND_GPIO_C) == 1) loop

System state at this moment:

  • A(Prio3): blocked on xSemaphoreTake — waiting for lock

  • B(Prio2): ready, briefly blocked

  • C(Prio1): running, holding lock, green LED on

At this point C has recovered the CPU, but GPIO4 is still connected to 3.3V, so C continues holding lock and running. The blocking chain of priority inversion has been relieved, but A is still waiting for C to release the lock.


Stage 6: GPIO4 to GND, C Releases Lock

Wiring operation: Change GPIO4 from 3.3V to GND.

gpio_get_level(COND_GPIO_C) returns 0, C's while (gpio_get_level(COND_GPIO_C) == 1) condition is false, exiting the lock-holding loop:

xxxxxxxxxx
C executes: xSemaphoreGive(xMutex) → release mutex
→ ESP_LOGI prints "[C] GPIO4 != 1, Release mutex"
→ vTaskDelay(2000ms) → give A a chance to take lock

At the moment C releases the lock, A(Prio3) blocked on xSemaphoreTake is woken up by the kernel. A has highest priority → A immediately preempts CPU:

C to GND, A takes lock, red LED

xxxxxxxxxx
A returns from xSemaphoreTake → got lock
→ g_a_has_mutex = true → flag A holding lock
→ set_led(150, 0, 0) → red LED on
→ ESP_LOGI prints "[A] Get mutex, RED ON"

C to GND, A takes lock, red LED

LED changes from green to red—A finally got the lock!


Stage 7: A Holds Lock, Verify Protection Mechanism (Button Press B Doesn't Respond)

Current wiring: GPIO2 floating (pulled up=high), GPIO4 already connected to GND. Operation: Hold the BOOT button, observe if LED changes color.

After A gets the lock, it enters while (gpio_get_level(COND_GPIO_A) == 1) polling—GPIO2 floating, pulled up=high, condition true, A holds lock and keeps red LED on.

At this point g_a_has_mutex = true. After pressing the BOOT button, B's detection logic:

xxxxxxxxxx
if (!g_a_has_mutex && gpio_get_level(KEY_GPIO) == 0)
// A holding lock, condition not met, B blocked

Although task B has higher priority than C, it is limited by the g_a_has_mutex flag. The first layer of condition is already false, not even entering debounce—no blue LED, no log output. LED stays red.

Press KEY again, B cannot preempt A, blue LED off, still red

This is the protection effect when A holds the lock: during the high-priority task's lock-holding period, low-priority B cannot interfere at all. Compared to Stage 4 (when C holds lock, B preempts easily), the difference is clear: when C holds the lock, the lock's protection is incomplete (CPU was stolen); when A holds the lock, the flag directly blocks B.


6.4 Complete Log Example

xxxxxxxxxx
I (1263) PRIO_INV: [C] Get mutex, GREEN ON              ← Stage 2: C takes lock, green
I (1263) PRIO_INV: [A] waiting for mutex...              ← Stage 3: A wakes up, waiting
I (2000) PRIO_INV: [B] Key pressed, BLUE ON (preempt C)  ← Stage 4: button triggered, inversion begins
I (2000) PRIO_INV: [C] Preempted by B, holding mutex but blocked!
I (2200) PRIO_INV: [B] holding...
I (2400) PRIO_INV: [B] holding...
I (3000) PRIO_INV: [B] Key released, restore GREEN       ← Stage 5: button released, B exits
I (3000) PRIO_INV: [C] B released, back running, still holding mutex
I (3500) PRIO_INV: [C] GPIO4 != 1, Release mutex       ← Stage 6: GPIO4 to GND, C releases lock
I (3500) PRIO_INV: [A] Get mutex, RED ON                 ← A takes lock, red LED on
(at this point pressing BOOT → B has no log output)      ← Stage 7: A holds lock, B blocked

The demonstration ends here, with A continuously holding the lock and red LED on.

To replay the demo: reconnect GPIO4 to 3.3V, briefly connect GPIO2 to GND then release (let A release lock and exit), system automatically returns to Stage 2 state.

Return to A released, C holds lock stage

Return to A released, C holds lock stage (back)

6.5 Quantifying Priority Inversion Damage

Taking this project as an example, assuming this is a real embedded product:

TaskRole AnalogyExpected Response
A (Prio 3)Emergency brake detection≤ 1ms
B (Prio 2)Button/UI refresh≤ 50ms
C (Prio 1)Sensor data logging≤ 500ms

Normal case (B doesn't exist): C holds lock 100ms → releases → A takes lock, A delay ≈ 100ms ✓

Priority inversion occurs (B holds button): C holds lock but preempted by B → A waits for lock → A delay = C's lock time + B's preemption duration

If the user holds the button for 10 seconds (child pressing randomly, accidental touch, stuck key, etc.), A is indirectly blocked by B for a full 10 seconds. A priority 3 "emergency brake" task, actual response delay is — 10 seconds.

This is the most lethal aspect of priority inversion: the priority you set for a task is completely useless. A priority 2 UI task, without holding any lock, can block a priority 3 critical task. Because the blocker isn't B itself—it's that B stole CPU from C, and C holds the lock that A needs.

6.6 How Much Can FreeRTOS's Priority Inheritance Help?

FreeRTOS mutex has built-in priority inheritance: when A(3) waits for lock, kernel detects C(1) holds it → temporarily elevates C's priority to 3 → C runs and releases lock → A takes lock → C restores to priority 1.

This shortens the inversion time, but cannot fundamentally eliminate it:

  • Priority inheritance only elevates C, cannot prevent B from preempting before C takes lock

  • If B already stole CPU during C's lock-holding, priority inheritance can't help (C is not running)

  • Fundamental solution: don't let low-priority tasks hold locks for long periods


7. Common Issues

PhenomenonCauseSolution
Priority inversion didn't occurC released lock too fast / B didn't preemptEnsure GPIO4 connected to 3.3V; confirm button held long enough
A also lit red during B's rung_a_has_mutex flag logic errorcheck if flag is updated before xSemaphoreTake
B pressed but LED didn't turn blueA is holding lock, B is blockedNormal — B shouldn't interfere during A's lock-holding
A never gets lockGPIO2 floating (pulled up=1) → A's polling doesn't exitConnect GPIO2 to GND, A releases lock
C never releases lockGPIO4 connected to 3.3V (1) → C's polling doesn't exitConnect GPIO4 to GND, C releases lock
What to do about priority inversion in productionPriority inheritance can only shorten inversion, not eliminateReduce lock-holding time; avoid low-priority tasks holding locks for extended periods
Deadlock
WIFI Network Basics

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文