{
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)]