FreeRTOS 任务基础
任务状态
- 运行态(Running)
- 任务当前正在 CPU 上执行
- 在单核系统中,同时只有一个任务处于运行态
- 任务只有在调度器(Scheduler)选择它执行时,才会进入运行态
- 就绪态(Ready)
- 任务已经具备执行条件,但由于 CPU 资源被其他更高优先级任务占用,它暂时无法运行
- 任务处于就绪列表(Ready List),等待 FreeRTOS 调度它运行
- 当更高优先级任务阻塞或时间片结束,调度器可能会让它进入运行态
- 阻塞态(Blocked)
- 任务正在等待某个事件(如
vTaskDelay
、信号量、消息队列、事件组等),暂时无法运行 - 任务在等待事件时会从就绪态转换到 阻塞态,避免占用 CPU 资源
- 当等待的事件发生后(如 信号量释放、消息到达、延时时间结束),任务会转换回 就绪态
- 任务正在等待某个事件(如
- 挂起态(Suspended)
- 任务被显式挂起(使用
vTaskSuspend()
),不会被调度执行 - 任务不会自动恢复,必须调用
vTaskResume()
或vTaskResumeFromISR()
才能恢复运行 - 与阻塞态不同,挂起任务不会因为外部事件自动恢复
- 任务被显式挂起(使用
任务优先级
FreeRTOS 任务的优先级用一个整数表示,数值越大,优先级越高。默认情况下,FreeRTOS 的最低优先级是 0,最大优先级由 configMAX_PRIORITIES
定义(通常在 FreeRTOSConfig.h
中配置)。
任务调度方式
- 抢占式调度
- 时间片调度
- 协程式调度(已基本弃用)
抢占式调度
即高优先级任务抢占低优先级任务。当高优先级任务进入就绪态,调度器会立刻抢占低优先级任务,并切换到高优先级任务执行。只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。
时间片调度
相同优先级的任务采用时间片轮转。若多个任务拥有相同优先级,FreeRTOS 默认使用时间片轮转调度,每个任务轮流执行一个时间片(依赖于 configUSE_TIME_SLICING
),即调度器会在每一次时间片之后切换任务,CPU轮流运行优先级相同的任务。
任务控制块(TCB,Task Control Block)
TCB为一结构体:
1 |
|
任务栈
动态方式创建任务时,系统会自动从系统heap中分配一块内存作为任务的栈空间:
1 |
|
usStackDepth
即为栈大小,以字(32位)为单位(非字节)。
API函数
xTaskCreate()
:动态创建任务xTaskCreateStatic()
: 静态创建任务xTaskCreateRestricted()
: 动态创建使用 MPU 限制的任务xTaskCreateRestrictedStatic()
: 静态创建使用 MPU 限制的任务vTaskDelete()
: 删除任务
xTaskCreate()
FreeRTOSConfig.h
中需要将configSUPPORT_DYNAMIC_ALLOCATION
配置为1
1 |
|
pxTaskCode
指向任务函数的指针pcName
任务名,最大长度为 configMAX_TASK_NAME_LENusStackDepth
任务堆栈大小,单位:字(注意,单位不是字节)pvParameters
传递给任务函数的参数uxPriority
任务优先级,最大值为(configMAX_PRIORITIES-1)pxCreatedTask
任务句柄,任务成功创建后,会返回任务句柄。任务句柄就
是任务的任务控制块
返回值:
pdPASS
任务创建成功errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
内存不足,任务创建失败
xTaskCreateStatic()
FreeRTOSConfig.h
中需要将configSUPPORT_STATIC_ALLOCATION
配置为1
1 |
|
和动态分配基本一致,不同在于:
puxStackBuffer
任务栈指针,内存由用户分配提供pxTaskBuffer
任务控制块指针,内存由用户分配提供
返回值:
NULL
用户没有提供相应的内存,任务创建失败其他值
任务句柄,任务创建成功
xTaskCreateRestricted()
用于使用动态的方式创建受 MPU 保护的任务,任务的任务控制块以及任务的栈空
间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配,若使用此函数,需要将宏configSUPPORT_DYNAMIC_ALLOCATION``和宏portUSING_MPU_WRAPPERS
同时配置为 1
。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
1 |
|
pxTaskDefinition
指向任务参数结构体的指针,建结构体中包含任务函数、任
务名、任务优先级等任务参数pxCreadedTask
任务句柄,任务成功创建后,会返回任务句柄。任务句柄就
是任务的任务控制块
vTaskDelete()
数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除.
注意:空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。
若使用此函数,需要在FreeRTOSConfig.h
文件中将宏INCLUDE_vTaskDelete
配置为 1
。
1 |
|
xTaskToDelete
待删除任务的任务句柄
注意事项
- FreeRTOS 任务函数不允许返回,因此
void task1(void *pvParameters)
中task
的具体实现必须包含在while(1)
中,否则会触发异常(HardFault 或任务栈溢出) - 任务中的延时不能用裸机时的
delay_ms
或delay_us
,要用vTaskDelay()
。该函数按照给定的滴答数延迟任务。任务保持阻塞的实际时间取决于滴答频率 。常量portTICK_PERIOD_MS
可用于根据滴答频率计算实际时间:
1 |
|
该函数通过阻塞的方式来进行延时,因此vTaskDelay()
不能很好的控制周期性任务的频率,因为途径代码的路径和其他任务/中断会影响vTaskDelay()
被调用的频率,久而久之会影响周期性任务的触发。请参阅 vTaskDelayUntil()
,了解设计用于方便 固定频率执行的替代 API 函数。此函数指定调用任务应取消阻塞的绝对时间(而非相对时间)来实现这一点 。