FreeRTOS 任务基础

任务状态

  • 运行态(Running)
    • 任务当前正在 CPU 上执行
    • 在单核系统中,同时只有一个任务处于运行态
    • 任务只有在调度器(Scheduler)选择它执行时,才会进入运行态
  • 就绪态(Ready)
    • 任务已经具备执行条件,但由于 CPU 资源被其他更高优先级任务占用,它暂时无法运行
    • 任务处于就绪列表(Ready List),等待 FreeRTOS 调度它运行
    • 当更高优先级任务阻塞或时间片结束,调度器可能会让它进入运行态
  • 阻塞态(Blocked)
    • 任务正在等待某个事件(如 vTaskDelay、信号量、消息队列、事件组等),暂时无法运行
    • 任务在等待事件时会从就绪态转换到 阻塞态,避免占用 CPU 资源
    • 当等待的事件发生后(如 信号量释放、消息到达、延时时间结束),任务会转换回 就绪态
  • 挂起态(Suspended)
    • 任务被显式挂起(使用 vTaskSuspend()),不会被调度执行
    • 任务不会自动恢复,必须调用 vTaskResume()vTaskResumeFromISR() 才能恢复运行
    • 与阻塞态不同,挂起任务不会因为外部事件自动恢复

task状态转换图

任务优先级

FreeRTOS 任务的优先级用一个整数表示,数值越大,优先级越高。默认情况下,FreeRTOS 的最低优先级是 0,最大优先级由 configMAX_PRIORITIES 定义(通常在 FreeRTOSConfig.h 中配置)。

任务调度方式

  • 抢占式调度
  • 时间片调度
  • 协程式调度(已基本弃用)

抢占式调度

即高优先级任务抢占低优先级任务。当高优先级任务进入就绪态,调度器会立刻抢占低优先级任务,并切换到高优先级任务执行。只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。

时间片调度

相同优先级的任务采用时间片轮转。若多个任务拥有相同优先级,FreeRTOS 默认使用时间片轮转调度,每个任务轮流执行一个时间片(依赖于 configUSE_TIME_SLICING),即调度器会在每一次时间片之后切换任务,CPU轮流运行优先级相同的任务。

任务控制块(TCB,Task Control Block)

TCB为一结构体:

1
2
3
4
5
6
7
8
9
typedef struct tskTaskControlBlock {
volatile StackType_t *pxTopOfStack; // 任务栈指针
ListItem_t xStateListItem; // 任务在就绪、阻塞等列表中的位置
StackType_t *pxStack; // 任务栈的起始地址
char pcTaskName[configMAX_TASK_NAME_LEN]; // 任务名称
UBaseType_t uxPriority; // 任务优先级
...
} tskTCB;

任务栈

动态方式创建任务时,系统会自动从系统heap中分配一块内存作为任务的栈空间:

1
2
3
4
5
6
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask)

usStackDepth即为栈大小,以字(32位)为单位(非字节)。

API函数

  • xTaskCreate():动态创建任务
  • xTaskCreateStatic(): 静态创建任务
  • xTaskCreateRestricted(): 动态创建使用 MPU 限制的任务
  • xTaskCreateRestrictedStatic(): 静态创建使用 MPU 限制的任务
  • vTaskDelete(): 删除任务

xTaskCreate()

FreeRTOSConfig.h中需要将configSUPPORT_DYNAMIC_ALLOCATION配置为1

1
2
3
4
5
6
7
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask);
  • pxTaskCode 指向任务函数的指针
  • pcName 任务名,最大长度为 configMAX_TASK_NAME_LEN
  • usStackDepth 任务堆栈大小,单位:字(注意,单位不是字节)
  • pvParameters 传递给任务函数的参数
  • uxPriority 任务优先级,最大值为(configMAX_PRIORITIES-1)
  • pxCreatedTask 任务句柄,任务成功创建后,会返回任务句柄。任务句柄就
    是任务的任务控制块

返回值:

  • pdPASS 任务创建成功
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 内存不足,任务创建失败

xTaskCreateStatic()

FreeRTOSConfig.h中需要将configSUPPORT_STATIC_ALLOCATION配置为1

1
2
3
4
5
6
7
8
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer);

和动态分配基本一致,不同在于:

  • puxStackBuffer 任务栈指针,内存由用户分配提供
  • pxTaskBuffer 任务控制块指针,内存由用户分配提供

返回值:

  • NULL 用户没有提供相应的内存,任务创建失败
  • 其他值 任务句柄,任务创建成功

xTaskCreateRestricted()

用于使用动态的方式创建受 MPU 保护的任务,任务的任务控制块以及任务的栈空
间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配,若使用此函数,需要将宏configSUPPORT_DYNAMIC_ALLOCATION``和宏portUSING_MPU_WRAPPERS 同时配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。

1
2
3
BaseType_t xTaskCreateRestricted(
const TaskParameters_t * const pxTaskDefinition,
TaskHandle_t * pxCreatedTask);
  • pxTaskDefinition 指向任务参数结构体的指针,建结构体中包含任务函数、任
    务名、任务优先级等任务参数
  • pxCreadedTask 任务句柄,任务成功创建后,会返回任务句柄。任务句柄就
    是任务的任务控制块

vTaskDelete()

数用于删除已被创建的任务,被删除的任务将被从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除.

注意:空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。

若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为 1

1
void vTaskDelete(TaskHandle_t xTaskToDelete);
  • xTaskToDelete 待删除任务的任务句柄

注意事项

  1. FreeRTOS 任务函数不允许返回,因此void task1(void *pvParameters)task的具体实现必须包含在while(1)中,否则会触发异常(HardFault 或任务栈溢出)
  2. 任务中的延时不能用裸机时的delay_msdelay_us,要用vTaskDelay()。该函数按照给定的滴答数延迟任务。任务保持阻塞的实际时间取决于滴答频率 。常量portTICK_PERIOD_MS可用于根据滴答频率计算实际时间:
1
2
3
4
5
6
7
8
9
10
11
12
void vTaskFunction( void * pvParameters )
{
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;

for( ;; )
{
/* Simply toggle the LED every 500ms, blocking between each toggle. */
vToggleLED();
vTaskDelay( xDelay );
}
}

该函数通过阻塞的方式来进行延时,因此vTaskDelay()不能很好的控制周期性任务的频率,因为途径代码的路径和其他任务/中断会影响vTaskDelay()被调用的频率,久而久之会影响周期性任务的触发。请参阅 vTaskDelayUntil(),了解设计用于方便 固定频率执行的替代 API 函数。此函数指定调用任务应取消阻塞的绝对时间(而非相对时间)来实现这一点 。


FreeRTOS 任务基础
http://akichen891.github.io/2025/04/02/RTOS任务基础/
作者
Aki
发布于
2025年4月2日
更新于
2025年4月2日
许可协议