FreeRTOS进入中断后为什么要先进入临界段

复制代码
void DEBUG_USART_IRQHandler(void)
{
  uint32_t ulReturn;
  ulReturn = taskENTER_CRITICAL_FROM_ISR();/* 进入临界段,临界段可以嵌套 */
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
	{		
		Uart_DMA_Rx_Data();       /* 释放一个信号量,表示数据已接收 */
		USART_ReceiveData(DEBUG_USARTx); /* 清除标志位 */
	}	 
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );/* 退出临界段 */
}
1. 中断专用临界段(taskENTER_CRITICAL_FROM_ISR)的本质:不关闭当前中断,仅屏蔽低优先级中断

taskENTER_CRITICAL_FROM_ISR是 FreeRTOS 提供的中断专用临界段入口函数,它的核心作用是:

  • 屏蔽优先级低于当前中断的其他中断(防止当前中断处理流程被低优先级中断打断);
  • 不关闭当前正在执行的 IDLE 中断,也不会清除当前 IDLE 中断的标志位;
  • 临界段是 "嵌套式" 的,不会影响当前中断自身的执行流程。

简单说:当前 IDLE 中断已经触发并进入服务函数,临界段只是 "挡住其他低优先级中断的干扰",对当前已经发生的 IDLE 中断和其标志位毫无影响。

2. IDLE 中断标志位的特性:一旦触发就保持置位,直到软件清除

USART 的 IDLE 中断标志(USART_IT_IDLE)有一个关键特性:

  • 当串口接收完一帧数据、无新数据输入时,硬件会自动将 IDLE 标志位置 1(触发中断);
  • 该标志位一旦置 1,会持续保持置位状态,不会自动清零,也不会被临界段操作改变;
  • 只有通过 "读取 USART 数据寄存器(DR)"(即代码中的USART_ReceiveData(DEBUG_USARTx))才能手动清除该标志位。

因此,即使进入临界段后再检测标志位,该标志依然是置位状态,USART_GetITStatus能正常检测到。

3. 代码执行逻辑的时序:中断触发在前,临界段进入在后

整个流程的时序是不可逆的,确保了标志位必然能被检测到:

  1. 硬件触发 IDLE 中断 :串口接收完成→IDLE 标志位置 1→CPU 响应中断,暂停当前任务,跳转到DEBUG_USART_IRQHandler入口;
  2. 进入中断服务函数:CPU 已经确认 IDLE 中断发生,才会执行中断服务函数内的代码;
  3. 进入临界段:此时 IDLE 标志位早已置位,临界段操作不会改变这个事实;
  4. 检测标志位USART_GetITStatus读取到已置位的 IDLE 标志,执行后续数据处理逻辑。

简单理解:"中断触发" 是 "进入中断服务函数" 的前提,而 "进入临界段" 是中断服务函数内的后续操作,不可能出现 "进入临界段后,中断才触发" 的时序颠倒。

三、该代码中临界段的真正作用(不是为了中断检测,而是保护后续操作)

这段代码中,临界段的目的不是为了检测 IDLE 中断,而是为了保护Uart_DMA_Rx_Data()中的关键操作不被干扰

  • Uart_DMA_Rx_Data()中包含 DMA 配置修改(关闭 / 使能 DMA、重置接收长度)、信号量释放等原子操作;
  • 如果没有临界段保护,这些操作可能被其他更高优先级的中断(注:实际是屏蔽低优先级中断,防止当前中断被嵌套打断)打断,导致 DMA 配置出错、信号量释放异常等问题;
  • 临界段保证了Uart_DMA_Rx_Data()USART_ReceiveData()的执行完整性,避免并发问题。
场景 1:高优先级中断执行过程中,低优先级中断请求产生(无法抢占,但会挂起等待)

假设当前串口 IDLE 中断(抢占优先级 7,较高)正在执行,此时:

  1. 全局中断是开启的,CPU 会持续检测中断请求线;
  2. 若有一个低优先级中断(比如定时器中断,抢占优先级 8,更低)产生请求,由于其抢占优先级低于当前串口中断,无法立即打断串口中断的执行
  3. 但这个低优先级中断的请求会被硬件挂起(标志位置位),不会消失;
  4. 当串口中断服务函数执行完毕、退出中断后,CPU 会立即响应这个挂起的低优先级中断,去执行它的服务函数。

这种情况下,低优先级中断虽然没「打断」高优先级中断,但会导致「高优先级中断对应的任务切换被延迟」(比如串口中断释放信号量后,本应立即切换到KEY_Task,但却先执行了低优先级中断,任务切换被延后)。

场景 2:高优先级中断服务函数中,存在「指令间隙」或「耗时操作」,低优先级中断可能嵌套(特殊情况)

更关键的是:高优先级中断的服务函数不是「原子执行」的,它由多条机器指令组成,在指令执行的间隙,若有低优先级中断请求已挂起,可能出现「嵌套」(注意:不是低优先级抢占高优先级,而是高优先级中断执行中,全局中断开启,允许更高优先级中断嵌套,但低优先级中断也可能在特定情况下插入)。

举个通俗例子:串口 IDLE 中断(高优先级)正在执行「关闭 DMA→清除标志→重置 DMA 长度→使能 DMA」这 4 步操作,每一步对应多条 CPU 指令:

  1. 执行完「关闭 DMA」后,CPU 需要取下一步指令(存在短暂间隙);
  2. 此时若有低优先级中断请求已挂起,虽然它不能抢占高优先级中断,但由于全局中断是开启的,硬件会标记「有未响应的中断请求」;
  3. 若高优先级中断的后续指令执行较慢,或存在短暂等待,这个低优先级中断就可能「插入」到高优先级中断的执行流程中(嵌套执行),导致:
    • DMA 配置流程被拆分(关闭 DMA 后,还没重置长度,就去执行低优先级中断);
    • 等低优先级中断执行完,回到串口中断继续执行时,DMA 状态可能异常(比如被其他操作修改)。

这种「嵌套干扰」不是「低优先级抢占高优先级」,而是「高优先级中断执行中,全局中断开启,允许低优先级中断在指令间隙插入嵌套」,最终导致关键操作(如 DMA 配置、信号量释放)失去原子性。

三、代码中临界段的真正作用:不是防止「被低优先级打断」,而是「屏蔽低优先级中断的干扰 / 嵌套」

taskENTER_CRITICAL_FROM_ISR的核心功能是通过设置 NVIC 的 BASEPRI 寄存器,屏蔽所有「抢占优先级低于某一阈值」的中断 (阈值由 FreeRTOS 的configMAX_SYSCALL_INTERRUPT_PRIORITY配置),它的目的不是防止高优先级中断被低优先级中断「抢占」(本来就不会),而是:

  1. 保证当前中断服务函数中关键操作的原子性:把「Uart_DMA_Rx_Data ()(DMA 配置 + 信号量释放)+ USART_ReceiveData ()(清除标志)」打包成一个「不可拆分」的原子操作,避免被低优先级中断嵌套插入,防止 DMA 配置出错、信号量释放不完整。

  2. 避免低优先级中断请求挂起,导致任务切换延迟 :串口中断释放信号量后,需要通过portYIELD_FROM_ISR触发任务切换(切换到KEY_Task)。如果不屏蔽低优先级中断,串口中断退出后,会先执行挂起的低优先级中断,再进行任务切换,导致数据处理任务被延迟响应。

  3. 保护 FreeRTOS 内核对象的操作安全xSemaphoreGiveFromISR是 FreeRTOS 内核 API,操作内核的信号量控制块。如果在执行这个 API 时,被低优先级中断嵌套打断,可能导致信号量的链表 / 计数被篡改,引发系统异常。

四、通俗类比

可以把中断执行比作「医院看病」:

  • 高优先级中断 = 急诊病人(优先看病,没人能插队);
  • 低优先级中断 = 普通门诊病人(正常情况下不能打断急诊);
  • 全局中断开启 = 急诊室的门没锁,护士会随时进来提醒「有普通病人在等」;
  • 临界段 = 急诊医生锁上门,告诉护士「先别打扰,我正在做关键手术(DMA 配置 + 信号量释放),做完再说」。

急诊医生(高优先级中断)不会被普通病人(低优先级中断)插队(抢占),但如果门没锁(没进入临界段),护士频繁进来提醒(低优先级中断请求挂起),会打断医生的手术节奏(指令间隙被干扰),甚至可能导致手术步骤出错(关键操作拆分)。锁门(临界段)不是为了防止插队,而是为了保证手术能连续、完整地完成。

相关推荐
森旺电子1 天前
信号量核心API函数详细介绍
freertos
Zeku1 天前
20251224 - 嵌入式 Linux 开发中的MQTT指南
stm32·freertos·linux驱动开发·linux应用开发
Zeku2 天前
20251222 - 韦东山Linux开发板I.MX6ULL连接无线WiFi
stm32·freertos·linux驱动开发·linux应用开发
Zeku2 天前
20251222 - 常用命令“source etcprofile”的详细解析
stm32·freertos·linux驱动开发·linux应用开发
Zeku2 天前
20251202 - Linux输入子系统支持的操作机制
stm32·freertos·linux驱动开发·linux应用开发
Zeku3 天前
20251202 - Linux输入子系统
stm32·freertos·嵌入式软件·linux驱动开发·linux应用开发
Zeku3 天前
20251202 - Linux输入系统的基础知识 - tslib
stm32·freertos·linux驱动开发·linux应用开发
离凌寒5 天前
一、基于freertos下对LAN8720模块进行通信测试
网络·freertos
charlie1145141919 天前
在上位机上熟悉FreeRTOS API
笔记·学习·嵌入式·c·freertos·工程