1. 任务同步基础:为什么需要同步?竞态条件与临界区概念

好,咱们开始第一讲。

做嵌入式开发,尤其是跑 VxWorks 这种实时系统,你迟早会碰到一个问题:多个任务同时访问同一个资源,系统就乱了

这不是理论上的“可能”,而是实际中“必然”会遇到的坑。我刚开始做 VxWorks 项目时,就因为这个栽过跟头。当时一个数据采集任务和一个显示任务共享一个缓冲区,结果数据经常错乱,显示的画面一跳一跳的。查了两天,最后发现是同步没做好。

所以,咱们得先搞清楚:为什么需要同步?

1.1 竞态条件:你永远不知道谁先跑完

先看一个最简单的场景。

假设有两个任务:TaskA 和 TaskB。它们都要对一个全局变量 counter 做加1操作。

// 伪代码
int counter = 0;

void TaskA() {
    counter = counter + 1;  // 读-改-写
}

void TaskB() {
    counter = counter + 1;  // 读-改-写
}

你想想看,如果两个任务同时执行,会发生什么?

理想情况:counter 从 0 变成 2。

实际情况:可能还是 1。

为什么会这样?因为 counter = counter + 1 不是一条原子指令。它分三步:

  1. 从内存读取 counter 的值(比如 0)
  2. 在 CPU 寄存器里加 1(变成 1)
  3. 把结果写回内存

如果 TaskA 刚读完(步骤1),还没来得及写回(步骤3),TaskB 也来读了。这时候 TaskB 读到的也是 0。两个任务各自加 1,最后写回的都是 1。

这就是典型的竞态条件(Race Condition)

竞态条件:多个任务并发访问共享数据,最终结果取决于任务执行的精确时序。说白了,谁先谁后不确定,结果就不可控。

我在项目中遇到过最离谱的一次,是一个电机控制任务和日志任务共享了一个状态变量。竞态导致电机状态被错误覆盖,电机直接反转。嗯,那次差点把设备搞坏。

1.2 临界区:保护共享资源的“禁地”

既然竞态条件这么危险,那怎么解决?

核心思路就一句话:同一时间,只允许一个任务访问共享资源

这个“只允许一个任务访问”的代码段,就叫临界区(Critical Section)

临界区有三个基本原则:

  • 互斥:一个任务在临界区里,其他任务不能进来
  • 有限等待:不能一直占着不放,得让其他任务有机会
  • 空闲让进:没人用的时候,谁想进谁进

说白了,临界区就像厕所的隔间。一个人进去了,其他人就得排队。你不能进去不出来,也不能没人用的时候锁着门。

个人经验:我习惯把临界区写得尽量短。只保护真正需要保护的代码,比如读写共享变量的那几行。千万别把整个任务函数都包进去,那样性能会崩。

1.3 VxWorks 中的同步机制概览

VxWorks 提供了几种同步工具。咱们先列个表,后面几章会逐个深入。

机制 适用场景 特点
二进制信号量 互斥访问、任务同步 简单高效,只有 0 和 1 两种状态
计数信号量 资源池管理(如缓冲区) 可以记录可用资源数量
互斥锁(Mutex) 互斥访问(带优先级继承) 解决优先级反转问题
读写锁 读多写少的场景 多个读任务可同时访问

你可能会问:信号量和互斥锁有什么区别?

简单说:信号量更轻量,适合通知事件;互斥锁更安全,适合保护共享数据。具体区别,咱们后面章节会详细讲。

1.4 一个真实案例:缓冲区覆盖

最后,分享一个我早期踩过的坑。

当时做一个数据采集系统。一个任务负责从传感器读数据,写到环形缓冲区。另一个任务负责把数据通过网口发出去。

代码大概长这样:

// 伪代码 - 有竞态问题
char buffer[1024];
int write_index = 0;
int read_index = 0;

void ProducerTask() {
    buffer[write_index] = read_sensor();
    write_index = (write_index + 1) % 1024;
}

void ConsumerTask() {
    char data = buffer[read_index];
    read_index = (read_index + 1) % 1024;
    send_data(data);
}

看起来没问题?

但实际运行时,数据经常丢包。原因就是:Producer 在写 buffer 的时候,Consumer 可能正在读同一个位置。或者 Producer 更新了 write_index,但 Consumer 还没读到,导致读到了旧数据。

避坑指南:我曾经以为只要读写索引分开就安全了。但忽略了“读-改-写”不是原子的。后来加了一个二进制信号量保护临界区,问题才解决。

1.5 小结

这一章咱们讲了三个核心概念:

  • 竞态条件:多个任务同时访问共享数据,结果不可预测
  • 临界区:需要互斥保护的代码段
  • 同步机制:VxWorks 提供了信号量、互斥锁等工具

记住一句话:凡是共享的,都要保护。别指望任务调度顺序永远正确,那是不可能的。

下一章,咱们开始实战:二进制信号量的创建、获取与释放。到时候我会手把手带你写代码。