3、RTOS核心概念速览:任务、调度器、临界区、同步与通信机制
好,咱们进入正题。这一章我打算带你快速过一遍RTOS的几个核心概念。说白了,就是那些让你从「裸机思维」切换到「RTOS思维」必须跨过的坎。
我记得刚接触RTOS那会儿,最困惑的就是:任务到底是个啥?跟裸机里的中断服务函数有啥区别? 嗯,咱们一个个来拆。
3.1 任务:RTOS里的「独立线程」
在裸机里,你写一个while(1)大循环,所有功能都挤在里面。在RTOS里,我们把每个独立的功能模块拆成一个任务。
任务,本质上就是一个无限循环的函数,加上它自己的栈空间和优先级。每个任务都觉得自己独占了CPU。
// 一个典型的RTOS任务
void vTaskLED(void *pvParameters)
{
while(1)
{
// 任务自己的逻辑
GPIO_Toggle(LED_PIN);
vTaskDelay(500); // 主动让出CPU
}
}
你看,任务里必须有个死循环。如果函数返回了,任务就结束了。我见过不少新手把任务写成普通函数,执行完就没了——那RTOS会直接把这个任务删掉。
- 任务函数:实际执行的代码
- 任务栈:保存局部变量和调用上下文
- 任务控制块(TCB):记录任务状态、优先级等信息
3.2 调度器:谁来决定谁跑?
多个任务都想跑,CPU只有一个。这时候就需要调度器来拍板。
调度器是RTOS的心脏。它决定:下一个该执行哪个任务?
最常见的调度策略是基于优先级的抢占式调度。什么意思呢?
- 高优先级的任务就绪了,立刻抢走CPU
- 同等优先级的任务,轮流执行(时间片轮转)
- 任务主动让出CPU(比如调用vTaskDelay)
我个人习惯把任务优先级分成三层:
| 优先级范围 | 典型用途 | 注意事项 |
|---|---|---|
| 高(3-5) | 实时控制、中断响应 | 执行时间要短,别阻塞 |
| 中(2-3) | 数据处理、协议解析 | 注意优先级反转 |
| 低(1) | 界面刷新、日志输出 | 空闲时才跑 |
3.3 临界区:别让共享数据乱掉
任务之间会共享数据。比如一个全局变量,任务A在写,任务B在读。如果调度器在A写了一半时切到B去读——那B读到的是个半成品,数据就坏了。
临界区就是用来保护共享资源的。在临界区内,调度器不会切换任务。
// 进入临界区
taskENTER_CRITICAL();
// 操作共享数据
shared_counter++;
// 退出临界区
taskEXIT_CRITICAL();
注意:临界区里不能调用阻塞函数(比如vTaskDelay)。为什么?因为临界区关掉了调度器,你阻塞了没人来切换,系统就死在那了。
3.4 同步与通信机制:任务间怎么聊天?
任务之间需要协作。比如:
- 任务A采集到数据,通知任务B去处理
- 多个任务共享一个串口,不能同时写
- 任务C要等某个事件发生才能继续
RTOS提供了几种标准机制:
3.4.1 信号量
信号量就是个计数器。你可以把它想象成停车场空位:
- 二值信号量:0或1,用于「事件发生」的通知
- 计数信号量:0到N,用于「资源可用数量」的管理
- 互斥信号量:带优先级继承的二值信号量,专门防优先级反转
// 二值信号量用法
SemaphoreHandle_t xSemaphore;
// 中断中给出信号量
xSemaphoreGiveFromISR(xSemaphore, NULL);
// 任务中等待信号量
if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE)
{
// 收到信号,开始处理数据
}
3.4.2 消息队列
队列是RTOS里最常用的通信方式。一个任务往里扔数据,另一个任务取出来。天然就是线程安全的。
// 创建队列,每个元素4字节,队列深度10
QueueHandle_t xQueue = xQueueCreate(10, sizeof(uint32_t));
// 发送数据
uint32_t data = 100;
xQueueSend(xQueue, &data, 0);
// 接收数据
uint32_t received;
xQueueReceive(xQueue, &received, portMAX_DELAY);
- 简单通知用信号量
- 传递数据用队列
- 保护共享资源用互斥量
- 多个条件等待用事件组
3.4.3 事件组
有时候一个任务要等多个条件同时满足。比如:传感器数据到了 并且 用户按了按钮 并且 定时器超时了。用事件组可以优雅地处理这种「多条件与/或」的场景。
3.5 避坑指南:新手最容易犯的错
嗯,说了这么多,我总结几个实战中常见的坑:
- 任务栈开太小:栈溢出会导致程序跑飞。我习惯给每个任务多留20%余量,然后用uxTaskGetStackHighWaterMark()检查实际使用量。
- 中断里调用RTOS API不加FromISR后缀:中断上下文和任务上下文不同,用错API会触发断言。
- 忘记处理优先级反转:低优先级任务拿了互斥量,中优先级任务一直跑,高优先级任务等不到资源。用互斥量(带优先级继承)可以解决。
- 临界区嵌套:RTOS一般支持嵌套,但退出次数要对齐。我曾经手抖多写了一个EXIT_CRITICAL,结果调度器提前开了,共享数据被撕成两半。
好了,这一章的核心概念就这些。说白了,RTOS就是把裸机里的「超级循环」拆成多个独立任务,然后用调度器、临界区、同步机制把它们串起来。你想想看,是不是比裸机那种「一个中断打天下」的方式清晰多了?
下一章咱们会深入讲任务状态机——怎么设计任务才能让系统既高效又稳定。到时候我会拿一个实际项目里的例子来拆解。