3. 任务管理核心:任务创建与删除、任务优先级、任务状态切换、空闲任务机制
各位同学,咱们今天聊聊RTOS里最核心的东西——任务管理。说白了,任务就是RTOS的灵魂。你想想看,一个裸机程序,就是一个大循环在那跑,但RTOS不一样,它能把一个复杂系统拆成多个独立的小任务,让它们看起来像是“同时”在运行。
我个人习惯把任务管理比作一个公司的运作。每个任务就是一个员工,有各自的职责,有优先级高低,有上班下班(创建删除),也有摸鱼等待(状态切换)。而那个永远在后台转悠的“空闲任务”,就是公司的保洁阿姨——没人干活的时候,她总在打扫卫生。
3.1 任务创建与删除:让任务“生”与“死”
任务创建,是RTOS开发的第一步。我记得刚入行那会儿,第一次用FreeRTOS创建任务,心里还挺激动——终于不用写那个while(1)大循环了!
任务创建的核心API,在FreeRTOS里是xTaskCreate()。它的原型长这样:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 栈深度
void *pvParameters, // 任务参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t *pxCreatedTask // 任务句柄
);
这里我要特别强调一下栈深度。我在项目中遇到过好几次任务莫名其妙崩溃,查了半天,最后发现是栈给得太小了。尤其是任务里用了printf、sprintf这类函数,它们会吃掉大量栈空间。我建议你给任务分配栈时,至少留出30%的余量。
uxTaskGetStackHighWaterMark()一查,发现某个任务的栈只剩不到10个字节。从那以后,我每个任务都会在调试阶段打印栈水位线。
任务删除相对简单,调用vTaskDelete()即可。但要注意:千万不要在中断服务函数里删除任务!这是RTOS的禁忌。另外,任务删除后,它占用的栈空间和TCB(任务控制块)会被释放回堆,但如果你用了动态内存分配,记得在任务退出前自己释放。
3.2 任务优先级:谁先跑,谁后跑?
优先级是RTOS调度器的“指挥棒”。说白了,优先级高的任务,CPU先伺候它。
在FreeRTOS中,优先级数值越小,优先级越低(0是最低)。我一般这样分配:
| 优先级 | 典型任务 | 说明 |
|---|---|---|
| 0 | 空闲任务 | 系统自带,不可删除 |
| 1-2 | 后台任务、日志记录 | 不紧急,可以慢慢跑 |
| 3-4 | 数据采集、通信处理 | 中等优先级,需要及时响应 |
| 5-6 | 控制算法、故障检测 | 高优先级,实时性要求高 |
| 7 | 紧急中断处理 | 最高优先级,慎用 |
这里有个常见的坑:优先级反转。低优先级任务拿着一个资源,高优先级任务等着用,结果中优先级任务插进来把低优先级任务抢走了CPU,高优先级任务反而被“饿死”。嗯,这个问题在电力监控里特别要命——如果故障检测任务因为优先级反转被耽误了,后果不堪设想。
3.3 任务状态切换:任务在干啥?
RTOS里的任务有四种状态:运行态、就绪态、阻塞态、挂起态。你想想看,这就像一个人的四种状态:正在干活、排队等着、被堵在路上、被叫去开会了。
- 运行态(Running):任务正在使用CPU。单核CPU上,同一时刻只有一个任务在运行。
- 就绪态(Ready):任务准备好了,但CPU被别的任务占着,它在等调度器叫它。
- 阻塞态(Blocked):任务在等某个事件(比如延时到期、信号量可用、队列有数据)。这是最常见的状态。
- 挂起态(Suspended):任务被
vTaskSuspend()挂起了,除非有人调用vTaskResume(),否则它永远不会被调度。
状态切换的典型场景是这样的:
void vTaskExample(void *pvParameters)
{
for(;;)
{
// 任务正在运行(运行态)
vTaskDelay(pdMS_TO_TICKS(100)); // 进入阻塞态,等100ms
// 100ms后,任务回到就绪态,等待调度器分配CPU
// 如果它是最高优先级的就绪任务,立刻进入运行态
}
}
我在项目中遇到过一个问题:某个任务调用了vTaskDelay()后,再也没有被调度回来。查了半天,发现是延时时间设置成了0。嗯,vTaskDelay(0)不会让任务阻塞,它只是让出CPU给同优先级的其他任务。如果你想让任务“睡一会儿”,延时至少1个tick。
3.4 空闲任务机制:系统的“保洁阿姨”
空闲任务(Idle Task)是RTOS自动创建的,优先级为0(最低)。它的作用很简单:当没有其他任务可以运行时,CPU就跑去跑空闲任务。
空闲任务到底在干啥?默认情况下,它就是一个死循环,啥也不干。但你可以通过configUSE_IDLE_HOOK这个宏,给空闲任务挂一个钩子函数(Idle Hook),在里面做一些低优先级的后台工作。
我一般会在空闲钩子里做这些事情:
- 统计CPU利用率(计算空闲任务运行时间占比)
- 进入低功耗模式(比如WFI指令)
- 做一些系统健康检查
vTaskDelay()或等待信号量。因为空闲任务一旦阻塞,系统就没有任务可跑了,调度器会直接崩溃。我曾经见过一个新手在空闲钩子里加了延时,结果系统一启动就卡死。
另外,空闲任务还有一个重要职责:清理被删除任务的资源。当你调用vTaskDelete()删除一个任务时,这个任务不会立刻被清理,而是等到空闲任务运行时,才会回收它的TCB和栈空间。所以,如果你频繁创建和删除任务,一定要确保空闲任务有足够的CPU时间来做清理工作。
好了,任务管理的核心内容就这些。说白了,任务创建和删除是“生与死”,优先级是“尊卑有序”,状态切换是“忙闲交替”,空闲任务是“兜底保障”。把这四个点吃透了,RTOS的调度机制你就算入门了。
下一章,咱们聊聊任务间的通信与同步——信号量、队列、事件组这些“传话工具”。到时候你会发现,任务之间怎么“说话”,比任务本身怎么跑还要有意思。