Real-Time Interrupt-driven Concurrency — страница 17 из 23

{

unsafe {

// начать критическую секцию: увеличить динамический приоритет до `2`

asm!("msr BASEPRI, 192" : : : "memory" : "volatile");


// запустить пользовательский код в критической секции

let r = f(&mut x);


// окончить критическую секцию: восстановить динамический приоритет до статического значения (`1`)

asm!("msr BASEPRI, 0" : : : "memory" : "volatile");


r

}

}

}

}

В данном случае важно указать "memory" в блоке asm!. Это не даст компилятору менять местами операции вокруг него. Это важно, поскольку доступ к переменной x вне критической секции привело бы к гонке данных.

Важно отметить, что сигнатура метода lock препятствет его вложенным вызовам. Это необходимо для безопасности памяти, так как вложенные вызовы привели бы к созданию множественных уникальных ссылок (&mut-) на x, ломая правила заимствования Rust. Смотреть ниже:


#![allow(unused)]

fn main() {

#[interrupt(binds = UART0, priority = 1, resources = [x])]

fn foo(c: foo::Context) {

// resource proxy

let mut res: resources::x = c.resources.x;


res.lock(|x: &mut u64| {

res.lock(|alias: &mut u64| {

//~^ ошибка: `res` уже был заимствован уникально (`&mut-`)

// ..

});

});

}

}

Вложенность

Вложенные вызовы lock на том же ресурсе должны отклоняться компилятором для безопасности памяти, однако вложенные вызовы lock на разных ресурсах - нормальная операция. В этом случае мы хотим убедиться, что вложенные критические секции никогда не приведут к понижению динамического приоритета, так как это плохо, и мы хотим оптимизировать несколько записей в регистр BASEPRI и compiler fences. Чтобы справиться с этим, мы проследим динамический приоритет задачи, с помощью стековой переменной и используем ее, чтобы решить, записывать BASEPRI или нет. На практике, стековая переменная будет соптимизирована компилятором, но все еще будет предоставлять информацию компилятору.

Рассмотрим такую программу:


#![allow(unused)]

fn main() {

#[rtic::app(device = ..)]

mod app {

struct Resources {

#[init(0)]

x: u64,

#[init(0)]

y: u64,

}


#[init]

fn init() {

rtic::pend(Interrupt::UART0);

}


#[interrupt(binds = UART0, priority = 1, resources = [x, y])]

fn foo(c: foo::Context) {

let mut x = c.resources.x;

let mut y = c.resources.y;


y.lock(|y| {

*y += 1;


*x.lock(|x| {

x += 1;

});


*y += 1;

});


// середина


x.lock(|x| {

*x += 1;


y.lock(|y| {

*y += 1;

});


*x += 1;

})

}


#[interrupt(binds = UART1, priority = 2, resources = [x])]

fn bar(c: foo::Context) {

// ..

}


#[interrupt(binds = UART2, priority = 3, resources = [y])]

fn baz(c: foo::Context) {

// ..

}


// ..

}

}

Код, сгенерированный фреймворком, выглядит так:


#![allow(unused)]

fn main() {

// опущено: пользовательский код


pub mod resources {

pub struct x<'a> {

priority: &'a Cell,

}


impl<'a> x<'a> {

pub unsafe fn new(priority: &'a Cell) -> Self {

x { priority }

}


pub unsafe fn priority(&self) ->&Cell {

self.priority

}

}


// repeat for `y`

}


pub mod foo {

pub struct Context {

pub resources: Resources,

// ..

}


pub struct Resources<'a> {

pub x: resources::x<'a>,

pub y: resources::y<'a>,

}

}


mod app {

use cortex_m::register::basepri;


#[no_mangle]

unsafe fn UART1() {

// статический приоритет прерывания (определено пользователем)

const PRIORITY: u8 = 2;


// сделать снимок BASEPRI

let initial = basepri::read();


let priority = Cell::new(PRIORITY);

bar(bar::Context {

resources: bar::Resources::new(&priority),

// ..

});


// вернуть BASEPRI значение из снимка, сделанного ранее

basepri::write(initial); // то же, что и `asm!` блок, виденный ранее

}


// так же для `UART0` / `foo` и `UART2` / `baz`


impl<'a> rtic::Mutex for resources::x<'a> {

type T = u64;


fn lock(&mut self, f: impl FnOnce(&mut u64) -> R) -> R {

unsafe {

// определение максимального приоритет ресурса

const CEILING: u8 = 2;


let current = self.priority().get();

if current < CEILING {

// увеличить динамический приоритет

self.priority().set(CEILING);

basepri::write(logical2hw(CEILING));


let r = f(&mut y);


// восстановить динамический приоритет

basepri::write(logical2hw(current));

self.priority().set(current);


r

} else {

// динамический приоритет достаточно высок

f(&mut y)

}

}

}

}


// повторить для ресурса `y`

}

}

Наконец, компилятор оптимизирует функцию foo во что-то наподобие такого:


#![allow(unused)]