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

FreeRTOS 任务调度与管理

1. FreeRTOS 任务调度器:核心概念与机制

FreeRTOS 调度器是实时操作系统的核心,负责确定哪个任务在何时获得 CPU 执行权。其设计遵循严格的优先级驱动规则。

1.1 调度器类型

FreeRTOS 支持三种调度器,通过 FreeRTOSConfig.h 中的宏进行配置:

  1. 抢占式调度器 (Preemptive Scheduler)

    • 机制:这是最常用的模式。调度器始终运行最高优先级的就绪态任务。如果一个比当前运行任务优先级更高的任务进入就绪态,调度器会立即暂停当前任务,切换到更高优先级的任务。

    • 配置:此模式是默认模式,无需特殊配置。

  2. 协作式调度器 (Co-operative Scheduler)

    • 机制:任务调度发生在以下两种情况下:

      • 一个任务显式地调用 taskYIELD()

      • 一个任务进入阻塞状态(例如调用 vTaskDelay()xQueueReceive() 等)。

    • 特点:低优先级任务不会被高优先级任务抢占,除非它主动放弃 CPU。这能提供更可预测的执行流程,但实时性较差。

    • 配置:在 FreeRTOSConfig.h 中定义 configUSE_PREEMPTION 为 0

  3. 带时间片的抢占式调度器 (Preemptive with Time Slicing)

    • 机制:在抢占式调度的基础上,为相同优先级的任务引入时间片概念。多个相同优先级的任务将以时间片为单位共享 CPU 时间。

    • 时间片:长度由系统心跳中断 (configTICK_RATE_HZ) 定义。例如,如果 configTICK_RATE_HZ 为 1000 (1kHz),则一个时间片为 1ms。

    • 行为:调度器在每个 tick 中断 (xPortSysTickHandler) 中检查是否需要进行任务切换。如果当前任务的时间片用完,并且存在相同优先级的就绪任务,则会触发一次上下文切换。

    • 配置:这是默认行为。确保 configUSE_PREEMPTION 和 configUSE_TIME_SLICING 均为 1

1.2 任务状态

一个任务在任何时刻都处于以下状态之一:

  • 运行态 (Running):任务正在 CPU 上执行。

  • 就绪态 (Ready):任务已准备就绪,可以运行,但有一个更高或同等优先级的任务正在运行。

  • 阻塞态 (Blocked):任务正在等待某个事件(如定时器到期、队列数据、信号量、通知等)。任务在阻塞状态下不消耗 CPU 时间。

  • 挂起态 (Suspended):任务被显式地挂起(vTaskSuspend())。它无法进入就绪态,直到被显式恢复(vTaskResume())。它不参与调度。


2. 任务管理相关函数详解

2.1 任务创建 (xTaskCreate / xTaskCreateStatic)

c

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,        // 任务函数指针const char * const pcName,        // 任务描述名(用于调试)configSTACK_DEPTH_TYPE usStackDepth, // 任务堆栈深度(字数,非字节)void *pvParameters,               // 传递给任务函数的参数UBaseType_t uxPriority,           // 任务优先级 (0 to configMAX_PRIORITIES-1)TaskHandle_t *pxCreatedTask );    // 可选的用于传递任务句柄的指针
  • 堆栈深度:需要根据函数调用深度和局部变量大小谨慎设置。可通过 uxTaskGetStackHighWaterMark() 函数监控堆栈使用峰值。

  • 优先级:数值越高,优先级越高。configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义,最大允许值为 32(受架构限制)。

  • 返回值pdPASS 表示成功,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 表示堆栈空间分配失败。

2.2 任务删除 (vTaskDelete)

c

void vTaskDelete( TaskHandle_t xTaskToDelete );
  • 可以删除其他任务或自身(参数传 NULL)。

  • 被删除的任务的资源(堆栈、TCB)由空闲任务 (Idle Task) 负责回收。因此,删除大量任务时需确保空闲任务有执行时间。

2.3 任务延时 (vTaskDelay / vTaskDelayUntil)

c

void vTaskDelay( const TickType_t xTicksToDelay ); // 相对延时void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, // 指向一个变量,用于存储任务上次解除阻塞的时间const TickType_t xTimeIncrement ); // 固定的周期时间
  • vTaskDelay相对延时。调用该函数后,任务阻塞 xTicksToDelay 个 tick。不适用于精确定时,因为从任务就绪到再次被调度执行存在不确定性。

  • vTaskDelayUntil绝对延时。用于实现固定频率的周期性任务。它能补偿任务本身执行时间带来的误差,是更精确的选择。

2.4 优先级控制

c

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
  • 可用于动态改变任务的优先级,实现诸如优先级继承协议 (Priority Inheritance Protocol) 或其他复杂调度策略。

2.5 任务挂起与恢复 (vTaskSuspend / vTaskResume)

c

void vTaskSuspend( TaskHandle_t xTaskToSuspend );
void vTaskResume( TaskHandle_t xTaskToResume );
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ); // 从中断中恢复
  • vTaskSuspend() 和 vTaskResume() 必须成对使用。

  • xTaskResumeFromISR() 专为中断服务程序设计,其返回值 pdTRUE 表示恢复的任务优先级更高,可能需要在中断退出前请求一次上下文切换(portYIELD_FROM_ISR())。


3. 内核实践与高级主题

3.1 空闲任务 (Idle Task) 与空闲钩子 (Idle Hook)

  • 空闲任务是在没有其他任务运行时自动运行的优先级为 0 的任务。

  • 可以定义一个 vApplicationIdleHook() 函数(需在 FreeRTOSConfig.h 中使能 configUSE_IDLE_HOOK)来让内核在空闲任务中执行后台功能,如低功耗处理(调用 WFI 指令)。

  • 警告:空闲钩子函数绝不能阻塞或挂起。

3.2 Tickless 低功耗模式

  • 在应用场景中,当系统空闲时,可以通过关闭 SysTick 中断来让 MCU 进入深度睡眠,显著降低功耗。

  • 通过配置 configUSE_TICKLESS_IDLE 为 1 或 2 来启用。需要根据具体 MCU 实现 portSUPPRESS_TICKS_AND_SLEEP() 函数。

3.3 调度器锁定 (vTaskSuspendAll / xTaskResumeAll)

  • 调用 vTaskSuspendAll() 可以临时挂起调度器,禁止任务切换,但中断仍然使能。

  • 调用 xTaskResumeAll() 恢复调度器。如果恢复过程中有更高优先级的任务就绪,会立即触发一次切换。

  • 用于保护非线程安全的代码段或执行精密的时序操作。应尽量缩短锁定时间


4. 综合应用实例:多任务系统与精确计时

以下实例创建了三个任务:一个高优先级任务,两个相同优先级的周期性任务。

c

#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>// 任务句柄
TaskHandle_t xPeriodicTaskHandle1, xPeriodicTaskHandle2;// 高优先级任务
void vHighPriorityTask(void *pvParameters) {while(1) {printf("[HP Task] Running...\n");vTaskDelay( pdMS_TO_TICKS(500) ); // 每500ms执行一次}
}// 周期性任务1 (使用 vTaskDelayUntil)
void vPeriodicTask1(void *pvParameters) {TickType_t xLastWakeTime;const TickType_t xFrequency = pdMS_TO_TICKS(1000); // 周期1000msxLastWakeTime = xTaskGetTickCount(); // 初始化基准时间while(1) {printf("[Task1] Tick at %lu\n", xTaskGetTickCount());// 绝对延时,精确保证1000ms周期vTaskDelayUntil(&xLastWakeTime, xFrequency);}
}// 周期性任务2 (使用 vTaskDelay)
void vPeriodicTask2(void *pvParameters) {while(1) {printf("[Task2] Tick at %lu\n", xTaskGetTickCount());vTaskDelay( pdMS_TO_TICKS(1000) ); // 相对延时1000ms}
}void main(void) {// 创建高优先级任务xTaskCreate(vHighPriorityTask, "HP Task", 1024, NULL, 3, NULL);// 创建两个相同优先级的周期性任务xTaskCreate(vPeriodicTask1, "Periodic1", 1024, NULL, 2, &xPeriodicTaskHandle1);xTaskCreate(vPeriodicTask2, "Periodic2", 1024, NULL, 2, &xPeriodicTaskHandle2);// 启动调度器vTaskStartScheduler();// 如果调度器启动成功,不会运行到这里while(1);
}

运行分析与预期输出

  1. 高优先级任务(优先级3)将抢占两个周期性任务(优先级2)。

  2. 每500ms,高优先级任务会运行一次,打印消息。

  3. 在两个周期性任务中:

    • Task1 使用 vTaskDelayUntil,其打印间隔会非常稳定地接近 1000ms,因为它补偿了执行和调度延迟。

    • Task2 使用 vTaskDelay,其打印间隔会是 1000ms + 执行延迟 + 被高优先级任务抢占的时间,因此间隔会长于且不稳定

  4. 当 Task1 和 Task2 同时就绪时,由于优先级相同,它们将共享时间片,你会在一个大概的 1000ms 周期内看到两条打印信息交错出现。


文章转载自:

http://41WgmrP8.wrysm.cn
http://4dVj7HRX.wrysm.cn
http://wd9OFVd3.wrysm.cn
http://qNJmpWAx.wrysm.cn
http://AcvDKIxi.wrysm.cn
http://hMju3Dga.wrysm.cn
http://hi9Zekyi.wrysm.cn
http://dr2SZQC7.wrysm.cn
http://Dy0DRGR6.wrysm.cn
http://AWmKKFdQ.wrysm.cn
http://4oKzvIAX.wrysm.cn
http://it8HDgw7.wrysm.cn
http://7kXwH6F1.wrysm.cn
http://fzlR17pY.wrysm.cn
http://8WaVXtfl.wrysm.cn
http://Pvuxgpq9.wrysm.cn
http://ecQyp9Yv.wrysm.cn
http://bP6zB6uT.wrysm.cn
http://0LUTQ7jQ.wrysm.cn
http://XlcN7aih.wrysm.cn
http://0439gSDe.wrysm.cn
http://mW9NJ5wa.wrysm.cn
http://BwpHCyhF.wrysm.cn
http://oYnMAti5.wrysm.cn
http://8FBpiLrx.wrysm.cn
http://sHpaqfWS.wrysm.cn
http://TLS1eZ5d.wrysm.cn
http://9OBjDOpd.wrysm.cn
http://g01MNlnS.wrysm.cn
http://NXjNxyvF.wrysm.cn
http://www.dtcms.com/a/387673.html

相关文章:

  • CI/CD 实战:GitHub Actions 自动化部署 Spring Boot 项目
  • 《第一课——环境搭建:告别JVM温室,构建你的第一个C程序》
  • 2025最新超详细FreeRTOS入门教程:第二十一章 FreeRTOS在物联网与边缘计算中的应用
  • VMware Workstation 快照恢复后,软件消失与空间占用的真相
  • VS2022中使用cmake+vcpkg工具链编译meshlab源码
  • Trunk的配置
  • k8s和prometheus面试都会问哪些问题?
  • web前端批量下载图片(另存为)最佳实践
  • 水题记录2
  • 苏州金龙闪耀比利时世界客车展:纯电新V系“绿”动未来
  • 漫谈网页自动化与数据采集的发展历程
  • Python 中的封装
  • 实测AI Ping,一个大模型服务选型的实用工具
  • ngrok 深度解析:内网穿透的高效解决方案
  • 总共分为几种IP
  • A股大盘数据-20250917分析
  • PyQt5中QLineEdit控件数值显示与小数位数控制
  • DeepSeek V3 深度解析:MoE、MLA 与 GRPO 的架构革新
  • 金蝶云星空插件开发记录(二)
  • Linux服务器中CPU100%如何排查
  • 从源代码开始构建、部署和管理应用程序
  • Java虚拟线程原理与性能优化实践指南
  • Java注解+com.fasterxml.jackson信息脱敏
  • Docker 镜像瘦身实战:从 1.2GB 压缩到 200MB 的优化过程——Node.js 前端 SSR 场景的“node_modules 大屠杀”
  • 外网穿透到内网---访问公网IP映射到内网IP---frp使用
  • Google Veo 3 实战指南:三步告别AI视频“PPT感”
  • NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置指南
  • 【Android】进程间如何通信
  • 从代码源码角度 解读 open-vla 算法架构
  • javaweb Tomcat及运行/HTTP