FreeRTOS学习笔记 · 第四讲:2.1任务状态与优先级


多任务运行机制

一个任务就是实现某一功能的一个函数,任务内部一般有一个死循环结构,任何时候都不允许退出。只有当一个任务功能结束,确实需要它结束自己时,才可以退出死循环,调用vTaskDelete删除自己,或在其它函数中调用该函数删除它。

在单核处理器上,任何时候都只能有一个任务占据 CPU 运行。但 freertos 通过任务调度,可以使多个任务对 CPU 进行分时复用。

时间片

假设有两个相同优先级的任务,task1 先执行。在一个时间片里只能有一个任务占据 CPU 并执行,当时间片结束时进行任务调度。

时间片什么时候结束?

在上一讲有一个设置参数位TICK_RATE_HZ,这就是设置时钟频率,默认 1000hz。映射到硬件上就是系统 systick 定时器中断时,发生任务调度。

当发生任务调度时,系统将 cpu 使用权交给 task2,此时 task1 会先将 cpu 场景,即各个核心寄存器的值压入自己的栈;task2 获得 CPU 使用权时,会用自己保存的数据恢复 cpu 场景,因此可以在上次运行的状态继续运行。通过这种方式,可以使相同优先级的任务均匀分配 cpu 时间。

“均匀分配”并不意味着“平均分配”,根据任务实际需要,每个任务可以占据不相等的时间片长度。

任务优先级不相同使 rtos 会进行基于优先级的抢占式任务调度,这点之后再讲。

任务的状态

就像在操作系统中学到的一样,任务的状态分为就绪 ready、运行 running、阻塞 blocked、挂起 suspended 四个状态。

就绪状态(Ready)

就绪状态是任务被创建后进入的初始状态。

处于就绪态表示:

  • 任务已经具备运行条件
  • 未被阻塞
  • 未被挂起
  • 正在等待调度器分配 CPU

基于抢占式调度方式,当一个任务处于就绪态时,调度结果分为以下三种情况:

  • 当前没有其他任务处于运行状态
    • 就绪任务直接进入运行状态。
  • 就绪任务优先级 ≥ 当前运行任务优先级
    • 发生抢占
    • 当前运行任务被切换出去
    • 就绪任务进入运行状态。
  • 就绪任务优先级 < 当前运行任务优先级
    • 不发生抢占
    • 当前任务继续运行
    • 该任务继续保持就绪状态。

运行状态(Running)

运行状态指任务正在占用 CPU 并执行指令。
在单核系统中,同一时刻只能有一个任务处于运行态。处于运行态的任务拥有 CPU 使用权。

运行任务的基本要求

当任务没有实际工作需要处理时,应主动让出 CPU 使用权。
否则会导致:

  • 低优先级任务无法运行
  • 系统实时性下降
  • 资源利用率降低

运行态任务主动让出 CPU 的两种方式

1)调用 vTaskSuspend()
作用:

  • 任务进入挂起状态(Suspended)
  • 不再参与调度
  • 需要其他任务或中断调用恢复函数后才能重新进入就绪态

2)调用阻塞类函数
例如:

  • 延时函数
  • 等待队列
  • 等待信号量
  • 等待事件组

作用:

  • 任务进入阻塞状态(Blocked)
  • 等待某个条件满足
  • 条件满足后自动回到就绪态

运行任务交出 CPU 之后的调度行为

当运行任务进入挂起态或阻塞态后:

  • 当前 CPU 立即空出
  • 调度器重新从“就绪列表”中选择最高优先级任务
  • 该任务进入运行态

阻塞状态(Blocked)

阻塞状态表示任务暂时让出 CPU 使用权,进入等待某个条件满足的状态。

处于阻塞态的任务:

  • 不参与调度
  • 不占用 CPU
  • 必须等待事件发生或时间到达

本质是因“等待条件”而主动退出运行。

进入阻塞状态的前提

只有处于运行态(Running)的任务,才能调用相关函数进入阻塞态。
就绪态或挂起态任务不能直接进入阻塞态。

进入阻塞状态的两类函数

时间延迟类函数

典型函数:

  • vTaskDelay()
  • vTaskDelayUntil()

作用:

  • 指定延迟时间
  • 在延迟时间内任务处于阻塞态
  • 时间到达后自动回到就绪态

进程间通信(IPC)相关函数

典型场景:

  • 等待信号量(xSemaphoreTake()
  • 等待队列数据
  • 等待事件组位

作用:

  • 当资源未满足时进入阻塞态
  • 当资源可用或事件发生时回到就绪态

属于“基于事件”的等待。

阻塞态的状态转换关系

Running → Blocked
调用延时或等待资源函数。

Blocked → Ready
满足以下任一条件:

  • 延时时间到达
  • 信号量被释放
  • 队列收到数据
  • 事件被置位

挂起状态(Suspended)

挂起状态表示任务被人为暂停执行。
处于挂起态的任务:

  • 不参与调度
  • 不进入就绪队列
  • 不占用 CPU
  • 不会因时间或事件自动恢复

挂起是一种“强制退出调度系统”的状态。

进入挂起状态的方式

任意状态的任务(Running / Ready / Blocked)都可以通过调用vTaskSuspend()

进入挂起状态。

调用该函数后,任务立即从当前状态移除,进入 Suspended 状态。

挂起状态的特征

1)不会自动恢复
与阻塞态不同,挂起态不会因:

  • 延时到期
  • 事件到达
  • 信号量释放

而自动回到就绪态。

2)完全脱离调度器管理
调度器不会考虑挂起任务,即使其优先级很高。

退出挂起状态的方法

必须由其他任务或中断调用:

vTaskResume()或xTaskResumeFromISR()

作用:

  • 使挂起任务进入 Ready 状态
  • 重新参与优先级调度

状态转换关系:

Suspended → Ready

任务的优先级

优先级的数量在MAX_PRIORITIES中定义,默认为 56。最低的优先级是 0,最高的优先级是宏定义-1 。除此之外,CMSIS2 还提供了一些预设的优先级,在cmsis_os2.h中查看。

空闲任务

当调用 osKernelStart() 启动 FreeRTOS 调度器时,系统会自动创建一个空闲任务(Idle Task)。

特点:

  • 自动创建,无需用户手动创建
  • 优先级固定为 0(最低优先级)
  • 当没有其他就绪任务时运行

作用:

  • 保证系统始终有任务可运行
  • 回收已删除任务的内核资源(动态任务)
  • 可用于低功耗或后台处理

与空闲任务相关的主要配置参数

configUSE_TICK_HOOK

作用:
控制是否使用 Tick 钩子函数。

取值:

  • 1:启用 Tick Hook
  • 0:不启用

启用后:

  • 每个系统节拍中断都会调用用户实现的 vApplicationTickHook()
  • 可用于周期性处理

注意:
Tick Hook 在中断上下文中执行,不能调用阻塞函数。

configIDLE_SHOULD_YIELD

控制空闲任务是否会对“同优先级任务”主动让出 CPU。

取值:

  • 1:空闲任务会主动 yield
  • 0:空闲任务不会主动让出

影响:

  • 若存在优先级为 0 的用户任务
  • 设置为 1 时可改善同优先级任务的时间片调度

configUSE_TICKLESS_IDLE

是否启用 Tickless Idle(无节拍空闲模式)。

取值:

  • 1:启用低功耗空闲模式
  • 0:使用正常周期 Tick

启用后:

  • 当系统长时间空闲
  • 会暂停 SysTick
  • MCU 进入低功耗模式
  • 下次事件发生时恢复 Tick

当有事件(中断)使系统提前或按期唤醒时,内核会在恢复调度前对内核节拍计数进行补偿。

空闲任务的特性

调度特性:

  • 只有在没有更高优先级任务处于就绪态时才会运行
  • 不应在空闲任务中执行阻塞操作
  • 不应长时间占用 CPU

行为特性:

  • 空闲任务始终存在
  • 优先级最低
  • 主要用于系统维护和低功耗管理
  • 与 Tick 和调度公平性密切相关

空闲任务是 FreeRTOS 的基础系统任务,
用于保证系统调度完整性,并提供资源回收与低功耗扩展能力。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


目录