Выбрать главу

Для расчета максимального приоритета ресурса мы должны сначала составить список задач, имеющих доступ к ресурсу -- так как фреймворк RTIC форсирует контроль доступа к ресурсам на этапе компиляции, он также имеет доступ к этой информации на этапе компиляции. Максимальный приоритет ресурса - просто наивысший логический приоритет среди этих задач.

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

В предыдущем разделе мы показывали, что разделяемые ресусы могут быть представлены уникальными ссылками (&mut-) или скрываться за прокси в зависимости от того, имеет ли задача к ним доступ. Какой из вариантов представляется задаче зависит от приоритета задачи и максимального приоритета ресурса. Если приоритет задачи такой же, как максимальный приоритет ресурса, тогда задача получает уникальную ссылку (&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) другие задачи.