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

T::foo => {

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

lock(priority, RQ1_CEILING, || {

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

task: T1::foo,

index: ready.index,

})

});

// вызвать диспетчер задач

rtic::pend(Interrupt::UART0);

}

}

}

}

}

}

Выглядит похоже на диспетчер задач, за исключением того, что вместо запуска готовой задачи, она лишь переносится в очередь готовности, что ведет к ее запуску с нужным приоритетом.

TimerQueue::dequeue установит новое прерывание истечения времени, если вернет None. Он сязан с TimerQueue::enqueue_unchecked, который вызывает это прерывание; на самом деле, enqueue_unchecked передает задачу установки нового прерывание истечения времени обработчику SysTick.

RTIC предоставляет реализацию Monotonic, основанную на счетчике тактов DWT (Data Watchpoint and Trace). Instant::now возвращает снимок таймера; эти снимки DWT (Instantы) используются для сортировки записей в очереди таймера. Счетчик тактов - 32-битный счетчик, работающий на частоте ядра. Этот счетчик обнуляется каждые (1 << 32) тактов; у нас нет прерывания, ассоциированног с этим счетчиком, поэтому ничего ужасного не случится, когда он пройдет оборот.

Чтобы упорядочить Instantы в очереди, нам нужно сравнить 32-битные целые. Чтобы учесть обороты, мы используем разницу между двумя Instantами, a - b, и рассматриваем результат как 32-битное знаковое целое. Если результат меньше нуля, значит b более поздний Instant; если результат больше нуля, значит b более ранний Instant. Это значит, что планирование задачи на Instant, который на (1 << 31) - 1 тактов больше, чем запланированное время (Instant) первой (самой ранней) записи в очереди приведет к тому, что задача будет помещена в неправильное место в очереди. У нас есть несколько debug assertions в коде, чтобы предотвратить эту пользовательскую ошибку, но этого нельзя избежать, поскольку пользователь может написать (instant + duration_a) + duration_b и переполнить Instant.

Системный таймер, SysTick - 24-битный счетчик также работающий на частоте процессора. Когда следующая планируемая задача более, чем в 1 << 24 тактов в будущем, прерывание устанавливается на время в пределах 1 << 24 тактов. Этот процесс может происходить несколько раз, пока следующая запланированная задача не будет в диапазоне счетчика SysTick.

Подведем итог, оба Instant и Duration имеют разрешение 1 такт ядра, и Duration эффективно имеет (полуоткрытый) диапазон 0..(1 << 31) (не включая максимум) тактов ядра.

Вместительность очереди таймера рассчитывается как сумма вместительностей всех планируемых (schedule) задач. Как и в случае очередей готовности, это значит, что как только мы затребовали пустую ячейку в буфере INPUTS, мы гарантируем, что способны передать задачу в очередь таймера; это позволяет нам опустить проверки времени выполнения.

Приориет системного таймера не может быть установлен пользователем; он выбирается фреймворком. Чтобы убедиться, что низкоприоритетные задачи не препятствуют запуску высокоприоритетных, мы выбираем приоритет системного таймера максимальным из всех планируемых задач.

Чтобы понять, почему это нужно, рассмотрим вариант, когда две ранее запланированные задачи с приоритетами 2 и 3 становятся готовыми в примерно одинаковое время, но низкоприоритетная задача перемещается в очередь готовности первой. Если бы приоритет системного таймера был, например, равен 1, тогда после перемещения низкоприоритетной (2) задачи, это бы привело к завершению (из-за того, что приоритет выше приоритета системного таймера) ожидания выполнения высокоприоритетной задачи (3). Чтобы избежать такого сценария, системный таймер должен работать на приоритете, равном наивысшему из приоритетов планируемых задач; в этом примере это 3.

Очередь таймера - это ресурс, разделяемый всеми задачами, которые могут планировать (schedule) задачи и обработчиком SysTick. Также интерфейс schedule соперничает с интерфейсом spawn за списки свободной памяти. Все это должно уситываться в анализе приоритетов.