FreeRTOS 任务与中断函数:运行机制、关键区别与使用准则
一、任务相关函数(Task-related Functions)
任务是 FreeRTOS 的基本执行单元,运行在操作系统的调度器控制下,属于线程级代码,通常用于实现复杂的、非实时性要求极高的逻辑。
核心特性:
- 运行在任务上下文(Task Context),可被调度器暂停、恢复或切换。
- 允许阻塞(如vTaskDelay()),阻塞时会释放 CPU 资源给其他任务。
- 函数名通常不带FromISR后缀。
- 可安全访问大部分 FreeRTOS 内核对象(队列、信号量等)。
常用函数分类:
- 任务创建与管理
- xTaskCreate():创建新任务(动态分配栈空间)。
- xTaskCreateStatic():创建新任务(静态分配栈空间,需手动指定栈和控制块)。
- vTaskDelete():删除指定任务。
- vTaskSuspend() / vTaskResume():暂停 / 恢复指定任务。
- vTaskDelay():任务延迟(相对时间,单位:ticks)。
- vTaskDelayUntil():任务延迟(绝对时间,用于周期性任务)。
- 内核对象操作(任务上下文)
- 队列:xQueueSend()、xQueueReceive()、xQueuePeek()等。
- 信号量:xSemaphoreGive()、xSemaphoreTake()等。
- 事件组:xEventGroupSetBits()、xEventGroupWaitBits()等。
二、中断相关函数(Interrupt-related Functions)
中断是硬件或软件触发的异步事件,用于快速响应紧急操作(如 GPIO 电平变化、定时器溢出等)。中断服务程序(ISR)运行在中断上下文,需尽可能简短,避免阻塞。
核心特性:
- 运行在中断上下文,优先级高于所有任务,不可被调度器暂停。
- 禁止阻塞操作(如vTaskDelay()),否则会导致系统崩溃。
- 函数名通常带FromISR后缀,明确标识用于 ISR。
- 操作内核对象时需使用专门的中断安全版本,且需处理 “中断嵌套” 和 “上下文切换请求”。
常用函数分类:
- 内核对象操作(中断上下文)
- 队列:xQueueSendFromISR()、xQueueReceiveFromISR()等(需传入pxHigherPriorityTaskWoken参数)。
- 信号量:xSemaphoreGiveFromISR()、xSemaphoreTakeFromISR()等。
- 事件组:xEventGroupSetBitsFromISR()等。
- 中断管理辅助函数
- portYIELD_FROM_ISR():在 ISR 中请求上下文切换(当pxHigherPriorityTaskWoken为pdTRUE时调用)。
- uxTaskPriorityGetFromISR():获取任务优先级(中断安全版本)。
- vTaskNotifyGiveFromISR():发送任务通知(中断安全版本)。
三、关键区别对比
维度 | 任务函数(非 FromISR) | 中断函数(FromISR) |
运行上下文 | 任务上下文(可被调度) | 中断上下文(不可被调度) |
是否允许阻塞 | 允许(如vTaskDelay()) | 禁止(否则系统崩溃) |
函数名标识 | 无FromISR后缀 | 带FromISR后缀 |
内核对象操作 | 直接操作,无需额外参数 | 需传入pxHigherPriorityTaskWoken,用于判断是否需要切换上下文 |
执行时间 | 可长(复杂逻辑) | 必须短(避免影响系统响应) |
优先级 | 受调度器管理(0~configMAX_PRIORITIES-1) | 由硬件 / 内核决定(通常高于任务) |
四、使用原则
- 任务中调用非 FromISR 函数:任务上下文允许阻塞和复杂操作,优先使用普通版本函数。
- ISR 中必须调用 FromISR 函数:中断上下文禁止阻塞,必须使用中断安全版本,且操作完成后需通过portYIELD_FROM_ISR()触发上下文切换(如果需要)。
- 避免在 ISR 中执行耗时操作:ISR 应仅完成必要工作(如数据传递给任务),复杂逻辑交给任务处理。
A_Task()任务调度
在while中调用
阻塞超时时间0---100个Tick
失败就阻塞 100个Tick
---------------------------------------------------------------------------------------------------------------------------------
Key_ISR()中断
调用
不进行阻塞超时间 也就是0个Tick
在中断中使用这个函数,要么成功,要么失败
---------------------------------------------------------------------------------------------------------------------------------
A写队列,当写队列时有可能唤醒任务B,B的优先级高于任务A,任务B马上运行,任务B一直没有主动放弃处理器资源的话,任务A就无法执行。这就是一个严重问题。
唤醒问题:
---------------------------------------------------------------------------------------------------------------------------------
就是当写队列A时,在函数内部把任务B唤醒了,任务B的优先级更高,导致函数迟迟无法返回,所以时间为任意(0-∞),B迟迟不放弃运行的话,任务A就永远无法运行。
这个函数
- 进行写队列
- 写队列失败后,进行阻塞,阻塞的时间最多为100TICK
- 写成功后,wake up 。如果唤醒的任务的优先级的任务更高,原来的任务就无法被运行。
---------------------------------------------------------------------------------------------------------------------------------
在中断中使用这个函数,要么成功,要么失败,不会进行等待
调用这个函数
- 是否要唤醒B 答案是的
- 是否需要马上切换 不进行马上切换,当中断快结束时,再进行切换。函数内部不调用切换
---------------------------------------------------------------------------------------------------------------------------------
假设调用KEY_ISR中断,
写100个数据,假设队列里面有100个任务,等待写入数据
通过中断写队列,队列里面有个链表有1-100个任务,都在等待数据
调用这个函数
- 唤醒 就是从阻塞链表移动到就绪链表 不花什么时间
- 切换 比较复杂 保存当前的任务现场,恢复新任务现场
涉及到寄存器的写 ,寄存器的读 比较耗时间
在中断里面切换有意义吗?
没有意义,因为你再怎么切换都无法立刻被执行,因为中断的优先级永远高于所有任务
当处于中断时,无法执行其他任务,当中断结束,切换为其他任务
所以
调用这个函数时,就不进行切换 ,改为记录
- 唤醒 就是从阻塞链表移动到就绪链表 不花什么时间
- 记录 记录是否有更高优先级的任务被唤醒
当执行完这些复杂操作之后,在退出中断之前再切换
---------------------------------------------------------------------------------------------------------------------------------
在这两个场景里面有很大的差别,所以使用这个函数
---------------------------------------------------------------------------------------------------------------------------------
改进实时问题
就是优先级A运行时,发生中断,会唤醒B(高优先级任务)。
按理来说应该当中断结束后,立刻进行高优先级的任务B
但是B没有运行,而是恢复任务A,
当下一个Tick中断到来时,切换为任务B
那么这段过程中任务B就是被延时了
为什么B被延时,就是由于在触发中断时,没有进行发起调度,从而导致了延时,所以为了实时性,应该在中断结束前发起调度
只进行了唤醒,而没有调度,导致实时性有所差距
---------------------------------------------------------------------------------------------------------------------------------
如何进行调度呢?
使用函数
切换函数
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
示例
void XXX_ISR()
{int i;BaseType_t xHigherPriorityTaskWoken = pdFALSE;for (i = 0; i < N; i++){xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */}/* 最后再决定是否进行任务切换 * xHigherPriorityTaskWoken为pdTRUE时才切换*/portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
xHigherPriorityTaskWoken 用于标记是否有更高优先级任务因队列操作被唤醒,需在中断结束时判断是否触发任务切换。
xQueueSendToBackFromISR 是 FreeRTOS 中断安全的队列发送函数(向队列尾部发数据),循环里多次调用,每次传 &xHigherPriorityTaskWoken 记录调度需求。
最后用 portYIELD_FROM_ISR ,根据 xHigherPriorityTaskWoken 的值(pdTRUE 则触发),决定是否在中断退出时切换到被唤醒的高优先级任务,保证实时性。