// уменьшить (восстановить) динамический приоритет до `1`
unsafe { basepri::write(224) }
// ПРИМЕЧАНИЕ: BASEPRI содержит значение `224` в этот момент
// обработчик UART0 восстановит значение `0` перед завершением
}
}
Инвариант BASEPRI
Инвариант, который фреймворк 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 = Cell::new(PRIORITY);
bar(bar::Context {
resources: bar::Resources::new(&priority),
// ..
});
// БАГ: ЗАБЫЛИ восстановить BASEPRI на значение из снимка
basepri::write(initial);
}
}
}
В результате, idle запустится на динамическом приоритете 2 и на самом деле система больше никогда не перейдет на динамический приоритет ниже 2. Это не компромис для безопасности памяти программы, а влияет на диспетчеризацию задач: в этом конкретном случае задачи с приоритетом 1 никогда не получат шанс на запуск.
Анализ приоритетов
Поиск максимального приоритета
ресурса (ceiling) - поиск динамического приоритета, который любая задача должна иметь, чтобы безопасно работать с памятью ресурсов. Анализ приоритетов - относительно прост, но критичен для безопасности памяти RTIC программ.