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

// ..


pub struct Context<'a> {

pub spawn: Spawn<'a>,

// ..

}


pub struct Spawn<'a> {

// отслеживает динамический приоритет задачи

priority: &'a Cell,

}


impl<'a> Spawn<'a> {

// `unsafe` и спрятано, поскольку сы не хотит, чтобы пользователь вмешивался сюда

#[doc(hidden)]

pub unsafe fn priority(&self) ->&Cell {

self.priority

}

}

}


mod app {

// ..


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

const RQ1_CEILING: u8 = 2;


// используется, чтобы отследить сколько еще сообщений для `bar` можно поставить в очередь

// `U2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь

// эта очередь заполняется фреймворком до того, как запустится `init`

static mut bar_FQ: Queue<(), U2> = Queue::new();


// Поиск максимального приоритета для конечного потребителя `bar_FQ`

const bar_FQ_CEILING: u8 = 2;


// приоритет-ориентированная критическая секция

//

// это запускае переданное замыкание `f` с динамическим приоритетом не ниже

// `ceiling`

fn lock(priority: &Cell, ceiling: u8, f: impl FnOnce()) {

// ..

}


impl<'a> foo::Spawn<'a> {

/// Вызывает задачу `bar`

pub fn bar(&self) -> Result<(), ()> {

unsafe {

match lock(self.priority(), bar_FQ_CEILING, || {

bar_FQ.split().1.dequeue()

}) {

Some(()) => {

lock(self.priority(), RQ1_CEILING, || {

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

RQ1.split().1.enqueue_unchecked(Ready {

task: T1::bar,

// ..

})

});


// вызываем прерывание, которое запускает диспетчер задач

rtic::pend(Interrupt::UART0);

}


None => {

// достигнута максимальная вместительность; неудачный вызов

Err(())

}

}

}

}

}

}

}

Использование bar_FQ для ограничения числа задач bar, которые могут бы вызваны, может показаться искусственным, но это будет иметь больше смысла, когда мы поговорим о вместительности задач.

Сообщения

Мы пропустили, как на самом деле работает передача сообщений, поэтому давайте вернемся к реализации spawn, но в этот раз для задачи baz, которая принимает сообщение типа u64.


#![allow(unused)]

fn main() {

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

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

}


mod app {

// ..


// Теперь мы покажем все содержимое структуры `Ready`

struct Ready {

task: Task,

// индекс сообщения; используется с буфером `INPUTS`

index: u8,

}


// память, зарезервированная для хранения сообщений, переданных `baz`

static mut baz_INPUTS: [MaybeUninit; 2] =

[MaybeUninit::uninit(), MaybeUninit::uninit()];


// список свободной памяти: используется для отслеживания свободных ячеек в массиве `baz_INPUTS`

// эта очередь инициализируется значениями `0` и `1` перед запуском `init`

static mut baz_FQ: Queue = Queue::new();


// Поиск максимального приоритета для конечного потребителя `baz_FQ`

const baz_FQ_CEILING: u8 = 2;


impl<'a> foo::Spawn<'a> {

/// Spawns the `baz` task

pub fn baz(&self, message: u64) -> Result<(), u64> {

unsafe {

match lock(self.priority(), baz_FQ_CEILING, || {

baz_FQ.split().1.dequeue()

}) {

Some(index) => {

// ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера

baz_INPUTS[index as usize].write(message);


lock(self.priority(), RQ1_CEILING, || {

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

RQ1.split().1.enqueue_unchecked(Ready {

task: T1::baz,

index,

});

});


// вызываем прерывание, которое запускает диспетчер задач

rtic::pend(Interrupt::UART0);

}


None => {

// достигнута максимальная вместительность; неудачный вызов

Err(message)

}

}

}

}

}

}

}

А теперь давайте взглянем на настоящую реализацию диспетчера задач:


#![allow(unused)]

fn main() {

mod app {

// ..


#[no_mangle]

unsafe UART1() {

const PRIORITY: u8 = 1;


let snapshot = basepri::read();


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

match ready.task {

Task::baz => {

// ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера

let input = baz_INPUTS[ready.index as usize].read();


// сообщение было прочитано, поэтому можно вернуть ячейку обратно

// чтобы освободить очередь

// (диспетчер задач имеет эксклюзивный доступ к

// последнему элементу очереди)

baz_FQ.split().0.enqueue_unchecked(ready.index);


let priority = Cell::new(PRIORITY);

baz(baz::Context::new(&priority), input)

}


Task::bar => {

// выглядит также как ветка для `baz`