当值将在启动时写入一次然后仅读取时,嵌入式 Rust 中的 Mutex 是否有轻量级替代方案?

Is there a lightweight alternative for a Mutex in embedded Rust when a value will be written once at startup and then only read?

根据 Rust Embedded Book about concurrency,在上下文之间共享一些数据的更好方法之一是使用带有引用单元的互斥体。我了解它们的工作原理以及为什么这是必要的。但是有一种情况是间接费用似乎很多。

cortex_m crate 的互斥量是这样工作的:

cortex_m::interrupt::free(|cs| {
    let my_value = my_mutex.borrow(cs).borrow();
    // Do something with the value
});

互斥锁在授予访问权限之前需要 cs (CriticalSection) 令牌。在关键部分,不会发生中断,因此我们知道我们是唯一可以更改和读取值的人。这很好用。

然而,我现在所处的场景是将变量写入一次以进行初始化(在运行时),然后始终将其视为只读值。在我的例子中,它是 MCU 的时钟速度。这不能是编译时常量。一个为什么从深度睡眠中醒来的例子:根据硬件的状态,可能会选择使用较慢的时钟速度来节省一些能量。所以在启动时(或者更确切地说是所有 RAM 都消失的唤醒)每次都可以选择不同的时钟速度。

如果我只是想读取值,那么完成整个临界区设置似乎很浪费。如果这个变量可以再次改变,那么,是的,这是必要的。但这里不是这种情况。它只会被读取。

有没有更好的方法来读取共享变量,开销更少且不使用不安全的 Rust?

在一些评论的帮助下,我想到了这个:

use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};

/// A cell that can be written to once. After that, the cell is readonly and will panic if written to again.
/// Getting the value will panic if it has not already been set. Try 'try_get(_ref)' to see if it has already been set.
///
/// The cell can be used in embedded environments where a variable is initialized once, but later only written to.
/// This can be used in interrupts as well as it implements Sync.
///
/// Usage:
/// ```rust
/// static MY_VAR: DynamicReadOnlyCell<u32> = DynamicReadOnlyCell::new();
///
/// fn main() {
///     initialize();
///     calculate();
/// }
///
/// fn initialize() {
///     // ...
///     MY_VAR.set(42);
///     // ...
/// }
///
/// fn calculate() {
///     let my_var = MY_VAR.get(); // Will be 42
///     // ...
/// }
/// ```
pub struct DynamicReadOnlyCell<T: Sized> {
    data: UnsafeCell<Option<T>>,
    is_populated: AtomicBool,
}

impl<T: Sized> DynamicReadOnlyCell<T> {
    /// Creates a new unpopulated cell
    pub const fn new() -> Self {
        DynamicReadOnlyCell {
            data: UnsafeCell::new(None),
            is_populated: AtomicBool::new(false),
        }
    }
    /// Creates a new cell that is already populated
    pub const fn from(data: T) -> Self {
        DynamicReadOnlyCell {
            data: UnsafeCell::new(Some(data)),
            is_populated: AtomicBool::new(true),
        }
    }

    /// Populates the cell with data.
    /// Panics if the cell is already populated.
    pub fn set(&self, data: T) {
        cortex_m::interrupt::free(|_| {
            if self.is_populated.load(Ordering::Acquire) {
                panic!("Trying to set when the cell is already populated");
            }
            unsafe {
                *self.data.get() = Some(data);
            }

            self.is_populated.store(true, Ordering::Release);
        });
    }

    /// Gets a reference to the data from the cell.
    /// Panics if the cell is not yet populated.
    #[inline(always)]
    pub fn get_ref(&self) -> &T {
        if let Some(data) = self.try_get_ref() {
            data
        } else {
            panic!("Trying to get when the cell hasn't been populated yet");
        }
    }

    /// Gets a reference to the data from the cell.
    /// Returns Some(T) if the cell is populated.
    /// Returns None if the cell is not populated.
    #[inline(always)]
    pub fn try_get_ref(&self) -> Option<&T> {
        if !self.is_populated.load(Ordering::Acquire) {
            None
        } else {
            Some(unsafe { self.data.get().as_ref().unwrap().as_ref().unwrap() })
        }
    }
}

impl<T: Sized + Copy> DynamicReadOnlyCell<T> {
    /// Gets a copy of the data from the cell.
    /// Panics if the cell is not yet populated.
    #[inline(always)]
    pub fn get(&self) -> T {
        *self.get_ref()
    }

    /// Gets a copy of the data from the cell.
    /// Returns Some(T) if the cell is populated.
    /// Returns None if the cell is not populated.
    #[inline(always)]
    pub fn try_get(&self) -> Option<T> {
        self.try_get_ref().cloned()
    }
}

unsafe impl<T: Sized> Sync for DynamicReadOnlyCell<T> {}

由于集合中的原子检查和临界区,我认为这是安全的。如果您发现任何错误或狡猾的地方,请告诉我。