多核嵌入式Rust中,使用static mut实现单向跨核数据共享是否合适?
多核嵌入式Rust中用
static mut实现单写单读跨核数据共享是否可行? 问题背景
在多核嵌入式Rust开发场景中,是否适合使用static mut实现单核心写入、另一核心读取的单向数据共享?以下是基于embassy框架的示例代码:
#![no_std] // … static mut CORE1_STACK: Stack<4096> = Stack::new(); static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); static mut one_way_data_exchange:u8 = 0; #[cortex_m_rt::entry] fn main() -> ! { spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { let executor1 = EXECUTOR1.init(Executor::new()); executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); }); let executor0 = EXECUTOR0.init(Executor::new()); executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); } #[embassy_executor::task] async fn core0_task() { info!("Hello from core 0"); loop { unsafe { one_way_data_exchange = 128; } // sensor value } } #[embassy_executor::task] async fn core1_task() { info!("Hello from core 1"); let sensor_val:u8 = 0; loop { unsafe { sensor_val = one_way_data_exchange; } // continue with rest of program } } }
已知双核心同时写入会产生竞态,但如果仅核心0写入、核心1读取,且业务上允许错过部分输入(不严格要求读写顺序),这种情况下是否还存在问题?
核心分析
直接用static mut实现跨核单写单读是不安全且不可靠的,核心原因如下:
- 内存可见性问题:现代CPU的缓存架构会导致核心0写入的值可能仅停留在本地缓存,核心1无法立即感知到最新值。同时编译器可能因无法识别跨核访问,对读写操作做优化(比如将读取缓存到寄存器,不会每次都从内存取值),导致核心1持续读取旧数据。
- Rust unsafe契约违反:
static mut的unsafe使用要求同一时间只能有一个线程/核心访问,跨核并行读写(哪怕是单写单读)已经违反了这个契约,属于未定义行为,编译器可能生成不符合预期的机器码。 - 潜在的原子性隐患:虽然示例中是
u8(多数ARM架构下是单字节原子操作),但如果后续换成更大的类型(比如非对齐的u32或自定义结构体),跨核读写可能出现部分写入/读取的情况,拿到不完整的数据。即使是u8,也不能依赖硬件原子性来保证长期可靠性,因为没有显式告知编译器和CPU这是跨核共享数据。
适合的替代方案
既然业务允许错过部分输入,推荐用以下更安全的方式:
原子类型(最适合轻量场景)
使用core::sync::atomic模块下的原子类型(比如AtomicU8),它能保证内存可见性和原子性,无需unsafe即可安全跨核读写。示例修改如下:
static ONE_WAY_DATA_EXCHANGE: AtomicU8 = AtomicU8::new(0); #[embassy_executor::task] async fn core0_task() { info!("Hello from core 0"); loop { ONE_WAY_DATA_EXCHANGE.store(128, Ordering::Relaxed); // 业务逻辑延迟 } } #[embassy_executor::task] async fn core1_task() { info!("Hello from core 1"); loop { let sensor_val = ONE_WAY_DATA_EXCHANGE.load(Ordering::Relaxed); // 数据处理逻辑 // 业务逻辑延迟 } }
这里使用Ordering::Relaxed就足够,因为业务不关心严格的读写顺序,只需要保证读取到的是某个时刻的有效值。
单生产者单消费者队列(SPMC)
如果需要保留多个输入值而非直接覆盖,可以使用嵌入式场景下的SPMC队列(比如embassy自带的队列或第三方实现),但如果仅允许覆盖最新值,原子类型的开销更小。
结论
即使是单写单读的跨核场景,也绝对不能用static mut实现数据共享,会引入未定义行为和潜在的稳定性问题。原子类型是这种轻量、允许丢失部分输入的跨核单向共享场景的最优选择。
内容的提问来源于stack exchange,提问作者risingtiger
相关产品推荐
相关产品推荐

