Ресурсы

Одно из ограничений атрибутов, предоставляемых библиотекой cortex-m-rt является то, что совместное использование данных (или периферии) между прерываниями, или прерыванием и функцией init, требуют cortex_m::interrupt::Mutex, который всегда требует отключения всех прерываний для доступа к данным. Отключение всех прерываний не всегда необходимо для безопасности памяти, но компилятор не имеет достаточно информации, чтобы оптимизировать доступ к разделяемым данным.

Атрибут app имеет полную картину приложения, поэтому может оптимизировать доступ к static-переменным. В RTFM мы обращаемся к static-переменным, объявленным внутри псевдо-модуля app как к ресурсам. Чтобы получить доступ к ресурсу, контекст (init, idle, interrupt или exception) должен сначала определить аргумент resources в соответствующем атрибуте.

В примере ниже два обработчика прерываний имеют доступ к одному и тому же ресурсу. Никакого Mutex в этом случае не требуется, потому что оба обработчика запускаются с одним приоритетом и никакого вытеснения быть не может. К ресурсу SHARED можно получить доступ только из этих двух прерываний.


# #![allow(unused_variables)]
#fn main() {
//! examples/resource.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    struct Resources {
        // A resource
        #[init(0)]
        shared: u32,
    }

    #[init]
    fn init(_: init::Context) {
        rtfm::pend(Interrupt::UART0);
        rtfm::pend(Interrupt::UART1);
    }

    // `shared` cannot be accessed from this context
    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        debug::exit(debug::EXIT_SUCCESS);

        // error: no `resources` field in `idle::Context`
        // _cx.resources.shared += 1;

        loop {}
    }

    // `shared` can be accessed from this context
    #[task(binds = UART0, resources = [shared])]
    fn uart0(cx: uart0::Context) {
        let shared: &mut u32 = cx.resources.shared;
        *shared += 1;

        hprintln!("UART0: shared = {}", shared).unwrap();
    }

    // `shared` can be accessed from this context
    #[task(binds = UART1, resources = [shared])]
    fn uart1(cx: uart1::Context) {
        *cx.resources.shared += 1;

        hprintln!("UART1: shared = {}", cx.resources.shared).unwrap();
    }
};

#}
$ cargo run --example resource
UART0: shared = 1
UART1: shared = 2

Приоритеты

Приоритет каждого прерывания можно определить в атрибутах interrupt и exception. Невозможно установить приоритет любым другим способом, потому что рантайм забирает владение прерыванием NVIC; также невозможно изменить приоритет обработчика / задачи в рантайме. Благодаря этому ограничению у фреймворка есть знание о статических приоритетах всех обработчиков прерываний и исключений.

Прерывания и исключения могут иметь приоритеты в интервале 1..=(1 << NVIC_PRIO_BITS), где NVIC_PRIO_BITS - константа, определённая в библиотеке device. Задача idle имеет приоритет 0, наименьший.

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

API критической секции, предоставляемое фреймворком RTFM (см. Mutex), основано на динамических приоритетах вместо отключения прерываний. Из этого следует, что критические секции не будут допускать запуск некоторых обработчиков, включая все соперничающие за ресурс, но будут позволять запуск обработчиков с большим приоритетом не соперничащих за ресурс.

В примере ниже у нас есть 3 обработчика прерываний с приоритетами от одного до трех. Два обработчика с низким приоритетом соперничают за ресурс SHARED. Обработчик с низшим приоритетом должен заблокировать (lock) ресурс SHARED, чтобы получить доступ к его данным, в то время как обработчик со средним приоритетом может напрямую получать доступ к его данным. Обработчик с наивысшим приоритетом может свободно вытеснять критическую секцию, созданную обработчиком с низшим приоритетом.


# #![allow(unused_variables)]
#fn main() {
//! examples/lock.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    struct Resources {
        #[init(0)]
        shared: u32,
    }

    #[init]
    fn init(_: init::Context) {
        rtfm::pend(Interrupt::GPIOA);
    }

    // when omitted priority is assumed to be `1`
    #[task(binds = GPIOA, resources = [shared])]
    fn gpioa(mut c: gpioa::Context) {
        hprintln!("A").unwrap();

        // the lower priority task requires a critical section to access the data
        c.resources.shared.lock(|shared| {
            // data can only be modified within this critical section (closure)
            *shared += 1;

            // GPIOB will *not* run right now due to the critical section
            rtfm::pend(Interrupt::GPIOB);

            hprintln!("B - shared = {}", *shared).unwrap();

            // GPIOC does not contend for `shared` so it's allowed to run now
            rtfm::pend(Interrupt::GPIOC);
        });

        // critical section is over: GPIOB can now start

        hprintln!("E").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[task(binds = GPIOB, priority = 2, resources = [shared])]
    fn gpiob(c: gpiob::Context) {
        // the higher priority task does *not* need a critical section
        *c.resources.shared += 1;

        hprintln!("D - shared = {}", *c.resources.shared).unwrap();
    }

    #[task(binds = GPIOC, priority = 3)]
    fn gpioc(_: gpioc::Context) {
        hprintln!("C").unwrap();
    }
};

#}
$ cargo run --example lock
A
B - shared = 1
C
D - shared = 2
E

Поздние ресурсы

В отличие от обычных static-переменных, к которым должно быть присвоено начальное значение, ресурсы можно инициализировать в рантайме. Мы называем ресурсы, инициализируемые в рантайме поздними. Поздние ресурсы полезны для переноса (как при передаче владения) периферии из init в обработчики прерываний и исключений.

Поздние ресурсы определяются как обычные ресурсы, но им присваивается начальное значение () (the unit value). init должен вернуть начальные значения для всех поздних ресурсов, упакованные в структуру типа init::LateResources.

В примере ниже использованы поздние ресурсы, чтобы установить неблокированный, односторонний канал между обработчиком прерывания UART0 и функцией idle. Очередь типа один производитель-один потребитель Queue использована как канал. Очередь разделена на элементы потребителя и поизводителя в init и каждый элемент расположен в отдельном ресурсе; UART0 владеет ресурсом произодителя, а idle владеет ресурсом потребителя.


# #![allow(unused_variables)]
#fn main() {
//! examples/late.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use heapless::{
    consts::*,
    i,
    spsc::{Consumer, Producer, Queue},
};
use lm3s6965::Interrupt;
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    // Late resources
    struct Resources {
        p: Producer<'static, u32, U4>,
        c: Consumer<'static, u32, U4>,
    }

    #[init]
    fn init(_: init::Context) -> init::LateResources {
        static mut Q: Queue<u32, U4> = Queue(i::Queue::new());

        let (p, c) = Q.split();

        // Initialization of late resources
        init::LateResources { p, c }
    }

    #[idle(resources = [c])]
    fn idle(c: idle::Context) -> ! {
        loop {
            if let Some(byte) = c.resources.c.dequeue() {
                hprintln!("received message: {}", byte).unwrap();

                debug::exit(debug::EXIT_SUCCESS);
            } else {
                rtfm::pend(Interrupt::UART0);
            }
        }
    }

    #[task(binds = UART0, resources = [p])]
    fn uart0(c: uart0::Context) {
        c.resources.p.enqueue(42).unwrap();
    }
};

#}
$ cargo run --example late
received message: 42

static-ресурсы

Переменные типа static также можно использовать в качестве ресурсов. Задачи могут получать только (разделяемые) & ссылки на ресурсы, но блокировки не нужны для доступа к данным. Вы можете думать о static-ресурсах как о простых static-переменных, которые можно инициализировать в рантайме и иметь лучшие правила видимости: Вы можете контролировать, какие задачи получают доступ к переменной, чтобы переменная не была видна всем фунциям в область видимости, где она была объявлена.

В примере ниже ключ загружен (или создан) в рантайме, а затем использован в двух задачах, запущенных на разных приоритетах.


# #![allow(unused_variables)]
#fn main() {
{{#include ../../../../examples/static.rs}}
#}
$ cargo run --example static
{{#include ../../../../ci/expected/static.run}}```