Функциональность очередь таймера позволяет пользователю планировать задачи на запуск в опреленное время в будущем. Неудивительно, что эта функция также реализуется с помощью очереди: очередь приоритетов, где запланированные задачи сортируются в порядке аозрастания времени. Эта функция требует таймер, способный устанавливать прерывания истечения времени. Таймер используется для пуска прерывания, когда настает запланированное время задачи; в этот момент задача удаляется из очереди таймера и помещается в очередь готовности.
Давайте посмотрим, как это реализовано в коде. Рассмотрим следующую программу:
#![allow(unused)]
fn main() {
#[rtic::app(device = ..)]
mod app {
#[task(capacity = 2, schedule = [foo])]
fn foo(c: foo::Context, x: u32) {
c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok();
}
extern "C" {
fn UART0();
}
}
}
Давайте сначала взглянем на интерфейс schedule.
#![allow(unused)]
fn main() {
mod foo {
pub struct Schedule<'a> {
priority: &'a Cell
}
impl<'a> Schedule<'a> {
#[doc(hidden)]
pub unsafe fn priority(&self) -> &Cell
self.priority
}
}
}
mod app {
type Instant =
enum T {
foo,
}
struct NotReady {
index: u8,
instant: Instant,
task: T,
}
static mut TQ: TimerQueue
const TQ_CEILING: u8 = 1;
static mut foo_FQ: Queue
const foo_FQ_CEILING: u8 = 1;
static mut foo_INPUTS: [MaybeUninit
[MaybeUninit::uninit(), MaybeUninit::uninit()];
static mut foo_INSTANTS: [MaybeUninit
[MaybeUninit::uninit(), MaybeUninit::uninit()];
impl<'a> foo::Schedule<'a> {
fn foo(&self, instant: Instant, input: u32) -> Result<(), u32> {
unsafe {
let priority = self.priority();
if let Some(index) = lock(priority, foo_FQ_CEILING, || {
foo_FQ.split().1.dequeue()
}) {
foo_INSTANTS[index as usize].write(instant);
foo_INPUTS[index as usize].write(input);
let nr = NotReady {
index,
instant,
task: T::foo,
};
lock(priority, TQ_CEILING, || {
TQ.enqueue_unchecked(nr);
});
} else {
Err(input)
}
}
}
}
}
}
Это очень похоже на реализацию Spawn. На самом деле одни и те же буфер INPUTS и список сободной памяти (FQ) используются совместно интерфейсами spawn и schedule. Главное отличие между ними в том, что schedule также размещает Instant, момент на который задача запланирована на запуск, в отдельном буфере (foo_INSTANTS в нашем случае).
TimerQueue::enqueue_unchecked делает немного больше работы, чем просто добавление записи в min-heap: он также вызывает прерывание системного таймера (SysTick), если новая запись оказывается первой в очереди.
Прерывание системного таймера (SysTick) заботится о двух вещах: передаче задач, которых становятся готовыми из очереди таймера в очередь готовности и установке прерывания истечения времени, когда наступит запланированное время следующей задачи.
Давайте посмотрим на соответствующий код.
#![allow(unused)]
fn main() {
mod app {
#[no_mangle]
fn SysTick() {
const PRIORITY: u8 = 1;