当前位置: 首页 > ops >正文

FreeRTOS学习笔记(四):任务执行与切换

好的,这是一个关于 FreeRTOS 核心机制的绝佳问题。我会详细、系统地讲解任务是如何执行的,以及优先级切换的完整过程。

第一部分:FreeRTOS 任务是如何执行的?

FreeRTOS 是一个抢占式的实时操作系统内核。其任务执行遵循一个核心原则:调度器(Scheduler)总是选择当前处于“就绪态”(Ready)的最高优先级任务来运行。

1. 任务的形态:一个永不返回的函数

每个 FreeRTOS 任务本质上是一个永不返回void 函数,通常是一个无限的 for(;;) 循环或 while(1) 循环。

void vMyTask( void * pvParameters ) // 任务函数原型
{// 可选的初始化代码for( ;; ) // 无限循环,任务的主体{// 任务要做的实际工作...do_work();// 关键点:必须调用一个能让任务“放弃”CPU的FreeRTOS API// 例如,等待一个事件或延迟一段时间vTaskDelay( pdMS_TO_TICKS( 1000 ) ); // 延迟1秒}// 理论上任务不应结束,但如果结束,必须调用 vTaskDelete(NULL) 来删除自己。
}
2. 任务的状态

一个任务在任何时刻都处于以下几种状态之一,理解这些状态是理解调度的关键:

  • 运行态(Running):任务正在 CPU 上执行。在单核处理器上,任何时候都只有一个任务处于此状态。
  • 就绪态(Ready):任务已经准备好可以运行(不被阻塞或挂起),但当前没有运行,因为有一个更高优先级的任务正在运行,或者一个同等优先级的任务正在其时间片内运行。
  • 阻塞态(Blocked):任务正在等待某个事件。事件可以是:
    • 时间相关:例如调用 vTaskDelay() 等待一段时间。
    • 同步事件:例如等待一个信号量(Semaphore)队列(Queue) 消息、任务通知(Task Notification) 等。
    • 处于阻塞态的任务不会被调度器选择执行。当事件发生时,任务会自动离开阻塞态,进入就绪态。
  • 挂起态(Suspended):任务被主动挂起,通过 vTaskSuspend() 实现。被挂起的任务对调度器“不可见”,无论发生什么事件都不会被执行,除非其他任务调用 vTaskResume() 来恢复它。它不参与调度。
3. 任务的控制块(TCB)和栈
  • TCB(Task Control Block):FreeRTOS 为每个任务创建一个数据结构(TCB),用来存储任务的所有元信息,如优先级、堆栈指针、状态、事件列表项等。
  • 栈(Stack):每个任务都有自己独立的堆栈空间,用于存储函数调用链、局部变量和任务挂起时的上下文(CPU寄存器值)。这是实现多任务并发的基石。

第二部分:优先级任务切换的详细过程

任务切换,也称为上下文切换(Context Switch),即保存当前任务的运行环境(上下文),恢复另一个任务的运行环境,并开始执行它。

核心原则:抢占(Preemption)

FreeRTOS 是抢占式调度器。这意味着:

  1. 如果一个更高优先级的任务进入就绪态(例如,它等待的事件发生了),调度器会立即停止当前运行的任务(即使它还没执行完),并切换到更高优先级的任务。
  2. 这保证了系统对高优先级事件的响应是即时的。
触发任务切换的四大场景
  1. 系统时钟节拍(SysTick)中断

    • 这是时间片轮转的基础。SysTick 定时器定期产生中断(例如每1ms一次)。
    • 在中断服务程序(ISR)中,内核会:
      • 递增系统时钟计数器 xTickCount
      • 检查是否有因延时到期而需要从阻塞态唤醒的任务。
      • 检查是否需要任务切换:如果当前任务的时间片已用完,并且存在同等优先级的就绪任务,则会触发切换(同优先级任务轮转)。如果发现了一个更高优先级的任务就绪了,也会触发切换。
    • 这是周期性的自动切换。
  2. 任务主动进入阻塞态

    • 这是最常见、最推荐的切换方式,体现了事件驱动编程思想。
    • 当一个高优先级任务执行到 xQueueReceive(), xSemaphoreTake(), vTaskDelay() 等函数时,因为它要等待的事件尚未发生,它会主动放弃CPU,将自己置于阻塞态
    • 一旦它进入阻塞态,它就不再是“就绪态”的任务,调度器会立刻寻找当前最高优先级的就绪任务来执行。这通常是低优先级或同等优先级的任务。
    • 示例:一个高优先级任务等待一个按键消息。在它等待(阻塞)期间,CPU 会去执行低优先级的 LED 闪烁任务、显示刷新任务等。
  3. 中断服务程序(ISR)使更高优先级任务就绪

    • 这是一个硬件外部中断(如 GPIO 引脚中断、UART 接收中断、定时器中断)触发的切换。
    • 流程:
      1. 硬件中断发生,CPU 跳转到对应的 ISR。
      2. 在 ISR 中,代码通过 xSemaphoreGiveFromISR(), xQueueSendToBackFromISR(), xTaskResumeFromISR()FromISR 系列的 API 给出一个信号量、发送一条消息或恢复一个任务。
      3. 这些 API 会通知调度器:一个更高优先级的任务因为此事件而就绪了。
      4. 在 ISR 的末尾,FreeRTOS 会进行上下文判断:如果被唤醒的任务优先级高于被中断的任务,ISR 退出时会直接触发一次上下文切换,让更高优先级的任务立即运行,而不是先返回被中断的任务。
  4. 任务主动让步(Yield)

    • 任务可以调用 taskYIELD()主动请求调度器立即进行任务切换。
    • 注意:taskYIELD() 并不会使任务进入阻塞态,它只是让任务从运行态变为就绪态,参与下一轮调度。
    • 如果存在同等或更高优先级的任务处于就绪态,则调度器会切换到那个任务。否则,它可能继续执行当前任务。
切换的核心机制:PendSV 异常

为了让切换过程高效且不干扰中断的实时性,FreeRTOS 在 ARM Cortex-M 架构上使用 PendSV(可挂起的系统调用) 异常来执行实际的上下文切换工作。

  1. 触发:上述任何一种场景(如 SysTick ISR、FromISR API)判断需要切换后,并不立刻切换,而是简单地挂起(Pend)一个 PendSV 异常
  2. 延迟执行:PendSV 被设置为最低优先级的中断。这意味着 CPU 会先完成所有高优先级的 ISR 处理(保证中断响应及时)。
  3. 执行切换:当所有高优先级中断处理完毕后,CPU 才来执行 PendSV 异常处理程序(xPortPendSVHandler)。在这里,才会进行繁重的上下文保存和恢复工作
    • 保存上下文:将当前任务的 CPU 寄存器(R4-R11等)压入该任务自己的堆栈
    • 切换TCB:将当前任务的控制块(TCB)指针指向下一个要运行的任务。
    • 恢复上下文:从下一个任务的堆栈中弹出 CPU 寄存器值。
  4. 退出并运行:当 PendSV 异常处理程序退出时,CPU 会自动使用刚刚恢复的寄存器,程序计数器(PC)也随之跳转,于是自然而然就开始执行新的任务了

总结与最佳实践

场景触发方式说明
时间片到期SysTick 中断同优先级任务轮转的基础
任务等待事件vTaskDelay(), xSemaphoreTake(), 等最推荐的方式,事件驱动,高效节能
中断唤醒任务xSemaphoreGiveFromISR(), 等保证高优先级任务对硬件事件的即时响应
主动让步taskYIELD()较少使用,用于计算密集型任务中主动让步

核心设计哲学一个设计良好的 FreeRTOS 应用,其高优先级任务的大部分时间都应处于阻塞态,等待事件发生。 事件到来后(来自中断或其他任务),高优先级任务被唤醒,快速处理事件,处理完毕后立刻又返回阻塞态。这样,低优先级任务就能获得充足的 CPU 时间片来运行。

错误示范(绝对要避免)

void vBadHighPriorityTask( void * pvParameters )
{for( ;; ){// 这是一个“忙等待”(Busy-Waiting)循环// 它永不阻塞,将永远霸占CPU,导致系统被“锁死”// 低优先级任务永远无法运行!process_data();}
}
http://www.xdnf.cn/news/18732.html

相关文章:

  • 入门Ubuntu操作系统
  • 类型签名,位置参数,关键字参数
  • 【Jetson】基于llama.cpp部署gpt-oss-20b(推理与GUI交互)
  • 利用Certbot生成ssl证书配置到nginx
  • Redis--2
  • 从下载到运行:MySQL 详细安装配置完整教程
  • Cloudflare 推出 GenAI 安全工具,守护企业数据
  • AI在提升阅读效率的同时,如何加强理解深度?
  • 2025中国生物制造科技创新论坛为何“花落”常德?
  • arm问题
  • 编写Linux下usb设备驱动方法:probe函数中要进行的工作
  • HTML+CSS+JavaScript实现的AES加密工具网页应用,包含完整的UI界面和加密/解密功能
  • 集成电路学习:什么是ONNX开放神经网络交换
  • 网络编程——TCP、UDP
  • ADC-工业信号采集卡-K004规格书
  • JWT用户认证后微服务间如何认证?(双向TLS(mTLS)、API网关、Refresh Token刷新Token)微服务间不传递用户认证Token
  • zookeeper基础概念及部署
  • Redis缓存雪崩缓存击穿缓存穿透的处理方式
  • java18学习笔记
  • Nuxt.js@4 中管理 HTML <head> 标签
  • AI 伦理的 “灰色地带”:数据隐私与技术创新如何平衡?
  • 零知开源——基于STM32F103RBT6和ADXL335实现SG90舵机姿态控制系统
  • Coze用户账号设置修改用户头像-前端源码
  • 深度学习之第三课PyTorch( MNIST 手写数字识别神经网络模型)
  • AI创业公司:Freya 金融语音AI Agent
  • 电池分选机:破解电池性能一致性难题的自动化方案|深圳比斯特
  • 【VS2022】背景设置详细教程(背景透明)
  • 智数园区-前台
  • Linux的奇妙冒险———进程信号
  • 算法每日一题 | 入门-分支结构-肥胖问题