diff options
| author | Henrik Tjäder <henrik@tjaders.com> | 2023-02-24 21:28:13 +0100 |
|---|---|---|
| committer | Henrik Tjäder <henrik@tjaders.com> | 2023-03-01 00:35:25 +0100 |
| commit | 0fc86d972c0305e73252dc20d702d647eb342ee5 (patch) | |
| tree | eb01e1556958912344b890c964ff3f364aaad0d2 /book/ru/src/internals/timer-queue.md | |
| parent | 032316855d8b55fc572ab7fcbd1de7ba394b2fd1 (diff) | |
Book: Remove RTIC v1 Russian translation
Diffstat (limited to 'book/ru/src/internals/timer-queue.md')
| -rw-r--r-- | book/ru/src/internals/timer-queue.md | 372 |
1 files changed, 0 insertions, 372 deletions
diff --git a/book/ru/src/internals/timer-queue.md b/book/ru/src/internals/timer-queue.md deleted file mode 100644 index 9f2dc37..0000000 --- a/book/ru/src/internals/timer-queue.md +++ /dev/null @@ -1,372 +0,0 @@ -# Очередь таймера - -Функциональность очередь таймера позволяет пользователю планировать задачи на запуск -в опреленное время в будущем. Неудивительно, что эта функция также реализуется с помощью очереди: -очередь приоритетов, где запланированные задачи сортируются в порядке аозрастания времени. -Эта функция требует таймер, способный устанавливать прерывания истечения времени. -Таймер используется для пуска прерывания, когда настает запланированное время задачи; -в этот момент задача удаляется из очереди таймера и помещается в очередь готовности. - -Давайте посмотрим, как это реализовано в коде. Рассмотрим следующую программу: - -``` rust -#[rtic::app(device = ..)] -mod app { - // .. - - #[task(capacity = 2, schedule = [foo])] - fn foo(c: foo::Context, x: u32) { - // запланировать задачу на повторный запуск через 1 млн. тактов - c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok(); - } - - extern "C" { - fn UART0(); - } -} -``` - -## `schedule` - -Давайте сначала взглянем на интерфейс `schedule`. - -``` rust -mod foo { - pub struct Schedule<'a> { - priority: &'a Cell<u8>, - } - - impl<'a> Schedule<'a> { - // `unsafe` и спрятано, потому что мы не хотим, чтобы пользовать сюда вмешивался - #[doc(hidden)] - pub unsafe fn priority(&self) -> &Cell<u8> { - self.priority - } - } -} - -mod app { - type Instant = <path::to::user::monotonic::timer as rtic::Monotonic>::Instant; - - // все задачи, которые могут быть запланированы (`schedule`) - enum T { - foo, - } - - struct NotReady { - index: u8, - instant: Instant, - task: T, - } - - // Очередь таймера - двоичная куча (min-heap) задач `NotReady` - static mut TQ: TimerQueue<U2> = ..; - const TQ_CEILING: u8 = 1; - - static mut foo_FQ: Queue<u8, U2> = Queue::new(); - const foo_FQ_CEILING: u8 = 1; - - static mut foo_INPUTS: [MaybeUninit<u32>; 2] = - [MaybeUninit::uninit(), MaybeUninit::uninit()]; - - static mut foo_INSTANTS: [MaybeUninit<Instant>; 2] = - [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() - }) { - // `index` - владеющий укачатель на ячейки в этих буферах - 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 { - // Не осталось места, чтобы разместить входные данные / instant - Err(input) - } - } - } - } -} -``` - -Это очень похоже на реализацию `Spawn`. На самом деле одни и те же буфер -`INPUTS` и список сободной памяти (`FQ`) используются совместно интерфейсами -`spawn` и `schedule`. Главное отличие между ними в том, что `schedule` также -размещает `Instant`, момент на который задача запланирована на запуск, -в отдельном буфере (`foo_INSTANTS` в нашем случае). - -`TimerQueue::enqueue_unchecked` делает немного больше работы, чем -просто добавление записи в min-heap: он также вызывает прерывание -системного таймера (`SysTick`), если новая запись оказывается первой в очереди. - -## Системный таймер - -Прерывание системного таймера (`SysTick`) заботится о двух вещах: -передаче задач, которых становятся готовыми из очереди таймера в очередь готовности -и установке прерывания истечения времени, когда наступит запланированное -время следующей задачи. - -Давайте посмотрим на соответствующий код. - -``` rust -mod app { - #[no_mangle] - fn SysTick() { - const PRIORITY: u8 = 1; - - let priority = &Cell::new(PRIORITY); - while let Some(ready) = lock(priority, TQ_CEILING, || TQ.dequeue()) { - match ready.task { - 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`. - -## Точность и диапазон `cyccnt::Instant` и `cyccnt::Duration` - -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` -за списки свободной памяти. Все это должно уситываться в анализе приоритетов. - -Чтобы проиллюстрировать, рассмотрим следующий пример: - -``` rust -#[rtic::app(device = ..)] -mod app { - #[task(priority = 3, spawn = [baz])] - fn foo(c: foo::Context) { - // .. - } - - #[task(priority = 2, schedule = [foo, baz])] - fn bar(c: bar::Context) { - // .. - } - - #[task(priority = 1)] - fn baz(c: baz::Context) { - // .. - } -} -``` - -Анализ приоритетов происходил бы вот так: - -- `foo` (prio = 3) и `baz` (prio = 1) планируемые задачи, поэтому - `SysTick` должен работать на максимальном из этих двух приоритетов, т.е. `3`. - -- `foo::Spawn` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за - конечный потребитель `baz_FQ`; это приводит к максимальному приоритету `3`. - -- `bar::Schedule` (prio = 2) имеет экслюзивный доступ к - конечному потребителю `foo_FQ`; поэтому максимальный приоритет `foo_FQ` фактически `2`. - -- `SysTick` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за - очередь таймера `TQ`; это приводит к максимальному приоритету `3`. - -- `SysTick` (prio = 3) и `foo::Spawn` (prio = 3) оба имеют неблокируемый - доступ к очереди готовности `RQ3`, что хранит записи `foo`; - поэтому максимальный приоритет `RQ3` фактически `3`. - -- `SysTick` имеет эксклюзивный доступ к очереди готовности `RQ1`, - которая хранит записи `baz`; поэтому максимальный приоритет `RQ1` фактически `3`. - -## Изменения в реализации `spawn` - -Когда интерфейс `schedule` используется, реализация `spawn` немного -изменяется, чтобы отслеживать baseline задач. Как можете видеть в -реализации `schedule` есть буферы `INSTANTS`, используемые, чтобы -хранить время, в которое задача была запланирована навыполнение; -этот `Instant` читается диспетчером задач и передается в пользовательский -код, как часть контекста задачи. - -``` rust -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 => { - let input = baz_INPUTS[ready.index as usize].read(); - // ADDED - let instant = baz_INSTANTS[ready.index as usize].read(); - - baz_FQ.split().0.enqueue_unchecked(ready.index); - - let priority = Cell::new(PRIORITY); - // ИЗМЕНЕНО instant передан как часть контекста задачи - baz(baz::Context::new(&priority, instant), input) - } - - Task::bar => { - // выглядит также как ветка для `baz` - } - - } - } - - // инвариант BASEPRI - basepri::write(snapshot); - } -} -``` - -И наоборот, реализации `spawn` нужно писать значение в буфер `INSTANTS`. -Записанное значение располагается в структуре `Spawn` и это либо -время `start` аппаратной задачи, либо время `scheduled` программной задачи. - -``` rust -mod foo { - // .. - - pub struct Spawn<'a> { - priority: &'a Cell<u8>, - // ADDED - instant: Instant, - } - - impl<'a> Spawn<'a> { - pub unsafe fn priority(&self) -> &Cell<u8> { - &self.priority - } - - // ADDED - pub unsafe fn instant(&self) -> Instant { - self.instant - } - } -} - -mod app { - 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) => { - baz_INPUTS[index as usize].write(message); - // ADDED - baz_INSTANTS[index as usize].write(self.instant()); - - lock(self.priority(), RQ1_CEILING, || { - RQ1.split().1.enqueue_unchecked(Ready { - task: Task::foo, - index, - }); - }); - - rtic::pend(Interrupt::UART0); - } - - None => { - // достигнута максимальная вместительность; неудачный вызов - Err(message) - } - } - } - } - } -} -``` |
