多任务运行机制
一个任务就是实现某一功能的一个函数,任务内部一般有一个死循环结构,任何时候都不允许退出。只有当一个任务功能结束,确实需要它结束自己时,才可以退出死循环,调用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 的基础系统任务,
用于保证系统调度完整性,并提供资源回收与低功耗扩展能力。


发表回复