Ресурсы

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

Ресурсы - это данные, видимые только функциями, определенными внутри модуля #[app]. Фреймворк дает пользователю полный контроль за тем, какой контекст может получить доступ к какому ресурсу.

Все ресурсы определены в одной структуре внутри модуля #[app]. Каждое поле структуры соответствует отдельному ресурсу. struct-ура должна быть аннотирована следующим атрибутом: #[resources].

Ресурсам могут быть опционально даны начальные значения с помощью атрибута #[init]. Ресурсы, которым не передано начально значение, называются поздними ресурсами, более детально они описаны в одном из разделов на этой странице.

Каждый контекс (задача-обработчик, init или idle) должен указать ресурсы, к которым он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя аргумент resources. Этот аргумент принимает список имен ресурсов в качестве значения. Перечисленные ресурсы становятся доступны в контексте через поле resources структуры Context.

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


#![allow(unused)]

fn main() {

//! examples/resource.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 {}


#[local]

struct Local {

local_to_uart0: i64,

local_to_uart1: i64,

}


#[init]

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

rtic::pend(Interrupt::UART0);

rtic::pend(Interrupt::UART1);


(

Shared {},

// initial values for the `#[local]` resources

Local {

local_to_uart0: 0,

local_to_uart1: 0,

},

init::Monotonics(),

)

}


// `#[local]` resources cannot be accessed from this context

#[idle]

fn idle(_cx: idle::Context) -> ! {

debug::exit(debug::EXIT_SUCCESS);


// error: no `local` field in `idle::Context`

// _cx.local.local_to_uart0 += 1;


// error: no `local` field in `idle::Context`

// _cx.local.local_to_uart1 += 1;


loop {

cortex_m::asm::nop();

}

}


// `local_to_uart0` can only be accessed from this context

// defaults to priority 1

#[task(binds = UART0, local = [local_to_uart0])]

fn uart0(cx: uart0::Context) {

*cx.local.local_to_uart0 += 1;

let local_to_uart0 = cx.local.local_to_uart0;


// error: no `local_to_uart1` field in `uart0::LocalResources`

// cx.local.local_to_uart1 += 1;


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

}


// `shared` can only be accessed from this context

// explicitly set to priority 2

#[task(binds = UART1, local = [local_to_uart1], priority = 2)]

fn uart1(cx: uart1::Context) {

*cx.local.local_to_uart1 += 1;

let local_to_uart1 = cx.local.local_to_uart1;


// error: no `local_to_uart0` field in `uart1::LocalResources`

// cx.local.local_to_uart0 += 1;


hprintln!("UART1: local_to_uart1 = {}", local_to_uart1).unwrap();

}

}

}

$ cargo run --example resource

UART1: local_to_uart1 = 1

UART0: local_to_uart0 = 1

Заметьте, что к ресурсу shared нельзя получить доступ из idle. Попытка сделать это приведет к ошибке компиляции.

lock

Критические секции необходимы для разделения изменяемых данных таким образом, чтобы избежать гонок данных.

Поле resources, передаваемого Context реализует трейт Mutex для каждого разделяемого ресурса, доступного задаче.

Единственный метод этого трейта, lock, запускает свой аргумент-замыкание в критической секции.

Критическая секция, создаваемая интерфейсом lock основана на динамических приоритетах: она временно повышает динамический приоритет контекста до максимального приоритета, что не дает другим задачам возможности вытеснить критическую секцию. Этот протокол синхронизации известен как Протокол немедленного максимального приоритета (ICPP), и компилируется диспетчером RTIC с Политикой ресурсов стека(SRP).

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


#![allow(unused)]

fn main() {

//! examples/lock.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 {

shared: u32,

}


#[local]

struct Local {}


#[init]

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

rtic::pend(Interrupt::GPIOA);


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

}


// when omitted priority is assumed to be `1`

#[task(binds = GPIOA, shared = [shared])]

fn gpioa(mut c: gpioa::Context) {

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


// the lower priority task requires a critical section to access the data

c.shared.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

rtic::pend(Interrupt::GPIOB);


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


// GPIOC does not contend for `shared` so it's allowed to run now

rtic::pend(Interrupt::GPIOC);

});


// critical section is over: GPIOB can now start


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


debug::exit(debug::EXIT_SUCCESS);

}


#[task(binds = GPIOB, priority = 2, shared = [shared])]

fn gpiob(mut c: gpiob::Context) {

// the higher priority task does still need a critical section

let shared = c.shared.shared.lock(|shared| {

*shared += 1;


*shared

});


hprintln!("D - shared = {}", 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

Множественное блокировка

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


#![allow(unused)]

fn main() {

//! examples/mutlilock.rs

//!

//! The multi-lock feature example.


#![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 {

shared1: u32,

shared2: u32,

shared3: u32,

}


#[local]

struct Local {}


#[init]

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

rtic::pend(Interrupt::GPIOA);


(

Shared {

shared1: 0,

shared2: 0,

shared3: 0,

},

Local {},

init::Monotonics(),

)

}


// when omitted priority is assumed to be `1`

#[task(binds = GPIOA, shared = [shared1, shared2, shared3])]

fn locks(c: locks::Context) {

let mut s1 = c.shared.shared1;

let mut s2 = c.shared.shared2;

let mut s3 = c.shared.shared3;


hprintln!("Multiple single locks").unwrap();

s1.lock(|s1| {

s2.lock(|s2| {

s3.lock(|s3| {

*s1 += 1;

*s2 += 1;

*s3 += 1;


hprintln!(

"Multiple single locks, s1: {}, s2: {}, s3: {}",

*s1,

*s2,

*s3

)

.unwrap();

})

})

});


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


(s1, s2, s3).lock(|s1, s2, s3| {

*s1 += 1;

*s2 += 1;

*s3 += 1;


hprintln!(

"Multiple single locks, s1: {}, s2: {}, s3: {}",

*s1,

*s2,

*s3

)

.unwrap();

});


debug::exit(debug::EXIT_SUCCESS);

}

}

}

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

Поздние ресурсы - такие ресурсы, которым не передано начальное значение во время компиляции с помощью атрибута #[init], но которые вместо этого инициализируются во время выполнения с помощью значений из структуры init::LateResources, возвращаемой функцией init.

Поздние ресурсы полезны, например, для 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.

Загрузка...