1、RTOS与互斥量概述:什么是实时操作系统、为什么需要互斥量、FreeRTOS简介
1.1 实时操作系统到底是什么?
先聊聊RTOS。很多人一听到“实时操作系统”,就觉得是跑得特别快的系统。其实不完全是。
实时操作系统的核心,是“确定性”。说白了,就是你能预测一个任务什么时候能做完。我刚开始做嵌入式那会儿,用裸机写程序,一个按键扫描循环里插了个延时,结果另一个紧急任务被堵住了。那时候我就意识到,光靠主循环轮询,根本没法保证响应时间。
RTOS把程序拆成多个独立任务。每个任务有自己的栈空间和优先级。调度器来决定谁先跑、谁后跑。这样,高优先级的紧急任务就能及时得到CPU。
举个例子:
// 裸机写法:所有事情挤在一个循环里
while(1) {
read_sensor(); // 可能卡住
process_data(); // 耗时不确定
send_uart(); // 等待发送完成
// 紧急报警?等着吧!
}
// RTOS写法:各干各的
void task_sensor(void *pvParameters) {
while(1) {
read_sensor();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void task_alarm(void *pvParameters) {
while(1) {
if(check_danger()) {
trigger_alarm(); // 高优先级,随时响应
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
你看,RTOS让代码结构清晰多了。每个任务只管自己的事,调度器帮你协调。
1.2 为什么需要互斥量?
任务多了,问题也来了。多个任务同时访问同一个资源怎么办?
比如一个全局变量,任务A在写,任务B在读。如果任务A写到一半被切换走了,任务B读到的就是半成品数据。这就是典型的“竞态条件”。
我在项目中遇到过这样的坑:两个任务共享一个串口缓冲区。任务A往里面填数据,任务B往外发。结果有一次任务A刚更新了长度字段,还没来得及更新数据内容,就被任务B抢占了。任务B发出去的数据,长度对不上内容,直接把设备搞死机了。
互斥量就是用来解决这个问题的。它像一个“通行证”,谁拿到谁才能访问共享资源。其他人必须等着。
核心概念:互斥量(Mutex)是一种特殊的信号量,具有优先级继承机制,专门用于解决资源互斥访问问题。
为什么不用普通的二值信号量?嗯,这里要注意:互斥量有优先级继承,能防止“优先级反转”。普通信号量没有这个能力。后面章节我会详细讲这个坑。
1.3 FreeRTOS 简介
FreeRTOS 是目前最流行的开源 RTOS 之一。我入行那会儿,它还是个小型内核,现在已经被亚马逊收购,功能越来越完善了。
它有几个特点让我一直用到现在:
- 轻量级:内核最小可以裁剪到 4KB 左右,适合资源受限的 MCU
- 免费:商业使用也不需要付费,这点对初创公司很友好
- 移植性好:支持 ARM、RISC-V、AVR、MSP430 等几十种架构
- 文档齐全:官方手册写得清楚,社区也很活跃
FreeRTOS 的核心组件包括:
| 组件 | 作用 | 我常用的场景 |
|---|---|---|
| 任务管理 | 创建、删除、挂起、恢复任务 | 每个传感器一个独立任务 |
| 队列 | 任务间传递数据 | UART 接收数据发给处理任务 |
| 信号量 | 同步和互斥 | 二值信号量做事件通知 |
| 互斥量 | 保护共享资源 | 保护全局变量、外设寄存器 |
| 软件定时器 | 定时执行回调函数 | 周期性采集数据 |
| 事件组 | 多事件同步 | 等待多个条件同时满足 |
我个人习惯,拿到一个新项目,先画任务图:哪些任务、优先级多少、用什么方式通信。画清楚了再动手写代码。这样能避免后期改得头大。
小技巧:刚开始用 FreeRTOS 时,别急着上复杂功能。先跑通两个任务,一个发一个收,把调度机制搞明白。我曾经一上来就搞了七八个任务,结果调试时根本分不清是哪个任务出了问题。
1.4 互斥量在 FreeRTOS 中的角色
FreeRTOS 把互斥量当作一种特殊的信号量来实现。创建互斥量的 API 是:
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
使用的时候,访问共享资源前“拿锁”,用完后“放锁”:
// 任务A
void taskA(void *pvParameters) {
while(1) {
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 安全访问共享资源
shared_counter++;
// 别忘了释放
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
你想想看,如果忘了释放互斥量,会发生什么?其他所有想访问这个资源的任务,都会永远卡在 xSemaphoreTake 那里。这就是死锁的一种。
警告:互斥量必须在同一个任务中“拿”和“放”。不能在任务A中拿锁,在任务B中放锁。FreeRTOS 会检测这种非法操作并返回错误。我曾经调试一个 bug 调了两天,最后发现是中断服务函数里放了锁——中断上下文不能调用 xSemaphoreGive!
好了,这一章我们搞清楚了:RTOS 是什么、为什么需要互斥量、FreeRTOS 的基本情况。下一章,我会带大家深入互斥量的内部实现,看看 FreeRTOS 到底是怎么保证“一次只有一个任务访问资源”的。
说实话,互斥量这东西,看着简单,用起来坑不少。但只要你理解了它的原理,后面那些死锁、优先级反转的问题,都能迎刃而解。