Выбрать главу

// средина

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

unsafe { basepri::write(192) }

x += 1;

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

unsafe { basepri::write(160) }

y += 1;

// уменьшить (восстановить) динамический приоритет до `2`

unsafe { basepri::write(192) }

// ПРИМЕЧАНИЕ: было вы правильно объединить эту операцию над `x` с предыдущей, но

// compiler fences грубые и предотвращают оптимизацию

x += 1;

// уменьшить (восстановить) динамический приоритет до `1`

unsafe { basepri::write(224) }

// ПРИМЕЧАНИЕ: BASEPRI содержит значение `224` в этот момент

// обработчик UART0 восстановит значение `0` перед завершением

}

}

Инвариант, который фреймворк RTIC должен сохранять в том, что значение BASEPRI в начале обработчика прерывания должно быть таким же, как и при выходе из него. BASEPRI может изменяться в процессе выполнения обработчика прерывания, но но выполнения обработчика прерывания в начале и конце не должно вызвать наблюдаемого изменения BASEPRI.

Этот инвариант нужен, чтобы избежать уеличения динамического приоритета до значений, при которых обработчик не сможет быть вытеснен. Лучше всего это видно на следующем примере:

#![allow(unused)]

fn main() {

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

mod app {

struct Resources {

#[init(0)]

x: u64,

}

#[init]

fn init() {

// `foo` запустится сразу после завершения `init`

rtic::pend(Interrupt::UART0);

}

#[task(binds = UART0, priority = 1)]

fn foo() {

// BASEPRI равен `0` в этот момент; динамический приоритет равен `1`

// `bar` вытеснит `foo` в этот момент

rtic::pend(Interrupt::UART1);

// BASEPRI равен `192` в этот момент (из-за бага); динамический приоритет равен `2`

// эта функция возвращается в `idle`

}

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

fn bar() {

// BASEPRI равен `0` (динамический приоритет = 2)

x.lock(|x| {

// BASEPRI увеличен до `160` (динамический приоритет = 3)

// ..

});

// BASEPRI восстановлен до `192` (динамический приоритет = 2)

}

#[idle]

fn idle() -> ! {

// BASEPRI равен `192` (из-за бага); динамический приоритет = 2

// это не оказывает эффекта, из-за значени BASEPRI

// задача `foo` не будет выполнена снова никогда

rtic::pend(Interrupt::UART0);

loop {

// ..

}

}

#[task(binds = UART2, priority = 3, resources = [x])]

fn baz() {

// ..

}

}

}

ВАЖНО: давайте например мы забудем восстановить BASEPRI в UART1 -- из-за какого нибудь бага в генераторе кода RTIC.

#![allow(unused)]

fn main() {

// код, сгенерированный RTIC

mod app {

// ..

#[no_mangle]

unsafe fn UART1() {

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

const PRIORITY: u8 = 2;

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

let initial = basepri::read();

let priority = Celclass="underline" :new(PRIORITY);

bar(bar::Context {

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

// ..

});

// БАГ: ЗАБЫЛИ восстановить BASEPRI на значение из снимка

basepri::write(initial);

}

}

}

В результате, idle запустится на динамическом приоритете 2 и на самом деле система больше никогда не перейдет на динамический приоритет ниже 2. Это не компромис для безопасности памяти программы, а влияет на диспетчеризацию задач: в этом конкретном случае задачи с приоритетом 1 никогда не получат шанс на запуск.

Поиск максимального приоритета ресурса (ceiling) - поиск динамического приоритета, который любая задача должна иметь, чтобы безопасно работать с памятью ресурсов. Анализ приоритетов - относительно прост, но критичен для безопасности памяти RTIC программ.