第四章 · 同步与任务间通信 本章要回答的三个问题:两个任务要共用一个外设或一段数据,怎么保证不互相踩?信号量、互斥量、消息队列、事件组长得像但行为不同,到底各管什么场景?死锁和优先级反转这两个经典故障,机制层面如何防、现场如何认? 为什么会有这一章 第三章解决了「谁用处理器」,但任务不是孤立的:采样任务产出的数据要交给处理任务,两个任务可能同时要往一根总线上发帧,一个中断要唤醒它对应的处理者。这些协作一旦不加约束,就会踩出三类问题:数据在写了一半时被读走、两个执行流同时改一个变量导致丢失更新、以及等待关系成环导致集体卡死。 同步与通信组件提供的就是这些约束。它们不是几个「加锁函数」,而是一组各有语义的契约:谁等谁、等多久、等不到怎么办、等待期间优先级怎么处理。
本章要回答的三个问题:两个任务要共用一个外设或一段数据,怎么保证不互相踩?信号量、互斥量、消息队列、事件组长得像但行为不同,到底各管什么场景?死锁和优先级反转这两个经典故障,机制层面如何防、现场如何认?
第三章解决了「谁用处理器」,但任务不是孤立的:采样任务产出的数据要交给处理任务,两个任务可能同时要往一根总线上发帧,一个中断要唤醒它对应的处理者。这些协作一旦不加约束,就会踩出三类问题:数据在写了一半时被读走、两个执行流同时改一个变量导致丢失更新、以及等待关系成环导致集体卡死。
同步与通信组件提供的就是这些约束。它们不是几个「加锁函数」,而是一组各有语义的契约:谁等谁、等多久、等不到怎么办、等待期间优先级怎么处理。用错契约的代码往往「大多数时候能跑」,然后在最关键的演示现场出一次难复现的错——这比稳定出错危险得多。本章按「先同步、再通信、后故障」的顺序展开。
| 节号 | 回答的问题 | 关键产出 |
|---|---|---|
| 4.1 同步原语 | 控制访问顺序的原语有哪些,怎么选 | 原语选型矩阵与互斥量优先级继承机制 |
| 4.2 通信机制 | 数据怎么在执行流之间安全移动 | 队列环形缓冲的运作与收发代码范式 |
| 4.3 死锁与优先级反转 | 协作怎么卡死,卡死怎么解 | 两类故障的发生条件、现场特征与解法 |
4.1 管「控制权」的传递,4.2 管「数据」的传递,4.3 收束到两类著名故障——它们分别对应控制权与数据流动被错误安排后的极端形态。
需要 3.1 节的阻塞态概念(等待如何实现)与 2.2 节的队列结构(信号量与队列共享同一份内核结构)。
一条典型数据链把三节串起来:中断收到数据帧,投进队列;高优先级处理任务被唤醒取走;处理中需要改写共享配置,先拿互斥量,而互斥量此刻正被低优先级任务持有——优先级反转的伏笔就埋在这里:
图里每一支箭头都对应本章的一个知识点:投递与唤醒的规则在 4.2,互斥量的继承行为在 4.1,而如果继承机制没有开启,这条链路会在 4.3 变成一个教学事故。
同步与通信的原语就绪后,第七章会把它们延伸到多核场景——核间不再共享内存列表,需要硬件辅助的核间通信原语。而 4.3 节的故障分析方法,会在第九章的实战复盘中反复用到。
同步与通信的代码往往一行就能写完,功夫全在设计决策上。把第四章的决策点收拢成一份自查清单,设计评审时逐条过:每一处共享资源是否都有明确的保护手段与持锁上限时长?每一把锁是否都在全局加锁顺序表里登记?互斥量与信号量的使用有没有混位——保护资源的绝不能是信号量?每个队列的满策略是什么,谁批准的丢弃规则?中断到任务的每一对通知关系,用的都是 FromISR 版本加出口切换吗?所有等待调用都带超时了吗,超时后的退避逻辑是已经写好还是「以后再补」?
这份清单的价值在项目初期:设计阶段每确认一条,联调阶段就少一类深夜故障。它也适合代码评审时逆向使用——看到一把锁就问它的顺序登记,看到一个队列就问它的深度依据,评审的锋利度立刻不同。