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

// `ceiling`

fn lock(priority: &Cell<u8>, 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<u64>; 2] =

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

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

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

static mut baz_FQ: Queue<u8, U2> = 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();

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

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

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