07-中断管理与临界段.md 7.5 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第6章 中断管理" author: "尚硅谷研究院" date: 2026-07-17

created: 2026-07-17

中断管理与临界段

用生活理解:中断就像领导突然敲门安排紧急任务——你得先把手头工作保存好(保存上下文),处理完紧急任务再回来继续。临界区则像"开会时手机静音"——某些关键时刻不允许被打断。

Concept

Basic concept

FreeRTOS 需要管理中断以确保系统的实时性和确定性。其核心思路是:

  • 临界段(Critical Section):完全关闭中断,任何中断都不能打断当前代码
  • BASEPRI 优先级屏蔽:只屏蔽优先级低于阈值的中断,高优先级中断仍可响应
  • 中断安全 API(FromISR 后缀):可在中断服务函数中安全调用的 API 版本

Working principle

FreeRTOS 提供两个层次的中断控制:

1. 临界段保护(完整中断屏蔽)

API 描述
taskENTER_CRITICAL() 进入临界区,关闭所有中断(有嵌套计数)
taskEXIT_CRITICAL() 退出临界区,恢复之前的中断状态
taskENTER_CRITICAL_FROM_ISR() 从中断中进入临界区
taskEXIT_CRITICAL_FROM_ISR() 从中断中退出临界区

任务级临界区内部维护一个嵌套计数器(uxCriticalNesting):

  • 第一次进入时关闭中断
  • 嵌套进入时仅增加计数
  • 每次退出递减计数,减到 0 时恢复中断

2. 中断开关(直接操作)

API 描述
portDISABLE_INTERRUPTS() 直接关闭所有中断
portENABLE_INTERRUPTS() 直接打开所有中断
注意:无嵌套保护,不推荐用于临界段

3. BASEPRI 优先级屏蔽

FreeRTOS 使用 Cortex-M3 的 BASEPRI 寄存器实现优先级屏蔽:

configKERNEL_INTERRUPT_PRIORITY     = 15 << 4   // 内核中断优先级(最低)
configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 << 4    // API可调用的最高中断优先级
  • 中断优先级 >= 5(数值 <= 5<<4)→ 可调用 FreeRTOS API
  • 中断优先级 < 5(数值 > 5<<4)→ 不允许调用 FreeRTOS API

4. 延后中断处理(Deferred Interrupt Processing)

在 ISR 中只做最少的处理(记录事件、发送信号量/消息到队列),将耗时逻辑推迟到任务中执行。这是 RTOS 中推荐的中断处理模式。

Experiment

Requirements

  • task1 检测按键:
    • KEY1 → portDISABLE_INTERRUPTS() 关闭中断(LED1 停止响应)
    • KEY2 → portENABLE_INTERRUPTS() 打开中断(LED1 恢复)
  • 对比 vTaskDelay()HAL_Delay() 的行为差异

Code Implementation

File: 04_中断管理/Core/Src/freertos_demo.c

#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"

/* 起始任务配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

void freertos_start(void)
{
    /* 动态创建起始任务 */
    xTaskCreate((TaskFunction_t)start_task,
                (char *)"start_task",
                (configSTACK_DEPTH_TYPE)START_TASK_STACK,
                (void *)NULL,
                (UBaseType_t)START_TASK_PRIORITY,
                (TaskHandle_t *)&start_task_handle);

    /* 启动调度器 */
    vTaskStartScheduler();
}

void start_task(void *pvParameters)
{
    /* 进入临界区,防止创建过程被打断 */
    taskENTER_CRITICAL();

    /* 创建1个任务 */
    xTaskCreate((TaskFunction_t)task1, (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK, (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);

    /* 删除起始任务自身 */
    vTaskDelete(NULL);

    taskEXIT_CRITICAL();
}

/**
 * @description: 任务1——按键控制中断开关
 *               KEY1 → portDISABLE_INTERRUPTS() 关闭中断
 *               KEY2 → portENABLE_INTERRUPTS()  打开中断
 *               使用HAL_Delay模拟工作负载(非vTaskDelay)
 */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* KEY1按下:关闭所有中断——系统将无法响应任何中断 */
            printf(">>>>关闭中断.....\r\n");
            portDISABLE_INTERRUPTS();
        }
        else if (key == KEY2_PRESS)
        {
            /* KEY2按下:打开所有中断 */
            printf(">>>>打开中断.....\r\n");
            portENABLE_INTERRUPTS();
        }

        /* 注意:这里使用HAL_Delay而非vTaskDelay
           HAL_Delay依赖SysTick中断,如果中断被关闭,HAL_Delay将永远卡死!
           vTaskDelay不依赖中断,在中断关闭时仍能正常工作 */
        HAL_Delay(500);
    }
}

Key Config

FreeRTOSConfig.h(中断优先级配置):

/* Cortex-M3 中断优先级配置 */
/* 内核中断优先级:最低优先级 15<<4 = 240(数值最大,优先级最低) */
#define configKERNEL_INTERRUPT_PRIORITY       (15 << 4)

/* 系统调用中断优先级:优先级 5 以上的中断可以调用 FreeRTOS API
   优先级为 0-4 的中断被屏蔽,不能调用 FreeRTOS API
   数值越小优先级越高:0 > 5 > 15                */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY  (5 << 4)

/* 别名配置 */
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY

Core API Reference Table

Function Parameters Return Description
taskENTER_CRITICAL() void 进入临界区(支持嵌套)
taskEXIT_CRITICAL() void 退出临界区(嵌套计数减一)
taskENTER_CRITICAL_FROM_ISR() uint32_t 从中断进入临界区,返回中断屏蔽状态
taskEXIT_CRITICAL_FROM_ISR(ux) uxSavedMaskStatus void 从中断退出临界区,恢复中断状态
portDISABLE_INTERRUPTS() void 直接关闭所有中断
portENABLE_INTERRUPTS() void 直接打开所有中断
portDISABLE_INTERRUPTS_FROM_ISR() UBaseType_t ISR中关闭中断
portENABLE_INTERRUPTS_FROM_ISR(ux) uxSavedStatus void ISR中恢复中断

Common Issues & Pitfalls

  1. HAL_Delay 在中断关闭后卡死HAL_Delay() 依赖 SysTick 中断更新 uwTick 变量。如果通过 portDISABLE_INTERRUPTS() 关闭了中断,SysTick 无法触发,HAL_Delay 将永远死等。
  2. 临界区内不要执行耗时操作:临界区内所有中断被屏蔽,包括 SysTick。长时间停留在临界区内会破坏系统的实时性。
  3. 嵌套临界区必须配对:每个 taskENTER_CRITICAL() 必须有对应的 taskEXIT_CRITICAL(),否则中断永久关闭。
  4. portDISABLE_INTERRUPTS 无嵌套保护:与 taskENTER_CRITICAL() 不同,portDISABLE_INTERRUPTS() 不维护嵌套计数,在嵌套使用时可能会导致提前恢复中断。
  5. BASEPRI 与 NVIC 优先级的关系:STM32 使用 4 位优先级(0~15),在 FreeRTOS 中需左移 4 位变为 CM3 的 8 位格式。例如优先级 5 → 5 << 4 = 0x50
  6. ISR 中必须使用 FromISR 版本:在中断服务函数中调用队列、信号量等 API 时,必须使用带 FromISR 后缀的版本,否则可能导致内核数据结构损坏。