freertos任务管理
任务
在FreeRTOS中,线程(Thread)和任务(Task)的概念是相同的。每个任务就是一个线程,有着自己的一个程序。
1 | void TaskFunction( void *pvParameters ) |
- 任务函数不能有返回值,即使用return语句。
- 不需要任务时必须显示删除任务,如调用vTaskDelete()函数。
- 每个任务都有自己的栈区和优先级。
任务的状态
- 就绪状态(Ready)
- 任务已准备好运行,但尚未被调度器选中执行
- 可能有多个任务同时处于就绪态
- 形成就绪列表,调度器从中选择最高优先级任务运行
- 运行状态(Running)
- 当前正在处理器上执行的任务
- 单核系统中任何时刻只能有一个任务处于运行态
- 由调度器选择优先级最高的就绪任务进入
- 阻塞状态(Blocked)
- 任务暂时不能执行,等待某个事件或超时
- 有明确的超时时间或等待条件
- 不参与调度器的任务选择
- 挂起状态(Suspended)
- 任务被明确挂起,不参与调度
- 没有超时机制,必须由其他任务或中断显示恢复
- 用于长期停止任务执行
1
2
3
4// 挂起任务示例
vTaskSuspend(xTaskHandle); // 将任务挂起
// ...其他代码
vTaskResume(xTaskHandle); // 将任务从挂起态恢复
freertos任务状态转换

cmsis rtos 接口

任务优先级
可以通过vTaskPrioritySet()设置。
FreeRTOSConfig.h头文件中的configMAX_PRIORITIES可以设置最高优先级的值。0最低,configMAX_PRIORITIES-1最高。
configMAX_PRIORITIES受configUSE_PORT_OPTIMISED_TASK_SELECTION参数值影响。
-
当configUSE_PORT_OPTIMISED_TASK_SELECTION值设为0时,代表通用方式。configMAX_PRIORITIES最大值不受限制。
- 但不建议设置太高,会增加RAM占用,程序最坏运行时间更长。
原因:freertos调度器通过为每个优先级维护独立的任务就绪列表来工作,就绪列表通常存储在一个数组中:List_t pxReadyTasksLists[ configMAX_PRIORITIES ]:
1
2
3
4>优先级0 → 就绪列表0 → [任务A] → [任务B]
>优先级1 → 就绪列表1 → [任务C]
>优先级2 → 就绪列表2 → [任务D] → [任务E] → [任务F]
>优先级N → 就绪列表N → [空]
- 但不建议设置太高,会增加RAM占用,程序最坏运行时间更长。
-
当configUSE_PORT_OPTIMISED_TASK_SELECTION值设为1时,代表架构优化方式。架构优化方式采用了平台相关的汇编代码,比通用方式更快,configMAX_PRIORITIES的值不会影响程序的最坏运行时间。在这种方式下,configMAX_PRIORITIES的最大值不能超过32。因为是平台相关,不是所有的单片机支持这个方式。
任务创建
使用xTaskCreate()函数创建。
1 | BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, |
- pvTaskCode:函数指针,指向执行任务的函数
- pcName:任务描述名称,方便调试,不需要设为NULL
- usStackDepth:栈空间大小,单位是字(word)(Cortex-M通常为4字节一字)
- pvParameters:传递给任务的参数指针,不需要设为NULL
- uxPriority:设置任务优先级,0~configMAX_PRIORITIES-1
- pxCreatedTask:指向任务句柄的指针,通过句柄对任务进行设置,如改变优先级,任务状态,不需要设为NULL
- 函数返回值:成功返回
pdPass(1),失败返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(-1)

CMSIS RTOS api 使用osThreadNew()函数创建任务,其封装了xTaskCreate()函数
调度器
调度器(scheduler)简单来说是一个决定哪个任务应该执行的算法代码。在FreeRTOS中采用了round-robin的调度算法,包含抢占式(preemptive)和合作式(cooperative)两种模式。模式的选择在FreeRTOSConfig.h头文件中由configUSE_PREEMPTION这个参数决定,为1时是抢占式模式,为0时是合作式模式。
- 抢占式
在抢占式模式下,在每次调度器运行时,高优先级的任务会被切换优先执行,当前处于运行状态的低优先级的任务则会立刻进入就绪状态等待运行,如下图所示,高优先级的Task2抢占了Task1。如果几个任务的优先级一样的话,它们就会轮流执行共享CPU资源。

- 合作式
在合作式模式下,高优先级任务不会抢占当前正在运行状态的低优先级任务,直到低优先级任务完成进入阻塞状态(比如调用osDelay()函数)或就绪状态(比如调用osThreadYield()函数)或者被系统置于挂起状态后才会切换任务,如下图所示。

Stm32CubeMX中,USE_PREEMPTION为Eabled时采用抢占式,Disabled为合作式

任务调度
FreeRTOS对任务的调度采用基于时间片(time slicing)的方式。时间片,顾名思义,把一段时间等分成了很多个时间段,在每一个时间段保证优先级最高的任务能执行,同时如果几个任务拥有相等的优先级,则它们会轮流使用每个时间段占用CPU资源。调度器会在每个时间片结束的时候通过周期中断(tick interrupt)执行一次,调度器根据设置的抢占式还是合作式模式选择哪个任务在下一个时间片会运行。
时间片的大小由configTICK_RATE_HZ这个参数设置。如果configTICK_RATE_HZ设置为10HZ,则时间片的大小为100ms。configTICK_RATE_HZ的值由应用需求决定,通常设为100HZ(时间片大小相应为10ms)。
在上图任务调度的演示中,Kernel表示系统内核即调度程序,Task1和Task2是两个优先级相同的任务。t1到t2是一个时间片,t2到t3是另一个时间片。在每一个时间片快结束的时候,调度程序通过周期中断(tick interrupt)被调用并选择在下一个时间片要执行的任务(红色部分代表调度程序Kernel在运行)。此时因为两个任务的优先级相同,调度程序会让两个任务轮流占用时间片进行运行(蓝色部分代表Task1在运行,绿色部分代表Task2在运行)
可以把FreeRTOS当成一个时间离散的系统(时间并不是连续的),时间的最小单位是一个节拍(tick),延时函数 vTaskDelayUntil和vTaskDelay的参数需要的是延时的节拍数,不能直接设置延时时间,因此使用 pdMS_TO_TICKS 函数将时间转换为节拍数。通过pdMS_TO_TICKS这个函数可以把时间转换成节拍数(一个节拍代表一个时间片),并且调用这个函数可以保证即使configTICK_RATE_HZ的值不同时时间是一致的。
- 抢占式时间片调度
内核调度器在每个时间片结束的时候执行一次,选择处于就绪状态的任务中优先级最高的任务置于下一个时间片执行。如果优先级相同的话则交替执行。此时,FreeRTOSConfig.h头文件的设置如下:
configUSE_PREEMPTION(允许抢占) 1
configUSE_TIME_SLICING(采用时间片) 1

- 抢占式无时间片调度
这种调度方式下,因为没有采取时间片,所以调度器的执行开销会比较小。如果两个任务的优先级相同的话,在抢占式时间片调度下,两个任务会交替运行;在抢占式无时间片调度下,当前运行的任务会一直运行,直到它进入阻塞或者挂起状态,另一个相同优先级的任务才会运行。高优先级的任务会抢占低优先任务。此时,FreeRTOSConfig.h头文件的设置如下:
configUSE_PREEMPTION(允许抢占) 1
configUSE_TIME_SLICING(采用时间片) 0

- 合作式调度
空闲任务(Idle Task)
vTaskStartScheduler()函数用来启动调度器。空闲任务是调用器启动后自动创建的一个任务。
FreeRTOS的设计原则是:调度器必须永远有一个可以运行的任务。即使所有用户任务都在等待事件(比如等待一个信号量、一个队列消息或者一个延时到期),系统也必须有事可做。
空闲任务就是为了保证始终至少有一个任务处于就绪(Ready)状态而存在的。
空闲任务特点:
- 优先级为0,最低的优先级确保空闲任务不会抢占用户任务。当用户创建的任务都在阻塞或者挂起状态时,空闲任务才执行。
- 负责清理内核资源,当有任务被删除后,确保空闲任务能运行清理和回收内核资源。
同时,空闲任务可以绑定一个钩子任务(Task Hook),当空闲任务运行的时候钩子任务也会被自动调用。钩子任务里可以添加测量空闲任务运行时间的函数或者把系统放入低功耗模式的函数。空闲任务运行的时间反映出了系统的可用计算资源,可以用于推算CPU的占用率。CPU的占用率过高的话可能会对系统的实时性有影响。
钩子任务的函数原型是vApplicationIdleHook( void );
