// `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();
// сообщение было прочитано, поэтому можно вернуть ячейку обратно
// чтобы освободить очередь
// (диспетчер задач имеет эксклюзивный доступ к