RTOS 下多任务共享 SPI Flash 的互斥方案:从关中断到 Mutex 的取舍
👁 2 阅读 · 2026-08-27 · 嵌入式
在 RTOS 环境中,多个任务并发访问 SPI Flash 是常见需求,但若不加保护,会导致数据错乱、擦写失败甚至系统崩溃。本文深入剖析从关中断、调度锁到互斥量(Mutex)的多种互斥方案,结合原理、代码示例与性能对比,帮助开发者根据实时性、优先级反转等场景做出合理取舍,确保 Flash 操作的原子性与可靠性。
# 引言
在嵌入式系统中,SPI Flash 常用于存储配置参数、日志或固件升级数据。当引入 RTOS 后,多个任务可能同时发起读写请求,若缺乏互斥保护,轻则数据覆盖,重则破坏 Flash 内部状态机。本文将从底层机制出发,探讨几种互斥方案的适用场景与实现细节。
# 为什么需要互斥?
SPI Flash 操作通常包含多个步骤:发送命令、地址、数据,等待忙状态。这些步骤必须作为一个整体执行,不可被其他任务打断。例如,一个任务正在执行页编程(Page Program),另一个任务突然发起擦除操作,会导致总线冲突或数据损坏。因此,互斥的核心是保证 Flash 操作的原子性。
# 方案一:关中断(Critical Section)
## 原理
关中断是 RTOS 中最简单的互斥手段。通过屏蔽系统中断,阻止任务调度发生,从而保证当前任务独占 CPU 和 Flash。
## 实现示例
```c
// 使用 CMSIS-RTOS v2 接口
void flash_write_protected(uint32_t addr, uint8_t *data, uint32_t len) {
uint32_t primask = __get_PRIMASK();
__disable_irq(); // 关中断
// 执行 Flash 写操作
spi_flash_write(addr, data, len);
__set_PRIMASK(primask); // 恢复中断
}
```
## 优缺点
- 优点:实现简单,无死锁风险,适用于极短操作。
- 缺点:关中断时间过长会破坏系统实时性,若 Flash 操作耗时(如擦除需数毫秒),则不可接受。此外,在多核系统中,关中断只能屏蔽当前核,无法全局互斥。
# 方案二:调度锁(Scheduler Lock)
## 原理
调度锁通过禁止任务切换,但允许中断响应。这样,中断服务程序(ISR)可以运行,但其他任务无法抢占当前任务。
## 实现示例
```c
void flash_write_sched_lock(uint32_t addr, uint8_t *data, uint32_t len) {
vTaskSuspendAll(); // 挂起调度器
// 执行 Flash 操作
spi_flash_write(addr, data, len);
xTaskResumeAll(); // 恢复调度器
}
```
## 优缺点
- 优点:比关中断更温和,允许中断响应,适合中等长度操作。
- 缺点:若操作时间过长,其他任务会被饿死;且无法防止中断服务程序(ISR)中访问 Flash(若 ISR 也操作 Flash,则仍需关中断)。
# 方案三:互斥量(Mutex)
## 原理
Mutex 是 RTOS 提供的标准同步机制,支持优先级继承,可有效避免优先级反转问题。任务在访问 Flash 前获取 Mutex,访问后释放。
## 配置步骤
1. 创建 Mutex 句柄(全局变量)。
2. 在任务中调用 `osMutexAcquire` 获取锁。
3. 执行 Flash 操作。
4. 调用 `osMutexRelease` 释放锁。
## 代码示例(CMSIS-RTOS v2)
```c
osMutexId_t flash_mutex;
void flash_init_mutex(void) {
flash_mutex = osMutexNew(NULL);
}
void flash_write_mutex(uint32_t addr, uint8_t *data, uint32_t len) {
osMutexAcquire(flash_mutex, osWaitForever);
// 执行 Flash 操作
spi_flash_write(addr, data, len);
osMutexRelease(flash_mutex);
}
```
## 优缺点
- 优点:阻塞等待,不会浪费 CPU;支持优先级继承,防止低优先级任务长时间占用;适用于长操作。
- 缺点:可能引入死锁(若获取顺序不当);在中断服务程序中无法使用(需用信号量代替)。
# 方案对比与取舍
| 方案 | 实时性影响 | 适用场景 | 风险 |
|------|------------|----------|------|
| 关中断 | 高(屏蔽所有中断) | 极短操作(<10us) | 中断延迟增大 |
| 调度锁 | 中(仅禁止任务切换) | 中等操作(<1ms) | 任务饿死 |
| Mutex | 低(阻塞等待) | 长操作(>1ms) | 死锁、优先级反转(有继承则缓解) |
# 进阶:混合策略
实际项目中,常组合使用。例如,对 Flash 的写操作分为“命令发送”和“等待完成”两个阶段。命令发送阶段短,可用关中断或调度锁;等待完成阶段长,可释放锁,让其他任务运行。但需注意,等待期间 Flash 状态可能被其他任务改变,因此需设计状态机或使用硬件信号。
# 注意事项
- 在 ISR 中不能使用 Mutex,应使用信号量或直接关中断。
- 获取 Mutex 后,务必在操作完成后释放,建议使用 `osMutexAcquire` 的返回值检查是否成功。
- 多任务访问 Flash 时,建议统一封装驱动接口,内部实现互斥,避免业务层重复加锁。
- 若 Flash 支持双片选或独立总线,可考虑分区域访问,减少锁竞争。
# 总结
选择互斥方案需权衡实时性、操作时长和系统复杂度。关中断适合极短操作,调度锁适合中等操作,而 Mutex 是大多数场景的首选,尤其当操作耗时较长时。理解每种方案的原理和限制,才能设计出健壮的嵌入式系统。