Как мне иметь несколько изменяемых ссылок на cortex-m0? - PullRequest
0 голосов
/ 04 августа 2020

Я пишу код для процессора cortex-m0, у которого нет атомики. Я хочу иметь одну ссылку только для чтения значения, а другую - только для записи значения. Я хочу реализовать конструктивные ограничения, которые предписывают только запись и только чтение.

Моя цель состоит в том, чтобы размер сохраненной переменной был доступен для записи с помощью одной инструкции ЦП. Обычно это делается с помощью атомики, которая позволяет читать-изменять-писать, но я не буду выполнять чтение-изменение-запись, только чтение или только запись.

Вариантом использования для этого будет прерывание обработчик для записи значения, считываемого основным кодом, или для основного кода для записи (другого) значения, которое считывается обработчиком прерывания. *

let mut thing = get_new_thing(5 as u8);
let mut reader = thing.get_reader();
let mut writer = thing.get_writer();

reader.read();
writer.write(12 as u8);

///not allowed
reader.write(5 as u8);
writer.read();

Ответы [ 3 ]

1 голос
/ 06 августа 2020

Если я правильно понимаю, вы работаете с 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<_>> во всех трех структурах подойдет.

0 голосов
/ 05 августа 2020

Вот решение, которое я придумал, которое реализует несколько функций из ответа Алекса Ларионова .

Это должно быть ограничено типами данных, которые могут быть записаны за один цикл ЦП. (u8, u16, u32, usize, i8, i16, и c.). Этот код еще не делает этого.

Тип Nothing (поля r, w, _b) предотвращает сбор нескольких читателей / писателей из одного IntSharedData и обеспечивает выполнение время жизни читателей и писателей.

Здесь работает небезопасный код, потому что только один экземпляр записывает значение, используя один тактовый цикл за раз. Читатели всегда будут иметь постоянное представление о том, какое значение они читают.

#[derive(Debug)]
pub struct IntSharedData<T> {
    data: T,
    r: Nothing,
    w: Nothing,
}

pub struct UsableSharedReader<'a, T, OWNER> {
    d: *const T,
    _b: &'a Nothing,
    _owner: OWNER,
}

pub struct UsableSharedWriter<'a, T, OWNER> {
    d: *mut T,
    _b: &'a Nothing,
    _owner: OWNER,
}

#[derive(Debug)]
pub struct Nothing;
pub struct Interrupt;
pub struct Thread;

pub fn new_data<T>(value: T) -> IntSharedData<T> {
    IntSharedData::<T> {
        data: value,
        r: Nothing,
        w: Nothing,
    }
}

impl<'a, T, OWNER> UsableSharedWriter<'a, T, OWNER>
where
    T: Copy,
{
    pub fn write(&mut self, val: T) {
        unsafe {
            *self.d = val;
        }
    }
}

impl<'a, T, OWNER> UsableSharedReader<'a, T, OWNER>
where
    T: Copy,
{
    pub fn read(&self) -> T {
        unsafe { *self.d }
    }
}

impl<T> IntSharedData<T>
where
    T: Copy,
{
    pub fn get_rw_to_interrupt(
        &mut self,
    ) -> (
        UsableSharedReader<T, Interrupt>,
        UsableSharedWriter<T, Thread>,
    ) {
        let r = UsableSharedReader::<T, Interrupt> {
            d: &mut self.data as *const T,
            _owner: Interrupt,
            _b: &mut self.r,
        };
        let wd = &mut self.data as *mut T;
        let w = UsableSharedWriter::<T, Thread> {
            d: wd,
            _owner: Thread,
            _b: &mut self.w,
        };
        return (r, w);
    }

    pub fn get_rw_from_interrupt(
        &mut self,
    ) -> (
        UsableSharedReader<T, Thread>,
        UsableSharedWriter<T, Interrupt>,
    ) {
        let r = UsableSharedReader::<T, Thread> {
            d: &mut self.data,
            _owner: Thread,
            _b: &mut self.r,
        };
        let w = UsableSharedWriter::<T, Interrupt> {
            d: &mut self.data,
            _owner: Interrupt,
            _b: &mut self.w,
        };
        return (r, w);
    }
}

fn main() {
    let mut element = new_data(5 as u8);
    let (r, mut w) = element.get_rw_from_interrupt();
    //let (mut a,mut b) = element.get_rw_from_interrupt();

    let v1 = r.read();

    w.write(5);
    let v2 = r.read();

    println!("v1: {}, v2: {}, t: {:?}", v1, v2, element);
}
0 голосов
/ 04 августа 2020

Следуя предложению Джей-Пи, вы могли бы сделать что-то вроде этого. Игровая площадка

#![allow(unused)]

#[derive(Debug)]
struct Thing(u8);
impl Thing {
    fn new(v: u8) -> Self {
        Self(v)
    }

    fn unwrap_rw(mut self) -> (ThingReader, ThingWriter) {
        (
            ThingReader(&self.0 as *const u8),
            ThingWriter(&mut self.0 as *mut u8),
        )
    }

    fn wrap_rw(r: ThingReader, w: ThingWriter) -> Self {
        Self(r.read())
    }
}

struct ThingReader(*const u8);
impl ThingReader {
    fn read(&self) -> u8 {
        unsafe { *self.0 }
    }
}

struct ThingWriter(*mut u8);
impl ThingWriter {
    fn write(&mut self, v: u8) {
        unsafe { *self.0 = v }
    }
}

fn main() {
    let t = Thing::new(5);
    let (reader, mut writer) = t.unwrap_rw();
    let v1 = reader.read();
    writer.write(10);
    let v2 = reader.read();
    writer.write(v1);
    let t = Thing::wrap_rw(reader, writer);
    println!("v1: {}, v2: {}, t: {:?}", v1, v2, t);
    // v1: 5, v2: 10, t: Thing(5)
}

Я не уверен, как это сделать в безопасном Rust, так как вы явно хотите иметь и читатель, и писатель одновременно (RefCell потребует некоторых накладных расходов во время выполнения) . Обратите внимание, что здесь нет синхронизации, поэтому для многопоточности требуется Mutex.

...