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

Поздние ресурсы полезны, например, для move (передача владения) периферии, инициализированной в init, в задачи.

Пример ниже использует поздние ресурсы, чтобы установить неблокируемый односторонний канал между обработчиком прерывания UART0 и задачей idle. Для канала использована очередь типа один производитель-один потребитель Queue. Структура очереди разделяется на потребителя и производителя в init, а затем каждая из частей располагается в отдельном ресурсу; UART0 владеет ресурсом производителя, а idle владеет ресурсом потребителя.


#![allow(unused)]

fn main() {

{{#include ../../../../examples/late.rs}}

}

$ cargo run --example late

received message: 42

Только разделяемый доступ

По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (&mut-) к ресурсам, но возможно указать, что задаче достаточен разделяемый доступ (&-) к ресурсы с помощью синтакисиса &resource_name в списке resources.

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

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

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


#![allow(unused)]

fn main() {

//! examples/static.rs


#![deny(unsafe_code)]

#![deny(warnings)]

#![no_main]

#![no_std]


use panic_semihosting as _;


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

mod app {

use cortex_m_semihosting::{debug, hprintln};

use lm3s6965::Interrupt;


#[shared]

struct Shared {

key: u32,

}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

rtic::pend(Interrupt::UART0);

rtic::pend(Interrupt::UART1);


(Shared { key: 0xdeadbeef }, Local {}, init::Monotonics())

}


#[task(binds = UART0, shared = [&key])]

fn uart0(cx: uart0::Context) {

let key: &u32 = cx.shared.key;

hprintln!("UART0(key = {:#x})", key).unwrap();


debug::exit(debug::EXIT_SUCCESS);

}


#[task(binds = UART1, priority = 2, shared = [&key])]

fn uart1(cx: uart1::Context) {

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

}

}

}

$ cargo run --example only-shared-access

UART1(key = 0xdeadbeef)

UART0(key = 0xdeadbeef)

Неблокируемый доступ к изменяемым ресурсам

Есть две других возможности доступа к ресурсам

   • #[lock_free]: могут быть несколько задач с одинаковым приоритетом, получающие доступ к ресурсу без критических секций. Так как задачи с одинаковым приоритетом никогда не могут вытеснить друг друга, это безопасно.

   • #[task_local]: в этом случае должна быть только одна задача, использующая этот ресурс, так же как локальный static mut ресурс задачи, но (опционально) устанавливаемая с в init.

Программные задачи

В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события, RTIC также поддерживает программные задачи, которые могут порождаться приложением из любого контекста выполнения.

Программным задачам можно также назначать приоритет и, под капотом, они диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные прерывания, были указаны в аргументе dispatchers модуля app, если используются программные задачи; часть из этих свободных прерываний будут использованы для управления программными задачами. Преимущество программных задач над аппаратными в том, что множество задач можно назначить на один обработчик прерывания.

Программные задачи также определяются атрибутом task, но аргумент binds опускается.

Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах. Три программные задачи привязаны к 2-м обработчикам прерываний.


#![allow(unused)]

fn main() {

//! examples/task.rs


#![deny(unsafe_code)]

#![deny(warnings)]

#![no_main]

#![no_std]


use panic_semihosting as _;


#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]

mod app {

use cortex_m_semihosting::{debug, hprintln};


#[shared]

struct Shared {}


#[local]

struct Local {}


#[init]

fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {

foo::spawn().unwrap();


(Shared {}, Local {}, init::Monotonics())

}


#[task]

fn foo(_: foo::Context) {

hprintln!("foo - start").unwrap();


// spawns `bar` onto the task scheduler

// `foo` and `bar` have the same priority so `bar` will not run until

// after `foo` terminates

bar::spawn().unwrap();


hprintln!("foo - middle").unwrap();


// spawns `baz` onto the task scheduler

// `baz` has higher priority than `foo` so it immediately preempts `foo`

baz::spawn().unwrap();


hprintln!("foo - end").unwrap();

}


#[task]

fn bar(_: bar::Context) {

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


debug::exit(debug::EXIT_SUCCESS);

}


#[task(priority = 2)]

fn baz(_: baz::Context) {

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