1. 课程导入

各位同学,咱们今天聊聊嵌入式开发里一个绕不开的话题——任务同步。

你想想看,一个单片机系统里跑着好几个任务:一个在采集传感器数据,一个在刷新屏幕,还有一个在通过串口往外发数据。它们各自跑各自的,看起来挺和谐。但问题来了——如果两个任务同时操作同一个全局变量,或者一个任务正在读数据,另一个任务却把它改了,会发生什么?

嗯,这就是典型的“竞态条件”。数据乱了,系统崩了,你查半天还找不到原因。

我个人习惯,在写驱动之前,先把同步机制想清楚。否则后面调试起来,那叫一个痛苦。

1.1 什么是任务同步?

任务同步,说白了就是让多个任务在访问共享资源时,能按规矩来,别打架。

共享资源可以是:

  • 全局变量——比如一个计数器的值
  • 硬件寄存器——比如UART的数据寄存器
  • 缓冲区——比如DMA传输的内存区域
  • 外设状态——比如SPI总线的忙闲标志

没有同步机制,就像一群人抢一个麦克风,谁嗓门大谁用,最后谁也听不清。

核心要点:任务同步的本质是“协调访问顺序”,不是“禁止并发”。

1.2 驱动开发中的典型同步场景

我在项目中遇到过不少因为同步没做好导致的bug。这里列几个最常见的场景,你看看是不是也眼熟。

场景 问题描述 后果
多任务写同一个全局变量 任务A写了一半,任务B也来写 数据被覆盖,逻辑错误
中断与任务共享数据 中断服务程序修改了数据,任务正在读 读到半新半旧的数据
生产者-消费者模型 生产者放数据,消费者取数据 缓冲区溢出或空读
外设驱动状态机 多个任务同时操作同一个外设 外设状态混乱,驱动崩溃

举个例子。我曾经调试一个I2C驱动,两个任务同时去读写同一个从设备。结果呢?一个任务刚发了地址,另一个任务就把数据线拉低了。整个总线卡死,系统挂掉。查了两天才发现是同步没做好。

避坑指南:我曾经以为加个全局标志位就能解决问题,结果发现标志位本身也是共享资源。多个任务同时读它、写它,照样出问题。同步机制的选择,不是拍脑袋决定的。

1.3 同步机制的两种基本思路

嵌入式系统里,同步机制大致分两类:

  • 互斥型:同一时间只允许一个任务访问资源。典型代表是互斥锁(Mutex)和信号量(Binary Semaphore)。
  • 条件型:任务等待某个条件满足后再继续执行。典型代表是条件变量(Condition Variable)和事件标志组。

你可能会问:那信号量能不能当条件变量用?

嗯,这个问题问得好。很多初学者会把它们混着用,结果代码越写越别扭。后面几章我会专门对比这两种机制,告诉你什么时候该用哪个。

1.4 同步机制的选择原则

我个人习惯,选同步机制时看三个维度:

  1. 资源类型:是数据资源还是状态资源?
  2. 访问模式:是独占访问还是等待条件?
  3. 实时性要求:任务能不能等?能等多久?

举个例子:

  • 如果只是保护一个全局变量不被同时写,用互斥锁就够了。
  • 如果任务需要等某个事件发生(比如数据到达),条件变量更合适。
  • 如果既要保护资源又要传递信号,信号量可以一肩挑。

小技巧:刚开始做驱动开发时,我习惯先用互斥锁解决所有同步问题。后来发现,锁用多了容易死锁。现在我的原则是:能用信号量解决的问题,尽量不用锁;能用条件变量解决的问题,尽量不用轮询。

1.5 同步机制的知识体系

下面这张图,帮你理清同步机制的整体脉络。

嵌入式同步机制知识体系 嵌入式同步机制知识体系 任务同步机制 互斥型 条件型 互斥锁 二值信号量 条件变量 事件标志 保护共享数据 防止竞态条件 等待事件发生 生产者-消费者 驱动开发中按需选择 核心原则:互斥型解决“谁能用”,条件型解决“什么时候能用” 两者不是替代关系,而是互补关系

从这张图可以看出,同步机制不是孤立的知识点。它跟任务调度、资源管理、中断处理都紧密相关。你理解得越深,写驱动时就越有底气。

1.6 本章小结

这一章我们聊了:

  • 任务同步为什么重要——防止竞态条件,保证数据一致性
  • 驱动开发中的典型同步场景——多任务、中断、生产者-消费者
  • 同步机制的两种思路——互斥型和条件型
  • 选择同步机制的基本原则——看资源类型、访问模式、实时性要求