Если я правильно понимаю, вы работаете с Rust без покрытия на однопоточном ЦП M0 и sh для обработки прерываний платформы.
Кажется, ваш сценарий очень специфичен, поскольку две причины:
- ЦП не имеет поддержки Atomics, потому что они не имеют никакого смысла, учитывая, что есть только одно ядро
- У вас все еще есть некоторая форма «потоковой передачи», даже если вы у вас один процессор, учитывая, что ваш код может быть прерван в любом месте для переключения на обработку прерывания. Обработчики прерываний следует рассматривать как отдельные «потоки», и вы не можете отправлять им не отправляемые данные. Фактически, в среде с голым металлом
Send
и Sync
предназначены для этой цели, а не для многоядерных .
Ключевой момент Решение здесь состоит в том, что даже если на ЦП нет межъядерных атомиков, в ядре Rust должны быть атомики, по крайней мере, с поддержкой load
и store
, обеспечивая безопасные изменяемые области памяти через прерывания, проблема, которую в противном случае чрезвычайно сложно правильно решить , потому что компилятор может широко оптимизировать материал, предполагая, что он работает на одном ядре и не может быть прерван. (По этой причине я не совсем уверен, что другие предложенные решения верны, см. , как это оптимизируется .)
Используя эти атомики (и не небезопасно), это выглядит Например, решение вашей проблемы будет следующим:
Примечание: для того, чтобы следовать обычной семантике Rust, вы можете использовать .get()
и .set()
в качестве методов на вашем читателе и писателе (соответствует Cell
). У него также есть дополнительное преимущество, заключающееся в том, что он устраняет двусмысленность относительно того, нужно ли перезаписывать это единственное значение каждый раз, в чем я изначально не был полностью уверен при чтении вопроса. Я использую эти имена в реализации.
#![no_std]
#![no_main]
use panic_halt as _;
use cortex_m_rt::{entry, exception};
mod reader_writer {
//! Simple wrapper to restrict a an atomic to read or write
use core::sync::atomic::{AtomicUsize, Ordering};
pub struct AtomicUsizeWrapper {
inner: AtomicUsize,
}
impl AtomicUsizeWrapper {
pub const fn new(val: usize) -> Self {
Self {
inner: AtomicUsize::new(val),
}
}
pub fn get(&self) -> usize {
self.inner.load(Ordering::SeqCst)
}
pub fn set(&self, val: usize) {
self.inner.store(val, Ordering::SeqCst)
}
pub fn reader(&self) -> AtomicUsizeReader {
AtomicUsizeReader { atomic: self }
}
pub fn writer(&self) -> AtomicUsizeWriter {
AtomicUsizeWriter { atomic: self }
}
}
pub struct AtomicUsizeReader<'a> {
atomic: &'a AtomicUsizeWrapper,
}
impl AtomicUsizeReader<'_> {
pub fn get(&self) -> usize {
self.atomic.get()
}
}
pub struct AtomicUsizeWriter<'a> {
atomic: &'a AtomicUsizeWrapper,
}
impl AtomicUsizeWriter<'_> {
pub fn set(&self, val: usize) {
self.atomic.set(val)
}
}
}
use reader_writer::*;
static SOME_VALUE: AtomicUsizeWrapper = AtomicUsizeWrapper::new(0);
#[entry]
fn main() -> ! {
let reader: AtomicUsizeReader<'static> = SOME_VALUE.reader();
loop {
reader.get();
}
}
#[exception]
fn SysTick() {
let writer: AtomicUsizeWriter<'static> = SOME_VALUE.writer();
writer.set(12);
}
Как видите, это генерирует правильную сборку: https://godbolt.org/z/PsaKv9
Чтобы полностью ответить на заголовок вопроса , если ваш тип не является одним из стандартных типов, для которых доступны атомики (размер больше, чем то, что вы могли бы написать с помощью одной операции), похоже, что решением было бы создать ограничивающую чтение / запись оболочку около bare_metal::Mutex<core::cell::Cell<YourType>>
(bare_metal::Mutex
совпадает с cortex_m::interrupt::Mutex
.)
bare_metal::Mutex
можно использовать следующим образом:
use core::cell::Cell;
use cortex_m::interrupt::{free, Mutex};
static SOME_VALUE: Mutex<Cell<u8>> = Mutex::new(Cell::new(0));
fn main() {
free(|cs| SOME_VALUE.borrow(cs).set(5)); // This is how you store
assert_eq!(5, free(|cs| SOME_VALUE.borrow(cs).get())); // This is how you read
}
(И вы можете использовать ту же дополнительную оболочку чтения / записи, что и выше, для ограничений чтения / записи, просто переопределив get
и set
с использованием блоков free
)
Примечание. В обычном случае , когда вы не обрабатываете прерывания и выполняете работу на одном ядре, подойдет простая следующая оболочка вокруг Cell
:
mod thing {
use std::cell::Cell;
pub struct Thing {
inner: Cell<u8>,
}
impl Thing {
pub fn new(val: u8) -> Self {
Self { inner: Cell::new(val) }
}
pub fn reader(&self) -> ThingReader {
ThingReader { thing: self }
}
pub fn writer(&self) -> ThingWriter {
ThingWriter { thing: self }
}
}
pub struct ThingReader<'a> {
thing: &'a Thing,
}
impl ThingReader<'_> {
pub fn get(&self) -> u8 {
self.thing.inner.get()
}
}
pub struct ThingWriter<'a> {
thing: &'a Thing,
}
impl ThingWriter<'_> {
pub fn set(&self, val: u8) {
self.thing.inner.set(val)
}
}
}
use thing::Thing;
fn main() {
let thing = Thing::new(5 as u8);
let reader = thing.reader();
let writer = thing.writer();
reader.get();
writer.set(12 as u8);
}
И если вам нужно было передать их с помощью 'static
, usi ng a Rc<Cell<_>>
во всех трех структурах подойдет.