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

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.dtcms.com/a/351822.html

相关文章:

  • ProfiNet 转 Ethernet/IP基于西门子 S7 - 1500 与罗克韦尔 PLC 的汽车零部件加工线协同案例
  • 基于微服务的水果分销系统-项目分享
  • LeetCode 3000.对角线最长的矩形的面积:一次遍历
  • 【golang长途旅行第32站】反射
  • 【机器学习深度学习】连续微调与权重合并的陷阱与最佳实践
  • 修改C盘缓存文件路径
  • MongoDB /redis/mysql 界面化的数据查看页面App
  • UCIE Specification详解(八)
  • 在MiniOB源码中学习使用Flex与Bison解析SQL语句-第一节
  • Rust 环境搭建与 SeekStorm 项目编译部署(支持中文)
  • Robrain V2.0正式登场:落地人形机器人,引爆智能进化革命
  • Ubuntu操作系统下使用mysql、mongodb、redis
  • [特殊字符] CentOS 7 升级 OpenSSH 10.0p2 完整教程(含 Telnet 备份)
  • 如果 我退休了
  • 汽车域控中Hypervisor方案极致安全原理与弊端
  • APP UI自动化测试的思路总结
  • 破解豆瓣Ajax动态加载:Python爬取完整长评论和短评
  • Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(私有区域)
  • 数据结构:链式队列尝试;0826
  • poi生成word固定表格列宽
  • Spring - 文件上传与下载:真正的企业开发高频需求——Spring Boot文件上传与下载全场景实践指南
  • 位运算卡常技巧详解
  • Charles抓包微信小程序请求响应数据
  • 信号无忧,转决千里:耐达讯自动化PROFIBUS集线器与编码器连接术
  • 快速了解卷积神经网络
  • springweb项目中多线程使用详解
  • 问:单证硕士含金量是否不足?
  • 【Linux 进程】进程程序替换
  • 【GitHub】使用SSH与GitHub交互
  • 工业大模型五层架构全景解析:从算力底座到场景落地的完整链路