在多核嵌入式Rust中,使用静态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
        }
    }
}

如果我从两个内核写入静态变量,显然会产生竞争情况.但是,如果我只从一个核心写入,并且只从另一个核心读取,这会解决竞争问题吗?或者,即使只有一个内核在写入,两个内核并行访问它仍然有问题吗?

在这种情况下,读->写或写->读的顺序并不重要.一个内核只是创建IO输入流,而另一个内核只要准备好再次处理循环,就会进入该流,即使它错过了一些间歇性输入.

推荐答案

不,这仍然是一个问题.

  • 写入并不总是保证是原子的.例如,在32位系统上,u64需要多个CPU周期才能写入-因此读取端只能看到更新的值的一半.
  • 这超过了soundness,因为编译器无法再证明您的代码没有未定义的行为

的确,像这样访问非常简单的基元类型是安全的.不过,您并不需要static mut--语言/核心库中内置了一些机制,因此您不必求助于static mut.在这种情况下,重要的是atomic.

它提供了一种叫做interior mutability的东西.这意味着您的值可以是static而不是mut,并且可以正常共享,并且类型本身提供了可变性.

让我来演示一下.由于我现在没有可用的微控制器,我重写了您的示例以实现正常执行:

use core::time::Duration;

static mut ONE_WAY_DATA_EXCHANGE: u8 = 0;

fn main() {
    std::thread::scope(|s| {
        s.spawn(thread0);
        s.spawn(thread1);
    });
}

fn thread0() {
    std::thread::sleep(Duration::from_millis(500));
    unsafe { ONE_WAY_DATA_EXCHANGE = 42 };
}

fn thread1() {
    for _ in 0..4 {
        std::thread::sleep(Duration::from_millis(200));
        let value = unsafe { ONE_WAY_DATA_EXCHANGE };
        println!("{}", value);
    }
}
0
0
42
42

以下是在使用atomic的情况下实现的效果:

use core::{
    sync::atomic::{AtomicU8, Ordering},
    time::Duration,
};

static ONE_WAY_DATA_EXCHANGE: AtomicU8 = AtomicU8::new(0);

fn main() {
    std::thread::scope(|s| {
        s.spawn(thread0);
        s.spawn(thread1);
    });
}

fn thread0() {
    std::thread::sleep(Duration::from_millis(500));
    ONE_WAY_DATA_EXCHANGE.store(42, Ordering::Release);
}

fn thread1() {
    for _ in 0..4 {
        std::thread::sleep(Duration::from_millis(200));
        let value = ONE_WAY_DATA_EXCHANGE.load(Ordering::Acquire);
        println!("{}", value);
    }
}
0
0
42
42

请注意,代码不包含unsafe;这完全有效,编译器可以理解,并且(几乎)没有运行时开销.


要演示这实际产生的开销有多小,请执行以下操作:

#![no_std]

use core::sync::atomic::{AtomicU8, Ordering};

static SHARED_VALUE_ATOMIC: AtomicU8 = AtomicU8::new(0);

pub fn write_static_atomic(val: u8){
    SHARED_VALUE_ATOMIC.store(val, Ordering::SeqCst)
}

pub fn read_static_atomic() -> u8 {
    SHARED_VALUE_ATOMIC.load(Ordering::SeqCst)
}

static mut SHARED_VALUE_STATICMUT: u8 = 0;

pub fn write_static_staticmut(val: u8){
    unsafe {
        SHARED_VALUE_STATICMUT = val;
    }
}

pub fn read_static_staticmut() -> u8 {
    unsafe {
        SHARED_VALUE_STATICMUT
    }
}

代码使用标志-C opt-level=3 -C linker-plugin-lto --target=thumbv6m-none-eabi编译为the following:

example::write_static_atomic:
        dmb     sy
        ldr     r1, .LCPI0_0
        strb    r0, [r1]
        dmb     sy
        bx      lr
.LCPI0_0:
        .long   example::SHARED_VALUE_ATOMIC.0

example::read_static_atomic:
        ldr     r0, .LCPI1_0
        ldrb    r0, [r0]
        dmb     sy
        bx      lr
.LCPI1_0:
        .long   example::SHARED_VALUE_ATOMIC.0

example::write_static_staticmut:
        ldr     r1, .LCPI2_0
        strb    r0, [r1]
        bx      lr
.LCPI2_0:
        .long   example::SHARED_VALUE_STATICMUT.0

example::read_static_staticmut:
        ldr     r0, .LCPI3_0
        ldrb    r0, [r0]
        bx      lr
.LCPI3_0:
        .long   example::SHARED_VALUE_STATICMUT.0

example::SHARED_VALUE_ATOMIC.0:
        .byte   0

example::SHARED_VALUE_STATICMUT.0:
        .byte   0

thumbv6m-none-eabi上的AtomicU8英寸似乎几乎没有开销.唯一的变化是dmb sy,它是防止争用条件的内存屏障;使用Ordering::Relaxed(如果您的问题允许的话)应该会消除这些障碍,导致实际零开销.其他架构也应该有类似的行为.

Rust相关问答推荐

Box::new()会从一个堆栈复制到另一个堆吗?

在Rust中宏的表达式中提取对象

支持TLS的模拟HTTP服务器

为什么`AlternateScreen`在读取输入键时需要按Enter键?

程序在频道RX上挂起

无法将 rust 蚀向量附加到另一个向量

使用关联类型重写时特征的实现冲突

是否可以使用Serde/Rust全局处理无效的JSON值?

在 Rust 中用问号传播错误时对类型转换的困惑?

如何将一个矩阵的列分配给另一个矩阵,纳尔代数?

仅发布工作区的二进制 crate

Rust与_有何区别?

没有分号的返回表达式的性能是否比使用返回更好?在Rust ?

我可以在不调用 .clone() 的情况下在类型转换期间重用 struct 字段吗?

为什么-x试图解析为文字并在声明性宏中失败?

如何在没有 `make_contiguous()` 的情况下对 VecDeque 进行排序或反转?

如果我立即等待,为什么 `tokio::spawn` 需要一个 `'static` 生命周期?

HashMap entry() 方法使borrow 的时间比预期的长

如何重写这个通用参数?

为什么当borrow 变量发生变化时,borrow 变量不会改变?