FreeRTOS 任务调度 系统节拍

本文详细解析FreeRTOS中的任务调度,包括xTaskIncrementTick()函数的作用,如节拍计数器溢出、任务超时唤醒、时间片轮询等。同时介绍了两种系统延时函数vTaskDelay和vTaskDelayUntil的使用,特别是vTaskDelayUntil用于确保周期性任务准确执行的机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@(嵌入式)

Freertos
FreeRtos

简述

文章 < FreeRTOS 任务调度 任务切换 > 记录了 FreeRTOS 中任务切换的过程, 提到触发任务切换的两种情况 : 高优先级任务就绪抢占和同优先级任务时间共享(包括提前挂起)。 系统中,时间延时和任务阻塞,时间片都以 Systick 为单位。

通过设置文件 FreeRTOSConfig.hconfigTICK_RATE_HZ 设置任务节拍中断频率, 在启动任务调度器时,系统会根据另一个变量, CPU 的频率 configCPU_CLOCK_HZ 计算对应写入节拍计数器的值,启动定时器中断。

系统在每一次节拍计数器中断服务程序xPortSysTickHandler(平台实现 port.c 中) 中调用处理函数 xTaskIncrementTick, 依据该函数返回值判断是否需要触发 PendSV 异常, 进行任务切换。
涉及任务时间片轮循, 任务阻塞超时, 以及结束以此实现的延时函数。

分析的源码版本是 v9.0.0

xTaskIncrementTick()

系统每次节拍中断服务程序中主要任务由函数 xTaskIncrementTick 完成。
在任务调度器没有挂起的情况下( xTaskIncrementTick != pdFALSE ),该函数主要完成 :
* 判断节拍计数器xTickCount 是否溢出, 溢出轮换延时函数队列
* 判断是否有阻塞任务超时,取出插入就绪链表
* 同优先级任务时间片轮

而当任务调度器被挂起时, 该函数累加挂起时间计数器 uxPendedTicks, 调用用户钩子函数, 此时,正在运行的任务不会被切换, 一直运行。
当恢复调度时, 系统会先重复调用 xTaskIncrementTick 补偿 (uxPendedTicks次)。

不管, 系统调度器是否挂起, 每次节拍中断都会调用用户的钩子函数 vApplicationTickHook。 由于函数是中断中调用,不要在里面处理太复杂的事情!!

节拍计数器溢出

涉及的变量, 定义在 task.c开头。

PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;

初始化时, pxDelayedTaskList 指向 xDelayedTaskList1pxOverflowDelayedTaskList 指向 pxOverflowDelayedTaskList,一开始我还在郁闷延时链表为什么要两个,到这里才明白。

当任务由于等待事件(延时,消息队列什么的堵塞)时,会设置一个时间,这时候,响应的任务会被挂到延时链表中,如果超过设置时间没有事件响应,则系统会从延时链表中取出任务恢复就绪。
系统任务延时参考系统节拍计数器 xTickCount, 加入链表前依据当前计数器的值计算出超时的值 ( xTickCount+ xTicksToDelay ), 顺序插入到延时链表中。

对不同平台xTickCount 表示的位数不同,但是每次节拍中断加一,总会溢出。 上述计算任务延时时间,如果系统发现计算出来的时间已经溢出,则会将该任务加入到 pxOverflowDelayedTaskList 这个链表中。
在系统节拍中断时, 节拍计数器每次加一, 系统判断是否溢出,如果溢出, 调用宏 taskSWITCH_DELAYED_LISTS()切换上述的链表指针。
宏主要实现

<think>我们正在讨论FreeRTOS任务调度优化技术。FreeRTOS是一个实时操作系统任务调度是其核心功能之一。优化任务调度可以提高系统的实时性和效率。 首先,我们需要了解FreeRTOS的调度机制:它支持抢占式调度和时间片轮转(可选)。任务优先级决定了任务执行的顺序,高优先级任务可以抢占低优先级任务。 优化任务调度可以从以下几个方面考虑: 1. 合理设置任务优先级:确保关键任务有足够高的优先级,但避免优先级反转问题。可以使用优先级继承协议(Priority Inheritance Protocol)或优先级天花板协议(Priority Ceiling Protocol)来避免优先级反转。 2. 优化任务数量:减少任务数量,将功能相似的任务合并,以减少上下文切换的开销。 3. 调整时间片:如果使用时间片轮转调度,合理设置时间片长度,避免频繁的上下文切换。 4. 使用空闲任务钩子函数(Idle Task Hook):在空闲任务中执行低优先级后台任务,但注意不要长时间阻塞空闲任务。 5. 使用任务通知(Task Notification)代替信号量、队列等:任务通知是一种轻量级的通信机制,速度更快,内存占用更少。 6. 使用软件定时器(Software Timers)处理周期性任务:避免创建多个任务来处理周期性事件,减少任务数量。 7. 优化中断服务程序(ISR):中断处理应尽量短,将耗时操作放到任务中处理。高频率的中断会增加调度器的负担。 8. 使用低功耗模式:当系统空闲时,进入低功耗模式,减少能耗,同时也可以减少调度器的负担(因为唤醒后才会进行调度)。 9. 配置调度器参数:根据具体应用调整FreeRTOS的配置参数,例如`configTICK_RATE_HZ`(系统节拍频率)不宜设置过高,以减少系统节拍中断的开销。 10. 使用任务挂起(Suspend)和恢复(Resume)来管理任务:在任务不需要运行时挂起,减少调度器的就绪任务数量。 11. 使用协程(Co-routines):虽然FreeRTOS支持协程,但注意协程在较新的版本中已被弃用,且使用较少,一般不推荐。 注意:优化时需要根据具体应用场景进行权衡,比如实时性要求、功耗要求等。 下面我们通过一个简单的例子来说明如何使用任务通知代替队列进行任务间通信,从而减少开销: 传统队列方式: ```c // 发送任务 xQueueSend(queue, &data, portMAX_DELAY); // 接收任务 xQueueReceive(queue, &data, portMAX_DELAY); ``` 使用任务通知: ```c // 发送任务 xTaskNotify(task_handle, value, eSetValueWithOverwrite); // 接收任务 xTaskNotifyWait(0, ULONG_MAX, &value, portMAX_DELAY); ``` 任务通知不需要创建队列,因此节省了内存,并且速度更快。 另外,关于任务优先级的设置,可以遵循以下原则: - 中断服务函数(ISR)触发的任务应设置为高优先级,以保证实时性。 - 周期性任务根据其周期长短设置优先级,通常周期短的任务优先级高(如控制任务),周期长的任务优先级低(如数据记录任务)。 - 用户交互任务可以设置为中等优先级。 最后,注意在优化过程中,使用FreeRTOS提供的跟踪工具(如`traceTASK_SWITCHED_IN`等宏)来分析调度行为,找出瓶颈。 参考文献: [1] FreeRTOS官方文档:https://www.freertos.org/ [2] 《Mastering the FreeRTOS Real Time Kernel》书籍 根据上述内容,生成相关问题:</think>### FreeRTOS任务调度优化技术 在FreeRTOS中优化任务调度性能需要从系统配置、任务设计和调度策略三个层面入手,以下是关键优化技术: 1. **优先级优化** - 合理分配任务优先级:确保实时性任务(如电机控制)具有最高优先级,非关键任务(如日志记录)使用较低优先级 - 使用优先级继承协议(`configUSE_PRIORITY_INHERITANCE`)避免优先级反转问题 - 示例优先级配置: ```c #define TASK_HIGH_PRIO (configMAX_PRIORITIES - 1) // 最高优先级 #define TASK_MID_PRIO (configMAX_PRIORITIES / 2) #define TASK_LOW_PRIO 1 // 最低优先级 ``` 2. **调度策略调整** - 启用抢占式调度(默认配置):高优先级任务可立即抢占低优先级任务 - 选择性使用时间片轮询(`configUSE_TIME_SLICING`):对同优先级任务公平分配CPU时间 - 关闭不必要的调度特性(如协程)减少开销: ```c #define configUSE_CO_ROUTINES 0 // 禁用协程 ``` 3. **任务设计优化** - **任务拆分**:将大任务分解为小任务,减少单次执行时间 ```c // 原始任务 void BigTask(void *pv) { Step1(); // 耗时操作 Step2(); // 耗时操作 } // 优化后 void SmallTask1(void *pv) { Step1(); } void SmallTask2(void *pv) { Step2(); } ``` - **阻塞机制**:使用`vTaskDelay()`或事件等待(信号量/队列)主动释放CPU - **任务通知**替代队列/信号量:减少内存占用和切换延迟 ```c // 传统队列 vs 任务通知 xQueueSend(queue, &data, 0); // 约10-30个周期 xTaskNotify(task, value, eSetValueWithOverwrite); // 仅需个位数周期 ``` 4. **系统配置优化** - 调整时钟节拍频率(`configTICK_RATE_HZ`): $$ f_{tick} = \frac{1}{t_{min}} \times k $$ 其中 $t_{min}$ 是最小时间精度需求,$k$ 为安全系数(通常2-10) - 优化栈分配:使用`uxTaskGetStackHighWaterMark()`检测实际栈使用量 - 启用调度器统计(`configGENERATE_RUN_TIME_STATS`)进行性能分析 5. **中断优化** - 将长中断处理移至任务(使用二值信号量触发) - 限制中断嵌套深度(`configMAX_API_CALL_INTERRUPT_PRIORITY`) - 使用`xHigherPriorityTaskWoken`参数高效唤醒任务: ```c void ISR_Handler() { BaseType_t xNeedSwitch = pdFALSE; xSemaphoreGiveFromISR(sem, &xNeedSwitch); portYIELD_FROM_ISR(xNeedSwitch); } ``` 6. **高级技术** - **Tickless模式**(`configUSE_TICKLESS_IDLE`):空闲时停用节拍中断,降低功耗[^1] - **CPU亲和性**(多核系统):通过`vTaskCoreAffinitySet()`绑定任务到特定核心 - **延迟中断**(`LPIT`):对非实时任务使用低优先级中断 > ⚠️ 优化前后务必使用FreeRTOS的跟踪工具(如`traceTASK_SWITCHED_IN`)对比调度延迟,确保实时性要求满足设计指标。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值