// ..
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`