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

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

Пример для иллюстрации анализа приоритетов:


#![allow(unused)]

fn main() {

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

mod app {

struct Resources {

// доступен из `foo` (prio = 1) и `bar` (prio = 2)

// -> CEILING = 2

#[init(0)]

x: u64,


// доступен из `idle` (prio = 0)

// -> CEILING = 0

#[init(0)]

y: u64,

}


#[init(resources = [x])]

fn init(c: init::Context) {

// уникальная ссылка, потому что это `init`

let x: &mut u64 = c.resources.x;


// уникальная ссылка, потому что это `init`

let y: &mut u64 = c.resources.y;


// ..

}


// PRIORITY = 0

#[idle(resources = [y])]

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

// уникальная ссылка, потому что

// приоритет (0) == максимальному приоритету ресурса (0)

let y: &'static mut u64 = c.resources.y;


loop {

// ..

}

}


#[interrupt(binds = UART0, priority = 1, resources = [x])]

fn foo(c: foo::Context) {

// прокси-ресурс, потому что

// приоритет задач (1) < максимальному приоритету ресурса (2)

let x: resources::x = c.resources.x;


// ..

}


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

fn bar(c: foo::Context) {

// уникальная ссылка, потому что

// приоритет задачи (2) == максимальному приоритету ресурса (2)

let x: &mut u64 = c.resources.x;


// ..

}


// ..

}

}

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

RTIC поддерживает программные и аппаратные задачи. Каждая аппаратная задача назначается на отдельный обработчик прерывания. С другой стороны, несколько программных задач могут управляться одним обработчиком прерывания -- это сделано, чтобы минимизировать количество обработчиков прерывания, используемых фреймворком.

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

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

Очередь готовности - неблокируемая очередь типа SPSC (один производитель - один потребитель). Диспетчер задач владеет конечным потребителем в очереди; конечным производителем считается ресурс, за который соперничают задачи, которые могут вызывать (spawn) другие задачи.

Дисметчер задач

Давайте сначала глянем на код, генерируемый фреймворком для диспетчеризации задач. Рассмотрим пример:


#![allow(unused)]

fn main() {

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

mod app {

// ..


#[interrupt(binds = UART0, priority = 2, spawn = [bar, baz])]

fn foo(c: foo::Context) {

foo.spawn.bar().ok();


foo.spawn.baz(42).ok();

}


#[task(capacity = 2, priority = 1)]

fn bar(c: bar::Context) {

// ..

}


#[task(capacity = 2, priority = 1, resources = [X])]

fn baz(c: baz::Context, input: i32) {

// ..

}


extern "C" {

fn UART1();

}

}

}

Фреймворк создает следующий диспетчер задач, состоящий из обработчика прерывания и очереди готовности:


#![allow(unused)]

fn main() {

fn bar(c: bar::Context) {

// .. пользовательский код ..

}


mod app {

use heapless::spsc::Queue;

use cortex_m::register::basepri;


struct Ready {

task: T,

// ..

}


/// вызываемые (`spawn`) задачи, выполняющиеся с уровнем приоритета `1`

enum T1 {

bar,

baz,

}


// очередь готовности диспетчера задач

// `U4` - целое число, представляющее собой емкость этой очереди

static mut RQ1: Queue, U4> = Queue::new();


// обработчик прерывания, выбранный для диспетчеризации задач с приоритетом `1`

#[no_mangle]

unsafe UART1() {

// приоритет данного обработчика прерывания

const PRIORITY: u8 = 1;


let snapshot = basepri::read();


while let Some(ready) = RQ1.split().1.dequeue() {

match ready.task {

T1::bar => {

// **ПРИМЕЧАНИЕ** упрощенная реализация


// используется для отслеживания динамического приоритета

let priority = Cell::new(PRIORITY);


// вызов пользовательского кода

bar(bar::Context::new(&priority));

}


T1::baz => {

// рассмотрим `baz` позднее

}

}

}


// инвариант BASEPRI

basepri::write(snapshot);

}

}

}

Вызов задачи

Интерфейс spawn предоставлен пользователю как методы структурв Spawn. Для каждой задачи существует своя структура Spawn.

Код Spawn, генерируемый фреймворком для предыдущего примера выглядит так:


#![allow(unused)]

fn main() {

mod foo {