From 0fc86d972c0305e73252dc20d702d647eb342ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 24 Feb 2023 21:28:13 +0100 Subject: [PATCH] Book: Remove RTIC v1 Russian translation --- .github/workflows/build.yml | 9 +- book/ru/book.toml | 9 - book/ru/src/RTIC.svg | 109 ---- book/ru/src/SUMMARY.md | 25 - book/ru/src/by-example.md | 23 - book/ru/src/by-example/app.md | 161 ------ book/ru/src/by-example/new.md | 84 --- book/ru/src/by-example/resources.md | 140 ----- book/ru/src/by-example/tasks.md | 116 ---- book/ru/src/by-example/timer-queue.md | 108 ---- book/ru/src/by-example/tips.md | 175 ------ book/ru/src/by-example/types-send-sync.md | 49 -- book/ru/src/internals.md | 14 - book/ru/src/internals/access.md | 158 ------ book/ru/src/internals/ceilings.md | 92 ---- book/ru/src/internals/critical-sections.md | 521 ------------------ .../src/internals/interrupt-configuration.md | 72 --- book/ru/src/internals/late-resources.md | 113 ---- book/ru/src/internals/non-reentrancy.md | 79 --- book/ru/src/internals/tasks.md | 399 -------------- book/ru/src/internals/timer-queue.md | 372 ------------- book/ru/src/migration.md | 4 - book/ru/src/migration/migration_rtic.md | 48 -- book/ru/src/migration/migration_v4.md | 230 -------- book/ru/src/migration/migration_v5.md | 365 ------------ book/ru/src/preface.md | 26 - 26 files changed, 1 insertion(+), 3500 deletions(-) delete mode 100644 book/ru/book.toml delete mode 100644 book/ru/src/RTIC.svg delete mode 100644 book/ru/src/SUMMARY.md delete mode 100644 book/ru/src/by-example.md delete mode 100644 book/ru/src/by-example/app.md delete mode 100644 book/ru/src/by-example/new.md delete mode 100644 book/ru/src/by-example/resources.md delete mode 100644 book/ru/src/by-example/tasks.md delete mode 100644 book/ru/src/by-example/timer-queue.md delete mode 100644 book/ru/src/by-example/tips.md delete mode 100644 book/ru/src/by-example/types-send-sync.md delete mode 100644 book/ru/src/internals.md delete mode 100644 book/ru/src/internals/access.md delete mode 100644 book/ru/src/internals/ceilings.md delete mode 100644 book/ru/src/internals/critical-sections.md delete mode 100644 book/ru/src/internals/interrupt-configuration.md delete mode 100644 book/ru/src/internals/late-resources.md delete mode 100644 book/ru/src/internals/non-reentrancy.md delete mode 100644 book/ru/src/internals/tasks.md delete mode 100644 book/ru/src/internals/timer-queue.md delete mode 100644 book/ru/src/migration.md delete mode 100644 book/ru/src/migration/migration_rtic.md delete mode 100644 book/ru/src/migration/migration_v4.md delete mode 100644 book/ru/src/migration/migration_v5.md delete mode 100644 book/ru/src/preface.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e16e460f89..cd52dee870 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -380,21 +380,14 @@ jobs: shell: 'script --return --quiet --command "bash {0}"' run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi - - name: Build book in Russian - shell: 'script --return --quiet --command "bash {0}"' - run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi - - name: Check links run: | td=$(mktemp -d) mkdir $td/book cp -r book/en/book $td/book/en - cp -r book/ru/book $td/book/ru cp LICENSE-* $td/book/en - cp LICENSE-* $td/book/ru lychee --offline $td/book/en/ - lychee --offline $td/book/ru/ # # Update stable branch # # @@ -485,7 +478,7 @@ jobs: # - name: Build books # shell: 'script --return --quiet --command "bash {0}"' # run: | -# langs=( en ru ) +# langs=( en ) # devver=( dev ) # # The latest stable must be the first element in the array # vers=( "1" "0.5" "0.4" ) diff --git a/book/ru/book.toml b/book/ru/book.toml deleted file mode 100644 index 98c5bf3f72..0000000000 --- a/book/ru/book.toml +++ /dev/null @@ -1,9 +0,0 @@ -[book] -authors = ["Jorge Aparicio, Per Lindgren and The Real-Time Interrupt-driven Concurrency developers"] -multilingual = false -src = "src" -title = "Real-Time Interrupt-driven Concurrency" - -[output.html] -git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic" -git-repository-icon = "fa-github" diff --git a/book/ru/src/RTIC.svg b/book/ru/src/RTIC.svg deleted file mode 100644 index 1c65cba059..0000000000 --- a/book/ru/src/RTIC.svg +++ /dev/null @@ -1,109 +0,0 @@ - -image/svg+xml diff --git a/book/ru/src/SUMMARY.md b/book/ru/src/SUMMARY.md deleted file mode 100644 index cf03cdae58..0000000000 --- a/book/ru/src/SUMMARY.md +++ /dev/null @@ -1,25 +0,0 @@ -# Summary - -[Введение](./preface.md) - -- [RTIC в примерах](./by-example.md) - - [Атрибут `app`](./by-example/app.md) - - [Ресурсы](./by-example/resources.md) - - [Программные задачи](./by-example/tasks.md) - - [Очередь таймера](./by-example/timer-queue.md) - - [Типы, Send и Sync](./by-example/types-send-sync.md) - - [Создание нового проекта](./by-example/new.md) - - [Советы и хитрости](./by-example/tips.md) -- [Инструкции по миграции](./migration.md) - - [v0.5.x на v1.0.x](./migration/migration_v5.md) - - [v0.4.x на v0.5.x](./migration/migration_v4.md) - - [RTFM на RTIC](./migration/migration_rtic.md) -- [Под капотом](./internals.md) - - [Настройка прерываний](./internals/interrupt-configuration.md) - - [Нереентерабельнось](./internals/non-reentrancy.md) - - [Контроль доступа](./internals/access.md) - - [Поздние ресурсы](./internals/late-resources.md) - - [Критические секции](./internals/critical-sections.md) - - [Анализ приоритетов](./internals/ceilings.md) - - [Программные задачи](./internals/tasks.md) - - [Очередь таймера](./internals/timer-queue.md) diff --git a/book/ru/src/by-example.md b/book/ru/src/by-example.md deleted file mode 100644 index 027716f120..0000000000 --- a/book/ru/src/by-example.md +++ /dev/null @@ -1,23 +0,0 @@ -# RTIC в примерах - -В этой части книги фреймворк Real-Time Interrupt-driven Concurrency (RTIC) представляется -новым пользователям путем прохода по примерам от простых к более сложным. - -Все примеры в этой части книги можно найти в [репозитарии] проекта. -Большинство из них можно пройти, запустив их на эмуляторе QEMU без специального оборудования. - -[репозитарии]: https://github.com/rtic-rs/cortex-m-rtic - -Для запуска примеров на вашем ПК, вам понадобится программа `qemu-system-arm`. -В [the embedded Rust book] есть инструкции по настройке среды для эмбеддед разработке, -в том числе QEMU. - -[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html - -## Примеры из реальной жизни - -Ниже представлены примеры использования RTIC (RTFM) в реальных проектах. - -### RTFM V0.4.2 - -- [etrombly/sandbox](https://github.com/etrombly/sandbox/tree/41d423bcdd0d8e42fd46b79771400a8ca349af55). Аппаратный дзэн-сад, рисующий картинки на песке. Картинки передаются по последовательному порту с помощью G-кода. diff --git a/book/ru/src/by-example/app.md b/book/ru/src/by-example/app.md deleted file mode 100644 index 5259bfa08e..0000000000 --- a/book/ru/src/by-example/app.md +++ /dev/null @@ -1,161 +0,0 @@ -# Атрибут `app` - -Это простейшая из возможных программ на RTIC: - -``` rust -{{#include ../../../../examples/smallest.rs}} -``` - -Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут -должен применяться к элементу `mod`. Атрибут `app` имеет обязательный аргумент `device`, -который принимает *путь* как значение. Это должен быть полный путь, указывающий на -*крейт доступа к периферии* (PAC), сгенерированный с помощью [`svd2rust`] версии **v0.14.x** -или новее. Более подробно в разделе [Создание нового проекта](./new.md). - -Атрибут `app` будет раскрыт в подходящую точку входа программы, поэтому -атрибут [`cortex_m_rt::entry`] не нужен. - -[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html -[`svd2rust`]: https://crates.io/crates/svd2rust -[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html - -## `init` - -Внутри модуля `app` атрибут ожидает найти функцию инициализации, помеченную -атрибутом `init`. Эта функция должна иметь сигнатуру -`fn(init::Context) (-> init::LateResources, init::Monotonics)`. - -Эта функция инициализации будет первой частью программы, выполняемой при запуске. -Функция `init` будет запущена *с отключенными прерываниями* и будет иметь эксклюзивный доступ -к Cortex-M, в котором токен `bare_metal::CriticalSection` доступен как `cs`. -Опционально, устройство-специфичные периферия доступна через поля `core` и `device` структуры -`init::Context`. - -`static mut` переменные, определенные в начале `init` будут преобразованы в -`&'static mut` ссылки, безопасные для доступа. Обратите внимание, данная возможность может -быть удалена в следующем релизе, см. `task_local` ресурсы. - -[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html - -Пример ниже показывает типы полей `core`, `device` и `cs`, и демонстрирует -безопасный доступ к `static mut` переменной. Поле `device` доступно только -когда аргумент `peripherals` установлен в `true` (по умолчанию). -В редких случаях, когда вы захотите создать приложение с минимальным потреблением ресурсов, -можно явно установить `peripherals` в `false`. - -``` rust -{{#include ../../../../examples/init.rs}} -``` - -Запуск примера напечатате `init` в консоли, а затем завершит процесс QEMU. - -``` console -$ cargo run --example init -{{#include ../../../../ci/expected/init.run}} -``` - -> **ПРИМЕЧАНИЕ**: Не забывайте указывать выбранное вами целевое устройство, передавая параметр target -> в cargo (например `cargo run --example init --target thumbv7m-none-eabi`) или -> настроив устройство, используемое по умолчанию для сборки примеров в `.cargo/config.toml`. -> В нашем случае используется Cortex M3, эмулируемый с помощью QEMU, поэтому пишем `thumbv7m-none-eabi`. -> Смотрите [`Создание нового проекта`](./new.md) для большей информации. - -## `idle` - -Функцию, помеченную атрибутом `idle` может опционально добавить в модуль. -Эта функция используется как специальная *задача ожидания* и должна иметь сигнатуру -`fn(idle::Context) - > !`. - -Если она присутствует, задача `idle` будет запущена после `init`. В отличие от -`init`, `idle` будет запущена *с включенными прерываниями* и она не может вернуть результат, -а значит должна работать вечно. - -Как и в `init`, `static mut` переменные будут трансформированы в `&'static mut` ссылки, -безопасные для доступа. Обратите внимание, данная возможность может -быть удалена в следующем релизе, см. `task_local` ресурсы. - -Пример ниже показывает, что `idle` запускается после `init`. - -**Примечание:** Цикл `loop {}` в функци ожидания не может быть пустым, так как это сломает -микроконтроллер, из-за того, что LLVM компилирует пустые циклы в инструкцию `UDF` в release mode. -Чтобы избежать неопределенного поведения, цикл должен включать "side-effect" -путем вставки ассемблерной инструкции (например, `WFI`) или ключевого слова `continue`. - -``` rust -{{#include ../../../../examples/idle.rs}} -``` - -``` console -$ cargo run --example idle -{{#include ../../../../ci/expected/idle.run}} -``` - -## Аппаратные задачи - -Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут `#[task]`, -который можно применять к функциям. Этот атрибут берет аргумент `binds`, чье значение - -это имя прерывания, которому будет назначен обработчик; -функция, декорированная этим атрибутом становится обработчиком прерывания. -В фреймворке такие типы задач именуются *аппаратными*, потому что они начинают -выполняться в ответ на аппаратное событие. - -Пример ниже демонстрирует использование атрибута `#[task]`, чтобы объявить -обработчик прерывания. Как и в случае с `#[init]` и `#[idle]` локальные `static -mut` переменные безопасны для использования с аппаратной задачей. - -``` rust -{{#include ../../../../examples/hardware.rs}} -``` - -``` console -$ cargo run --example hardware -{{#include ../../../../ci/expected/hardware.run}} -``` - -До сих пор все программы на RTIC, которые мы видели, не отличались от программ, -которые можно написать, используя лишь крейт `cortex-m-rt`. С этого момента мы -начинаем представлять возможности, уникальные для RTIC. - -## Приоритеты - -Статический приоритет каждого обработчика можно оределить в атрибуте `task`, используя -аргумент `priority`. Задачи могут иметь приоритет в диапазоне `1..=(1 << NVIC_PRIO_BITS)`, -где `NVIC_PRIO_BITS` - это константа, определенная в крейте `устройства`. -Когда аргумент `priority` не указан, предполагается, что приоритет равен `1`. -Задача `idle` имеет ненастраиваемый приоритет `0`, наименьший из возможных. - -> Более высокое значение означает более высокий приоритет в RTIC, что противоположно тому, -> что указано в периферии NVIC Cortex-M. -> Точнее, это значит, что число `10` обозначает приоритет **выше**, чем число `9`. - -Когда несколько задач готовы к запуску, задача с самым большим статическим -приоритетом будет запущена первой. Приоритезацию задач можно рассматривать по -такому сценарию: сигнал прерывания приходит во время выполнения задачи с низким приоритетом; -сигнал переключает задачу с высоким приоритетом в режим ожидания. -Разница в приоритетах приводи к тому, что задача с высоким приоритетом вытесняет задачу с низким: -выполнение задачи с низким приоритетом замораживается и задача с высоким приоритетом выполняется, -пока не будет завершена. Как только задача с высоким приоритетом будет остановлена, -продолжится выполнение задачи с низким приоритетом. - -Следующий пример демонстрирует диспетчеризацию на основе приоритетов задач. - -``` rust -{{#include ../../../../examples/preempt.rs}} -``` - -``` console -$ cargo run --example preempt -{{#include ../../../../ci/expected/preempt.run}} -``` - -Заметьте, что задача `gpiob` *не* вытесняет задачу `gpioc`, потому что ее приоритет -*такой же*, как и у `gpioc`. Однако, как только `gpioc` возвращает результат, -выполненяется задача `gpiob`, как более приоритетная по сравнению с `gpioa`. -Выполнение `gpioa` возобновляется только после выхода из `gpiob`. - -Еще одно замечание по поводу приоритетов: выбор приоритета большего, чем поддерживает устройство -(а именно `1 << NVIC_PRIO_BITS`) приведет к ошибке компиляции. -Из-за ограничений языка, сообщение об ошибке далеко от понимания: -вам скажут что-то похожее на "evaluation of constant value failed", а указатель на ошибку -*не* покажет на проблемное значение прерывания -- -мы извиняемся за это! diff --git a/book/ru/src/by-example/new.md b/book/ru/src/by-example/new.md deleted file mode 100644 index 0ff8d986e9..0000000000 --- a/book/ru/src/by-example/new.md +++ /dev/null @@ -1,84 +0,0 @@ -# Создание нового проекта - -Теперь, когда Вы изучили основные возможности фреймворка RTIC, Вы можете -попробовать его использовать на Вашем оборудовании следуя этим инструкциям. - -1. Создайте экземпляр из шаблона [`cortex-m-quickstart`]. - -[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart#cortex-m-quickstart - -``` console -$ # например используя `cargo-generate` -$ cargo generate \ - --git https://github.com/rust-embedded/cortex-m-quickstart \ - --name app - -$ # следуйте остальным инструкциям -``` - -2. Добавьте крейт доступа к периферии (PAC), сгенерированный с помощью[`svd2rust`] - **v0.14.x**, или крейт отладочной платы, у которой в зависимостях один из таких PAC'ов. - Убедитесь, что опция `rt` крейта включена. - -[`svd2rust`]: https://crates.io/crates/svd2rust - -В этом примере я буду использовать крейт устройства [`lm3s6965`]. -Эта библиотека не имеет Cargo-опции `rt`; эта опция всегда включена. - -[`lm3s6965`]: https://crates.io/crates/lm3s6965 - -Этот крейт устройства предоставляет линковочный скрипт с макетом памяти -целевого устройства, поэтому `memory.x` и `build.rs` нужно удалить. - -``` console -$ cargo add lm3s6965 --vers 0.1.3 - -$ rm memory.x build.rs -``` - -3. Добавьте крейт `cortex-m-rtic` как зависимость. - -``` console -$ cargo add cortex-m-rtic --allow-prerelease -``` - -4. Напишите свою RTIC программу. - -Здесь я буду использовать пример `init` из крейта `cortex-m-rtic`. - -Примеры находтся в папке `examples`, а содержание `init.rs` показано здесь: - -``` console -{{#include ../../../../examples/init.rs}} -``` - -Пример `init` использует устройство `lm3s6965`. Не забудьте настроить аргумент `device` -в атрибуте макроса app так, чтобы он соответствовал пути к PAC-крейту, если он отличается, -а также добавить перифериб и другие аргументы если необходимо. -Несмотря на то, что в программе могут использоваться псевдонимы типов, -здесь необходимо указать полный путь (из корня крейта). Для многих устройств, -есть общий подход в крейтах реализации HAL (с псевдонимом `hal`) и крейтах поддержки -отладочных плат реекспортиорвать PAC как `pac`, что приводит нас к образцу, аналогичному -приведенному ниже: - -```rust -use abcd123_hal as hal; -//... - -#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] -mod app { /*...*/ } -``` - -Пример `init` также зависит от крейта `panic-semihosting`: - -``` console -$ cargo add panic-semihosting -``` - -5. Соберите его, загрузите в микроконтроллер и запустите. - -``` console -$ # ПРИМЕЧАНИЕ: Я раскомментировал опцию `runner` в `.cargo/config.toml` -$ cargo run -{{#include ../../../../ci/expected/init.run}} -``` diff --git a/book/ru/src/by-example/resources.md b/book/ru/src/by-example/resources.md deleted file mode 100644 index ed8904ba93..0000000000 --- a/book/ru/src/by-example/resources.md +++ /dev/null @@ -1,140 +0,0 @@ -# Ресурсы - -Фреймворк предоставляет абстракцию для разделения данных между любыми контекстами, -с которыми мы встречались в предыдущей главе (задачами-обработчиками, `init` и `idle`): ресурсы. - -Ресурсы - это данные, видимые только функциями, определенными внутри модуля `#[app]`. -Фреймворк дает пользователю полный контроль за тем, какой контекст может -получить доступ к какому ресурсу. - -Все ресурсы определены в *двух* структурах внутри модуля `#[app]`. -Каждое поле этих структур соответствует отдельному ресурсу. -Одна `struct`-ура должна быть аннотирована атрибутом `#[local]`. -Другая `struct`-ура должна быть аннотирована атрибутом `#[shared]`. -Разница между этими двумя множествами ресурсов будет описана познее. - -Каждый контекс (задача-обработчик, `init` или `idle`) должен указать ресурсы, к которым -он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя -либо аргумент `local`, либо `shared`. Этот аргумент принимает список имен ресурсов в качестве значения. -Перечисленные ресурсы становятся доступны в контексте через поля `local` и `shared` структуры `Context`. - -Во время выполнения при выходе из функции `#[init]` все ресурсы инициализированы. -Функция `#[init]` должна возвращать начальные значения для всех ресурсов; -отсюда следует, что тип возвращаемого ею значения включает типы -структур `#[shared]` и `#[local]`. -Поскольку ресурсы инициализированы в ходе функции `#[init]`, к ним нельзя -получить доступ внетри функции `#[init]`. - -Пример программы, показанной ниже содержит два обработчика прерывания. -Каждый обработчик имеет доступ к его собственному `#[local]` ресурсу. - -``` rust -{{#include ../../../../examples/resource.rs}} -``` - -``` console -$ cargo run --example resource -{{#include ../../../../ci/expected/resource.run}} -``` - -К ресурсу `#[local]` нельзя получить доступ извне задачи к которой он -привязан атрибутом `#[task]`. -Попытка обращения к одному и тому же ресурсу `#[local]` из более чем одной -задачи - ошибка компиляции. - -## `lock` - -Критические секции необходимы для доступа к ресурсам `#[shared]` таким образом, -чтобы избежать гонок данных. - -Поле `shared`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого -ресурса, доступного задаче. - -Единственный метод этого трейта, [`lock`], запускает свой аргумент-замыкание в критической секции. - -[`Mutex`]: ../../../api/rtic/trait.Mutex.html -[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock - -Критическая секция, создаваемая интерфейсом `lock` основана на динамических приоритетах: -она временно повышает динамический приоритет контекста до *максимального* приоритета, -что не дает другим задачам возможности вытеснить критическую секцию. -Этот протокол синхронизации известен как [Протокол немедленного максимального приоритета -(ICPP)][icpp], и компилируется диспетчером RTIC с [Политикой ресурсов стека(SRP)][srp]. - -[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol -[srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy - -В примере ниже у нас есть три обработчика прерываний с приоритетами от одного до трех. -Два из обработчиков с более низким приоритетом соревнуются за ресурс `shared`, -поэтому должны блокировать доступа к данным ресурса. -Обработчик с наивысшим приоритетом, который не имеет доступа к ресурсу `shared`, -может свободно вытеснять критическую секцию, созданную обработчиком с низким приоритетом. - -``` rust -{{#include ../../../../examples/lock.rs}} -``` - -``` console -$ cargo run --example lock -{{#include ../../../../ci/expected/lock.run}} -``` - -## Множественное блокировка - -Это расширение к `lock`, чтобы уменьшить количесво отступов, блокируемые ресурсы можно объединять в кортежи. -Следующий пример это демонстрирует: - -``` rust -{{#include ../../../../examples/multilock.rs}} -``` - -## Только разделяемый (`&-`) доступ - -По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (`&mut-`) к ресурсам, -но возможно указать, что задаче достаточен разделяемый доступ (`&-`) к ресурсы с помощью синтакисиса -`&resource_name` в списке `resources`. - -Преимущество указания разделяемого досупа (`&-`) к ресурсу в том, что для доступа к ресурсу -не нужна блокировка, даже если за ресурс соревнуются несколько задач, запускаемые с -разными приоритетами. Недостаток в том, что задача получает только разделяемую ссылку (`&-`) -на ресурс, и ограничена операциями, возможными с ней, но там, где разделяемой ссылки достаточно, -такой подход уменьшает количесво требуемых блокировок. -В дополнение к простым неизменяемым данным, такой разделяемый доступ может быть полезен для -ресурсов, безопасно реализующих внутреннюю мутабельность с самоблокировкой или атомарными операциями. - -Заметьте, что в этом релизе RTIC невозможно запросить и эксклюзивный доступ (`&mut-`) -и разделяемый (`&-`) для *одного и того же* ресурса из различных задач. -Попытка это сделать приведет к ошибке компиляции. - -В примере ниже ключ (например криптографический ключ) загружается (или создается) во время выполнения, -а затем используется двумя задачами, запускаемымы с различным приоритетом без каких-либо блокировок. - -``` rust -{{#include ../../../../examples/only-shared-access.rs}} -``` - -``` console -$ cargo run --example only-shared-access -{{#include ../../../../ci/expected/only-shared-access.run}} -``` - -## Неблокируемый доступ к изменяемым ресурсам - -Критическая секция *не* требуется для доступа к ресурсу `#[shared]`, -к которому обращаются только из задач с *одинаковым* приоритетом. -В этом случае вы можете избежать `lock` API, добавив атрибут поля `#[lock_free]` при объявдении ресурса (смотреть пример ниже). -Заметьте, что это лишь для удобства: даже если вы используете `lock` API, -во время выполнения фреймворк *не* создаст критическую секцию. -Еще одно ценное замечание: использование `#[lock_free]` на ресурсах, -разделяемых задачами, запускаемыми с разными приоритетами -приведет к ошибке *компиляции* -- не импользование `lock` API может -привести к гонке данных в этом случае. - -``` rust -{{#include ../../../../examples/lock-free.rs}} -``` - -``` console -$ cargo run --example lock-free -{{#include ../../../../ci/expected/lock-free.run}} -``` \ No newline at end of file diff --git a/book/ru/src/by-example/tasks.md b/book/ru/src/by-example/tasks.md deleted file mode 100644 index 3c99d00efc..0000000000 --- a/book/ru/src/by-example/tasks.md +++ /dev/null @@ -1,116 +0,0 @@ -# Программные задачи - -В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события, -RTIC также поддерживает *программные* задачи, которые могут порождаться -приложением из любого контекста выполнения. - -Программным задачам можно также назначать приоритет и, под капотом, они -диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные -прерывания, были указаны в аргументе `dispatchers` модуля `app`, если используются -программные задачи; часть из этих свободных прерываний будут использованы для -управления программными задачами. Преимущество программных задач над аппаратными -в том, что множество задач можно назначить на один обработчик прерывания. - -Программные задачи также определяются атрибутом `task`, но аргумент `binds` опускается. - -Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах. -Три программные задачи привязаны к 2-м обработчикам прерываний. - -``` rust -{{#include ../../../../examples/task.rs}} -``` - -``` console -$ cargo run --example task -{{#include ../../../../ci/expected/task.run}} -``` - -## Передача сообщений - -Другое преимущество программной задачи в том, что задачам можно передать сообщения -в момент их запуска. Тип передаваемого сообщения должен быть определен в сигнатуре -задачи-обработчика. - -Пример ниже демонстрирует три задачи, две из которых ожидают сообщение. - -``` rust -{{#include ../../../../examples/message.rs}} -``` - -``` console -$ cargo run --example message -{{#include ../../../../ci/expected/message.run}} -``` - -## Вместимость - -RTIC *не* производит никакого рода аллокаций памяти в куче. -Память, необходимая для размещения сообщения резервируется статически. -По-умолчанию фреймворк минимизирует выделение памяти программой таким образом, -что каждая задача имеет "вместимость" для сообщения равную 1: -это значит, что не более одного сообщения можно передать задаче перед тем, как -у нее появится возможность к запуску. Это значение по-умолчанию можно -изменить для каждой задачи, используя аргумент `capacity`. -Этот аргумент принимает положительное целое, которое определяет как много -сообщений буфер сообщений задачи может хранить. - -Пример ниже устанавливает вместимость программной задачи `foo` равной 4. -Если вместимость не установить, второй вызов `spawn.foo` в `UART0` приведет к ошибке (панике). - -``` rust -{{#include ../../../../examples/capacity.rs}} -``` - -``` console -$ cargo run --example capacity -{{#include ../../../../ci/expected/capacity.run}} -``` - -## Обработка ошибок - -Интерфейс `spawn` возвращает вариант `Err`, если для размещения сообщения нет места. -В большинстве сценариев возникающие ошибки обрабатываются одним из двух способов: - -- Паника, с помощью `unwrap`, `expect`, и т.п. Этот метод используется, чтобы обнаружить - ошибку программиста (например bug) выбора вместительности, которая оказалась недостаточна. - Когда эта паника встречается во время тестирования, выбирается большая вместительность, - и перекомпиляция программы может решить проблему, но иногда достаточно окунуться глубже - и провести анализ времени выполнения программы, чтобы выяснить, может ли платформа - обрабатывать пиковые нагрузки, или процессор необходимо заменить на более быстрый. - -- Игнорирование результата. В программах реального времени, как и в обычных, может быть - нормальным иногда терять данные, или не получать ответ на некоторые события в пиковых ситуациях. - В таких сценариях может быть допустимо игнорирование ошибки вызова `spawn`. - -Следует отметить, что повторная попытка вызова `spawn` обычно неверный подход, поскольку -такая операция на практике вероятно никогда не завершится успешно. -Так как у нас есть только переключения контекста на задачи с *более высоким* приоритетом, -повторение вызова `spawn` на задаче с низким приоритом никогда не позволит планировщику -вызвать задачу, что значит, что буфер никогда не будет очищен. Такая ситуация отражена в -следующем наброске: - -``` rust -#[rtic::app(..)] -mod app { - #[init(spawn = [foo, bar])] - fn init(cx: init::Context) { - cx.spawn.foo().unwrap(); - cx.spawn.bar().unwrap(); - } - - #[task(priority = 2, spawn = [bar])] - fn foo(cx: foo::Context) { - // .. - - // программа зависнет здесь - while cx.spawn.bar(payload).is_err() { - // повтор попытки вызова spawn, если произошла ошибка - } - } - - #[task(priority = 1)] - fn bar(cx: bar::Context, payload: i32) { - // .. - } -} -``` diff --git a/book/ru/src/by-example/timer-queue.md b/book/ru/src/by-example/timer-queue.md deleted file mode 100644 index c8818d7d50..0000000000 --- a/book/ru/src/by-example/timer-queue.md +++ /dev/null @@ -1,108 +0,0 @@ -# Очередь таймера - -В отличие от интерфейса `spawn`, который немедленно передает программную задачу -планировщику для немедленного запуска, интерфейс `schedule` можно использовать -для планирования задачи к запуске через какое-то время в будущем. - -Чтобы использовать интерфейс `schedule`, предварительно должен быть определен -монотонный таймер с помощью аргумента `monotonic` атрибута `#[app]`. -Этот аргумент принимает путь к типу, реализующему трейт [`Monotonic`]. -Ассоциированный тип, `Instant`, этого трейта представляет метку времени в соответствущих -единицах измерения и широко используется в интерфейсе `schedule` -- предлагается смоделировать -этот тип позднее [один из таких есть в стандартной библиотеке][std-instant]. - -Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов), -разница двух `Instant`ов должна возвращать какой-то тип `Duration` (см. [`core::time::Duration`]) -и этот `Duration` должен реализовывать трейт `TryInto`. -Реализация этого трейта должна конвертировать значение `Duration`, которое -использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера -(SYST)". Результат преобразований должен быть 32-битным целым. -Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа. - -[`Monotonic`]: ../../../api/rtic/trait.Monotonic.html -[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html -[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html - -Для целевых платформ ARMv7+ крейт `rtic` предоставляет реализацию `Monotonic`, основанную на -встроенном CYCle CouNTer (CYCCNT). Заметьте, что это 32-битный таймер, работающий на -частоте центрального процессора, и поэтому не подходит для отслеживания интервалов времени в секундах. - -Когда планируется задача, (определенный пользователем) `Instant`, в который задача должна быть -выполнена, должен передаваться в качестве первого аргумента вызова `schedule`. - -К тому же, выбранный `monotonic` таймер, необходимо сконфигурировать и инициализировать в -фазе работы `#[init]`. Заметьте, что *также* касается случая использования `CYCCNT`, -предоставляемого крейтом `cortex-m-rtic`. - -Пример ниже планирует к выполнению две задачи из `init`: `foo` и `bar`. `foo` запланирована -к запуску через 8 миллионов циклов в будущем. Далее, `bar` запланировано запустить через -4 миллиона циклов в будущем. Таким образом, `bar` запустится до `foo`, так как и запланировано. - -> **DF:YJ**: Примеры, использующие интерфейс `schedule` или абстракцию `Instant` -> **не будут** правильно работать на эмуляторе QEMU, поскольку счетчик циклов Cortex-M -> функционально не был реализован в `qemu-system-arm`. - -``` rust -{{#include ../../../../examples/schedule.rs}} -``` - -Запусе программы на реальном оборудовании создает следующий вывод в консоли: - -``` text -{{#include ../../../../ci/expected/schedule.run}} -``` - -Когда интерфейс `schedule` используется, среда исполнения использует внутри -обработчик прерываний `SysTick` и периферию системного таймера (`SYST`), поэтому ни -тот ни другой нельзя использовать в программе. Это гарантируется изменением типа -`init::Context.core` с `cortex_m::Peripherals` на `rtic::Peripherals`. -Последняя структура содержит все поля из предыдущей кроме `SYST`. - -## Периодические задачи - -Программные задачи имеют доступ к моменту времени `Instant`, в который они были запланированы -на выполнение переменной `scheduled`. Эта информация и интерфейс `schedule` можно использовать, -чтобы реализовать периодические задачи, как показано ниже. - -``` rust -{{#include ../../../../examples/periodic.rs}} -``` - -Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания -даже несмотря на то, что `schedule.foo` была вызвана в *конце* `foo`. Использование -`Instant::now` вместо `scheduled` вызвало бы дрейф / колебания. - -``` text -{{#include ../../../../ci/expected/periodic.run}} -``` - -## Базовое время - -Для задач, вызываемых из `init` мы имеем точную информацию о их `scheduled` времени. -Для аппаратных задач такого времени нет, поскольку они асинхронны по природе. -Для аппаратных задач среда исполнения предоставляет время запуска (`start`), которое отражает -время, в которое обработчик прерывания будет запущен. - -Заметьте, что `start` **не** равно времени прихода события, которое вызывает задачу. -В зависимости от приоритета задачи и загрузки системы, время `start` может сильно отдалиться от -времени прихода события. - -Какое по вашему мнению будет значение `scheduled` для программных задач, которые вызываются через -`spawn` вместо планирования? Ответ в том, что вызываемые задачи наследуют -*базовое* время того контекста, который их вызывает. Базовое время аппаратных задач - -это их время `start`, базовое время программных задач - их время `scheduled`, а -базовое время `init` - время старта системы, или нулевое -(`Instant::zero()`). `idle` на самом деле не имеет базового времени, но задачи вызываемые из нее, -используют `Instant::now()` в качестве базового. - -Пример ниже демонстрирует разные смыслы *базового времени*. - -``` rust -{{#include ../../../../examples/baseline.rs}} -``` - -Запуск программы на реальном оборудовании приведет к следующему выводу в консоли: - -``` text -{{#include ../../../../ci/expected/baseline.run}} -``` diff --git a/book/ru/src/by-example/tips.md b/book/ru/src/by-example/tips.md deleted file mode 100644 index 7d4fc2f47b..0000000000 --- a/book/ru/src/by-example/tips.md +++ /dev/null @@ -1,175 +0,0 @@ -# Советы и хитрости - -Полные примеры для RTIC смотрите в репозитарии [rtic-examples][rtic-examples]. - -[rtic-examples]: https://github.com/rtic-rs/rtic-examples - -## Обобщенное программирование (Generics) - -Все объекты, предоставляющие ресурысы реализуют трейт `rtic::Mutex`. -Если ресурс не реализует его, можно обернуть его в новый тип [`rtic::Exclusive`], -который реализует трейт `Mutex`. С помощью этого нового типа -можно написать обобщенную функцию, которая работает с обобщенным ресурсом и -вызывать его из различных задач, чтобы производить однотипные операции над -похожим множеством ресурсов. -Вот один такой пример: - -[`rtic::Exclusive`]: ../../../api/rtic/struct.Exclusive.html - -``` rust -{{#include ../../../../examples/generics.rs}} -``` - -``` console -$ cargo run --example generics -{{#include ../../../../ci/expected/generics.run}} -``` - -## Условная компиляция - -Вы можете использовать условную компиляцию (`#[cfg]`) на ресурсах (полях структуры -`#[resources] struct Resources`) и задачах (элементах `fn`). -Эффект использования атрибутов `#[cfg]` в том, что ресурс/ задача -будут *не* доступны в соответствующих структурах `Context` если условие не выполняется. - -В примере ниже выводится сообщение каждый раз, когда вызывается задача `foo`, но только -если программы скомпилирова с профилем `dev`. - -``` rust -{{#include ../../../../examples/cfg.rs}} -``` - -``` console -$ cargo run --example cfg --release - -$ cargo run --example cfg -{{#include ../../../../ci/expected/cfg.run}} -``` - -## Запуск задач из ОЗУ - -Главной целью переноса описания программы на RTIC в атрибуты в -RTIC v0.4.x была возможность взаимодействия с другими атрибутами. -Напримерe, атрибут `link_section` можно применять к задачам, чтобы разместить -их в ОЗУ; это может улучшить производительность в некоторых случаях. - -> **ВАЖНО**: Обычно атрибуты `link_section`, `export_name` и `no_mangle` -> очень мощные, но их легко использовать неправильно. Неверное использование -> любого из этих атрибутов может вызвать неопределенное поведение; -> Вам следует всегда предпочитать использование безопасных, высокоуровневых -> атрибутов вместо них, таких как атрибуты `interrupt` и `exception` -> из `cortex-m-rt`. -> -> В особых функций, размещаемых в ОЗУ нет безопасной абстракции в `cortex-m-rt` -> v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе. - -[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100 - -В примере ниже показано как разместить высокоприоритетную задачу `bar` в ОЗУ. - -``` rust -{{#include ../../../../examples/ramfunc.rs}} -``` - -Запуск этой программы создаст ожидаемый вывод. - -``` console -$ cargo run --example ramfunc -{{#include ../../../../ci/expected/ramfunc.run}} -``` - -Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ -(`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`). - -``` console -$ cargo nm --example ramfunc --release | grep ' foo::' -{{#include ../../../../ci/expected/ramfunc.run.grep.foo}} -``` - -``` console -$ cargo nm --example ramfunc --release | grep ' bar::' -{{#include ../../../../ci/expected/ramfunc.run.grep.bar}} -``` - -## Обходной путь для быстрой передачи сообщений - -Передача сообщений всегда вызывает копирование от отправителя в -статическую переменную, а затем из статической переменной получателю. -Таким образом, при передаче большого буфера, например `[u8; 128]`, передача сообщения -вызывает два дорогих вызова `memcpy`. Чтобы минимизировать накладные расходы на передачу -сообщения, можно использовать обходной путь: вместо передачи буфера по значению, -можно передавать владеющий указатель на буфер. - -Можно использовать глобальный аллокатор, чтобы реализовать данный трюк (`alloc::Box`, -`alloc::Rc`, и т.п.), либо использовать статически аллоцируемый пул памяти, например [`heapless::Pool`]. - -[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html - -Здесь приведен пример использования `heapless::Pool` для "упаковки" буфера из 128 байт. - -``` rust -{{#include ../../../../examples/pool.rs}} -``` - -``` console -$ cargo run --example pool -{{#include ../../../../ci/expected/pool.run}} -``` - -## Инспектирование раскрываемого кода - -`#[rtic::app]` - это процедурный макрос, который создает код. -Если по какой-то причине вам нужно увидеть код, сгенерированный этим макросом, -у вас есть два пути: - -Вы можете изучить файл `rtic-expansion.rs` внутри папки `target`. Этот файл -содержит элемент `#[rtic::app]` в раскрытом виде (не всю вашу программу!) -из *последней сборки* (с помощью `cargo build` или `cargo check`) RTIC программы. -Раскрытый код не отформатирован по-умолчанию, но вы можете запустить `rustfmt` -на нем перед тем, как читать. - -``` console -$ cargo build --example foo - -$ rustfmt target/rtic-expansion.rs - -$ tail target/rtic-expansion.rs -``` - -``` rust -#[doc = r" Implementation details"] -mod app { - #[doc = r" Always include the device crate which contains the vector table"] - use lm3s6965 as _; - #[no_mangle] - unsafe extern "C" fn main() -> ! { - rtic::export::interrupt::disable(); - let mut core: rtic::export::Peripherals = core::mem::transmute(()); - core.SCB.scr.modify(|r| r | 1 << 1); - rtic::export::interrupt::enable(); - loop { - rtic::export::wfi() - } - } -} -``` - -Или, вы можете использовать подкоманду [`cargo-expand`]. Она раскроет -*все* макросы, включая атрибут `#[rtic::app]`, и модули в вашем крейте и -напечатает вывод в консоль. - -[`cargo-expand`]: https://crates.io/crates/cargo-expand - -``` console -$ # создаст такой же вывод, как выше -$ cargo expand --example smallest | tail -``` - -## Деструктуризация ресурса - -Если задача требует нескольких ресурсов, разбиение структуры ресурсов -может улучшить читабельность. Вот два примера того, как это можно сделать: - -``` rust -{{#include ../../../../examples/destructure.rs}} -``` diff --git a/book/ru/src/by-example/types-send-sync.md b/book/ru/src/by-example/types-send-sync.md deleted file mode 100644 index 755a379bb9..0000000000 --- a/book/ru/src/by-example/types-send-sync.md +++ /dev/null @@ -1,49 +0,0 @@ -# Типы, Send и Sync - -Каждая функция в модуле `app` принимает структуру `Context` в качесте первого параметра. -Все поля этих структур имеют предсказуемые, неанонимные типы, -поэтому вы можете написать обычные функции, принимающие их как аргументы. - -Справочник по API определяет как эти типы генерируются на основе входных данных. -Вы можете также сгенерировать документацию к вашему крейту программы (`cargo doc --bin `); -в документации вы найдете структуры `Context` (например `init::Context` и -`idle::Context`). - -Пример ниже показывает различные типы, сгенерированные атрибутом `app`. - -``` rust -{{#include ../../../../examples/types.rs}} -``` - -## `Send` - -[`Send`] - это маркерный трейт для "типов, которые можно передавать через границы -потоков", как это определено в `core`. В контексте RTIC трейт `Send` необходим -только там, где возможна передача значения между задачами, запускаемыми на -*разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений, -в разделяемых `static mut` ресурсах и при инициализации поздних ресурсов. - -[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html - -Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому вам не -стоит волноваться об этом. В настоящий момент все передаваемые типы в RTIC должны быть `Send`, но -это ограничение возможно будет ослаблено в будущем. - -## `Sync` - -Аналогично, [`Sync`] - маркерный трейт для "типов, на которые можно безопасно разделять между потоками", -как это определено в `core`. В контексте RTIC типаж `Sync` необходим только там, -где возможно для двух или более задач, запускаемых на разных приоритетах получить разделяемую ссылку (`&-`) на -ресурс. Это возникает только (`&-`) ресурсах с разделяемым доступом. - -[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html - -Атрибут `app` проверит, что `Sync` реализован, где необходимо, но важно знать, -где ограничение `Sync` не требуется: в (`&-`) ресурсах с разделяемым доступом, за которые -соперничают задачи с *одинаковым* приоритетом. - -В примере ниже показано, где можно использовать типы, не реализующие `Sync`. - -``` rust -{{#include ../../../../examples/not-sync.rs}} -``` diff --git a/book/ru/src/internals.md b/book/ru/src/internals.md deleted file mode 100644 index 48495b04e5..0000000000 --- a/book/ru/src/internals.md +++ /dev/null @@ -1,14 +0,0 @@ -# Под капотом - -**Этот раздел в настоящий момент находится в разработке, -он появится снова, когда будет завершен** - -Этот раздел описывает внутренности фреймворка RTIC на *высоком уровне*. -Низкоуровневые детали, такие как парсинг и генерация кода, выполняемые процедурным макросом -(`#[app]`) объясняться не будут. Внимание будет сосредоточено на анализе -спецификации пользователя и структурах данных, используемых на этапе выполнения. - -Мы настоятельно рекомендуем вам прочитать раздел о [конкуренции] в embedonomicon -перед тем, как погружаться в материал. - -[конкуренции]: https://github.com/rust-embedded/embedonomicon/pull/48 diff --git a/book/ru/src/internals/access.md b/book/ru/src/internals/access.md deleted file mode 100644 index ea073a4d3e..0000000000 --- a/book/ru/src/internals/access.md +++ /dev/null @@ -1,158 +0,0 @@ -# Контроль доступа - -Одна из основ RTIC - контроль доступа. Контроль того, какая часть программы -может получить доступ к какой статической переменной - инструмент обеспечения -безопасности памяти. - -Статические переменные используются для разделения состояний между обработчиками -прерываний, или между обработчиком прерывания и нижним контекстом выполнения, `main`. -В обычном Rust коде трудно обеспечить гранулированный контроль за тем, какие функции -могут получать доступ к статическим переменным, поскольку к статическим переменным -можно получить доступ из любой функции, находящейся в той же области видимости, -в которой они определены. Модули дают частичный контроль над доступом -к статическим переменным, но они недостаточно гибкие. - -Чтобы добиться полного контроля за тем, что задачи могут получить доступ -только к статическим переменным (ресурсам), которые им были указаны в RTIC атрибуте, -фреймворк RTIC производит трансформацию структуры кода. -Эта трансформация состоит из размещения ресурсов (статических переменных), определенных -пользователем *внутри* модуля, а пользовательского кода *вне* модуля. -Это делает невозможным обращение пользовательского кода к статическим переменным. - -Затем доступ к ресурсам предоставляется каждой задаче с помощью структуры `Resources`, -чьи поля соответствуют ресурсам, к которым получает доступ задача. -Есть лишь одна такая структура на задачу и структура `Resources` инициализируется -либо уникальной ссылкой (`&mut-`) на статическую переменную, либо с помощью прокси-ресурса (см. -раздел [критические секции](critical-sections.html)). - -Код ниже - пример разных трансформаций структуры кода, происходящих за сценой: - -``` rust -#[rtic::app(device = ..)] -mod app { - static mut X: u64: 0; - static mut Y: bool: 0; - - #[init(resources = [Y])] - fn init(c: init::Context) { - // .. пользовательский код .. - } - - #[interrupt(binds = UART0, resources = [X])] - fn foo(c: foo::Context) { - // .. пользовательский код .. - } - - #[interrupt(binds = UART1, resources = [X, Y])] - fn bar(c: bar::Context) { - // .. пользовательский код .. - } - - // .. -} -``` - -Фреймворк создает код, подобный этому: - -``` rust -fn init(c: init::Context) { - // .. пользовательский код .. -} - -fn foo(c: foo::Context) { - // .. пользовательский код .. -} - -fn bar(c: bar::Context) { - // .. пользовательский код .. -} - -// Публичное API -pub mod init { - pub struct Context<'a> { - pub resources: Resources<'a>, - // .. - } - - pub struct Resources<'a> { - pub Y: &'a mut bool, - } -} - -pub mod foo { - pub struct Context<'a> { - pub resources: Resources<'a>, - // .. - } - - pub struct Resources<'a> { - pub X: &'a mut u64, - } -} - -pub mod bar { - pub struct Context<'a> { - pub resources: Resources<'a>, - // .. - } - - pub struct Resources<'a> { - pub X: &'a mut u64, - pub Y: &'a mut bool, - } -} - -/// Детали реализации -mod app { - // все, что внутри этого модуля спрятано от пользовательского кода - - static mut X: u64 = 0; - static mut Y: bool = 0; - - // настоящая точка входа в программу - unsafe fn main() -> ! { - interrupt::disable(); - - // .. - - // вызов пользовательского кода; передача ссылок на статические переменные - init(init::Context { - resources: init::Resources { - X: &mut X, - }, - // .. - }); - - // .. - - interrupt::enable(); - - // .. - } - - // обработчик прерывания,с которым связан `foo` - #[no_mangle] - unsafe fn UART0() { - // вызов пользовательского кода; передача ссылок на статические переменные - foo(foo::Context { - resources: foo::Resources { - X: &mut X, - }, - // .. - }); - } - - // обработчик прерывания,с которым связан `bar` - #[no_mangle] - unsafe fn UART1() { - // вызов пользовательского кода; передача ссылок на статические переменные - bar(bar::Context { - resources: bar::Resources { - X: &mut X, - Y: &mut Y, - }, - // .. - }); - } -} -``` diff --git a/book/ru/src/internals/ceilings.md b/book/ru/src/internals/ceilings.md deleted file mode 100644 index df9901a2b4..0000000000 --- a/book/ru/src/internals/ceilings.md +++ /dev/null @@ -1,92 +0,0 @@ -# Анализ приоритетов - -*Поиск максимального приоритета* ресурса (*ceiling*) - поиск динамического -приоритета, который любая задача должна иметь, чтобы безопасно работать с -памятью ресурсов. Анализ приоритетов - относительно прост, -но критичен для безопасности памяти RTIC программ. - -Для расчета максимального приоритета ресурса мы должны сначала составить -список задач, имеющих доступ к ресурсу -- так как фреймворк RTIC -форсирует контроль доступа к ресурсам на этапе компиляции, он -также имеет доступ к этой информации на этапе компиляции. -Максимальный приоритет ресурса - просто наивысший логический приоритет -среди этих задач. - -`init` и `idle` не настоящие задачи, но у них есть доступ к ресурсам, -поэтому они должны учитываться при анализе приоритетов. -`idle` учитывается как задача, имеющая логический приоритет `0`, -в то время как `init` полностью исключается из анализа -- -причина этому в том, что `init` никогда не использует (не нуждается) критические -секции для доступа к статическим переменным. - -В предыдущем разделе мы показывали, что разделяемые ресусы -могут быть представлены уникальными ссылками (`&mut-`) или скрываться за -прокси в зависимости от того, имеет ли задача к ним доступ. -Какой из вариантов представляется задаче зависит от приоритета задачи и -максимального приоритета ресурса. -Если приоритет задачи такой же, как максимальный приоритет ресурса, тогда -задача получает уникальную ссылку (`&mut-`) на память ресурса, -в противном случае задача получает прокси -- это также касается `idle`. -`init` особеннвй: он всегда получает уникальные ссылки (`&mut-`) на ресурсы. - -Пример для иллюстрации анализа приоритетов: - -``` rust -#[rtic::app(device = ..)] -mod app { - struct Resources { - // доступен из `foo` (prio = 1) и `bar` (prio = 2) - // -> CEILING = 2 - #[init(0)] - x: u64, - - // доступен из `idle` (prio = 0) - // -> CEILING = 0 - #[init(0)] - y: u64, - } - - #[init(resources = [x])] - fn init(c: init::Context) { - // уникальная ссылка, потому что это `init` - let x: &mut u64 = c.resources.x; - - // уникальная ссылка, потому что это `init` - let y: &mut u64 = c.resources.y; - - // .. - } - - // PRIORITY = 0 - #[idle(resources = [y])] - fn idle(c: idle::Context) -> ! { - // уникальная ссылка, потому что - // приоритет (0) == максимальному приоритету ресурса (0) - let y: &'static mut u64 = c.resources.y; - - loop { - // .. - } - } - - #[interrupt(binds = UART0, priority = 1, resources = [x])] - fn foo(c: foo::Context) { - // прокси-ресурс, потому что - // приоритет задач (1) < максимальному приоритету ресурса (2) - let x: resources::x = c.resources.x; - - // .. - } - - #[interrupt(binds = UART1, priority = 2, resources = [x])] - fn bar(c: foo::Context) { - // уникальная ссылка, потому что - // приоритет задачи (2) == максимальному приоритету ресурса (2) - let x: &mut u64 = c.resources.x; - - // .. - } - - // .. -} -``` diff --git a/book/ru/src/internals/critical-sections.md b/book/ru/src/internals/critical-sections.md deleted file mode 100644 index e4c3d0abb1..0000000000 --- a/book/ru/src/internals/critical-sections.md +++ /dev/null @@ -1,521 +0,0 @@ -# Критические секции - -Когда ресурсы (статические переменные) разделяются между двумя или более задачами, -которые выполняются с разными приоритетами, некая форма запрета изменений -необходима, чтобы изменять память без гонки данных. В RTIC мы используем -основанные на приоритетах критические секции, чтобы гарантировать запрет изменений -(см. [Протокол немедленного максимального приоритета][icpp]). - -[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol - -Критическия секция состоит во временном увеличении *динамического* приоритета задачи. -Пока задача находится в критической секции, все другие задачи, которые могут -послать запрос переменной *не могут запуститься*. - -Насколько большим должен быть динамический приориткт, чтобы гарантировать запрет изменений -определенного ресурса? [Анализ приоритетов](ceilings.html) отвечает на этот вопрос -и будет обсужден в следующем разделе. В этом разделе мы сфокусируемся -на реализации критической секции. - -## Прокси-ресурсы - -Для упрощения, давайте взглянем на ресурс, разделяемый двумя задачами, -запускаемыми с разными приоритетами. Очевидно, что одна задача может вытеснить -другую; чтобы предотвратить гонку данных задача с *низким приоритетом* должна -использовать критическую секцию, когда необходимо изменять разделяемую память. -С другой стороны, высокоприоритетная задача может напрямую изменять -разделяемую память, поскольку не может быть вытеснена низкоприоритетной задачей. -Чтобы заставить использовать критическую секцию на задаче с низким приоритетом, -мы предоставляем *прокси-ресурсы*, в которых мы отдаем уникальную ссылку -(`&mut-`) высокоприоритетной задаче. - -Пример ниже показывает разные типы, передаваемые каждой задаче: - -``` rust -#[rtic::app(device = ..)] -mut app { - struct Resources { - #[init(0)] - x: u64, - } - - #[interrupt(binds = UART0, priority = 1, resources = [x])] - fn foo(c: foo::Context) { - // прокси-ресурс - let mut x: resources::x = c.resources.x; - - x.lock(|x: &mut u64| { - // критическая секция - *x += 1 - }); - } - - #[interrupt(binds = UART1, priority = 2, resources = [x])] - fn bar(c: bar::Context) { - let mut x: &mut u64 = c.resources.x; - - *x += 1; - } - - // .. -} -``` - -Теперь давайте посмотрим. как эти типы создаются фреймворком. - -``` rust -fn foo(c: foo::Context) { - // .. пользовательский код .. -} - -fn bar(c: bar::Context) { - // .. пользовательский код .. -} - -pub mod resources { - pub struct x { - // .. - } -} - -pub mod foo { - pub struct Resources { - pub x: resources::x, - } - - pub struct Context { - pub resources: Resources, - // .. - } -} - -pub mod bar { - pub struct Resources<'a> { - pub x: &'a mut u64, - } - - pub struct Context { - pub resources: Resources, - // .. - } -} - -mod app { - static mut x: u64 = 0; - - impl rtic::Mutex for resources::x { - type T = u64; - - fn lock(&mut self, f: impl FnOnce(&mut u64) -> R) -> R { - // мы рассмотрим это детально позднее - } - } - - #[no_mangle] - unsafe fn UART0() { - foo(foo::Context { - resources: foo::Resources { - x: resources::x::new(/* .. */), - }, - // .. - }) - } - - #[no_mangle] - unsafe fn UART1() { - bar(bar::Context { - resources: bar::Resources { - x: &mut x, - }, - // .. - }) - } -} -``` - -## `lock` - -Теперь давайте рассмотрим непосредственно критическую секцию. В этом примере мы должны -увеличить динамический приоритет минимум до `2`, чтобы избежать гонки данных. -В архитектуре Cortex-M динамический приоритет можно изменить записью в регистр `BASEPRI`. - -Семантика регистра `BASEPRI` такова: - -- Запись `0` в `BASEPRI` отключает его функциональность. -- Запись ненулевого значения в `BASEPRI` изменяет уровень приоритета, требуемого для - вытеснения прерывания. Однако, это имеет эффект, только когда записываемое значение - *меньше*, чем уровень приоритета текущего контекста выполнения, но обращаем внимание, что - более низкий уровень аппаратного приоритета означает более высокий логический приоритет - -Таким образом, динамический приоритет в любой момент времени может быть рассчитан как - -``` rust -dynamic_priority = max(hw2logical(BASEPRI), hw2logical(static_priority)) -``` - -Где `static_priority` - приоритет, запрограммированный в NVIC для текущего прерывания, -или логический `0`, когда текущий контекств - это `idle`. - -В этом конкретном примере мы можем реализовать критическую секцию так: - -> **ПРИМЕЧАНИЕ:** это упрощенная реализация - -``` rust -impl rtic::Mutex for resources::x { - type T = u64; - - fn lock(&mut self, f: F) -> R - where - F: FnOnce(&mut u64) -> R, - { - unsafe { - // начать критическую секцию: увеличить динамический приоритет до `2` - asm!("msr BASEPRI, 192" : : : "memory" : "volatile"); - - // запустить пользовательский код в критической секции - let r = f(&mut x); - - // окончить критическую секцию: восстановить динамический приоритет до статического значения (`1`) - asm!("msr BASEPRI, 0" : : : "memory" : "volatile"); - - r - } - } -} -``` - -В данном случае важно указать `"memory"` в блоке `asm!`. -Это не даст компилятору менять местами операции вокруг него. -Это важно, поскольку доступ к переменной `x` вне критической секции привело бы -к гонке данных. - -Важно отметить, что сигнатура метода `lock` препятствет его вложенным вызовам. -Это необходимо для безопасности памяти, так как вложенные вызовы привели бы -к созданию множественных уникальных ссылок (`&mut-`) на `x`, ломая правила заимствования Rust. -Смотреть ниже: - -``` rust -#[interrupt(binds = UART0, priority = 1, resources = [x])] -fn foo(c: foo::Context) { - // resource proxy - let mut res: resources::x = c.resources.x; - - res.lock(|x: &mut u64| { - res.lock(|alias: &mut u64| { - //~^ ошибка: `res` уже был заимствован уникально (`&mut-`) - // .. - }); - }); -} -``` - -## Вложенность - -Вложенные вызовы `lock` на *том же* ресурсе должны отклоняться компилятором -для безопасности памяти, однако вложенные вызовы `lock` на *разных* ресурсах - -нормальная операция. В этом случае мы хотим убедиться, что вложенные критические секции -никогда не приведут к понижению динамического приоритета, так как это плохо, -и мы хотим оптимизировать несколько записей в регистр `BASEPRI` и compiler fences. -Чтобы справиться с этим, мы проследим динамический приоритет задачи, с помощью стековой -переменной и используем ее, чтобы решить, записывать `BASEPRI` или нет. -На практике, стековая переменная будет соптимизирована компилятором, но все еще -будет предоставлять информацию компилятору. - -Рассмотрим такую программу: - -``` rust -#[rtic::app(device = ..)] -mod app { - struct Resources { - #[init(0)] - x: u64, - #[init(0)] - y: u64, - } - - #[init] - fn init() { - rtic::pend(Interrupt::UART0); - } - - #[interrupt(binds = UART0, priority = 1, resources = [x, y])] - fn foo(c: foo::Context) { - let mut x = c.resources.x; - let mut y = c.resources.y; - - y.lock(|y| { - *y += 1; - - *x.lock(|x| { - x += 1; - }); - - *y += 1; - }); - - // середина - - x.lock(|x| { - *x += 1; - - y.lock(|y| { - *y += 1; - }); - - *x += 1; - }) - } - - #[interrupt(binds = UART1, priority = 2, resources = [x])] - fn bar(c: foo::Context) { - // .. - } - - #[interrupt(binds = UART2, priority = 3, resources = [y])] - fn baz(c: foo::Context) { - // .. - } - - // .. -} -``` - -Код, сгенерированный фреймворком, выглядит так: - -``` rust -// опущено: пользовательский код - -pub mod resources { - pub struct x<'a> { - priority: &'a Cell, - } - - impl<'a> x<'a> { - pub unsafe fn new(priority: &'a Cell) -> Self { - x { priority } - } - - pub unsafe fn priority(&self) -> &Cell { - self.priority - } - } - - // repeat for `y` -} - -pub mod foo { - pub struct Context { - pub resources: Resources, - // .. - } - - pub struct Resources<'a> { - pub x: resources::x<'a>, - pub y: resources::y<'a>, - } -} - -mod app { - use cortex_m::register::basepri; - - #[no_mangle] - unsafe fn UART1() { - // статический приоритет прерывания (определено пользователем) - const PRIORITY: u8 = 2; - - // сделать снимок BASEPRI - let initial = basepri::read(); - - let priority = Cell::new(PRIORITY); - bar(bar::Context { - resources: bar::Resources::new(&priority), - // .. - }); - - // вернуть BASEPRI значение из снимка, сделанного ранее - basepri::write(initial); // то же, что и `asm!` блок, виденный ранее - } - - // так же для `UART0` / `foo` и `UART2` / `baz` - - impl<'a> rtic::Mutex for resources::x<'a> { - type T = u64; - - fn lock(&mut self, f: impl FnOnce(&mut u64) -> R) -> R { - unsafe { - // определение максимального приоритет ресурса - const CEILING: u8 = 2; - - let current = self.priority().get(); - if current < CEILING { - // увеличить динамический приоритет - self.priority().set(CEILING); - basepri::write(logical2hw(CEILING)); - - let r = f(&mut y); - - // восстановить динамический приоритет - basepri::write(logical2hw(current)); - self.priority().set(current); - - r - } else { - // динамический приоритет достаточно высок - f(&mut y) - } - } - } - } - - // повторить для ресурса `y` -} -``` - -Наконец, компилятор оптимизирует функцию `foo` во что-то наподобие такого: - -``` rust -fn foo(c: foo::Context) { - // ПРИМЕЧАНИЕ: BASEPRI содержит значение `0` (значение сброса) в этот момент - - // увеличить динамический приоритет до `3` - unsafe { basepri::write(160) } - - // две операции над `y` объединены в одну - y += 2; - - // BASEPRI не изменяется для доступа к `x`, потому что динамический приоритет достаточно высок - x += 1; - - // уменьшить (восстановить) динамический приоритет до `1` - unsafe { basepri::write(224) } - - // средина - - // увеличить динамический приоритет до `2` - unsafe { basepri::write(192) } - - x += 1; - - // увеличить динамический приоритет до `3` - unsafe { basepri::write(160) } - - y += 1; - - // уменьшить (восстановить) динамический приоритет до `2` - unsafe { basepri::write(192) } - - // ПРИМЕЧАНИЕ: было вы правильно объединить эту операцию над `x` с предыдущей, но - // compiler fences грубые и предотвращают оптимизацию - x += 1; - - // уменьшить (восстановить) динамический приоритет до `1` - unsafe { basepri::write(224) } - - // ПРИМЕЧАНИЕ: BASEPRI содержит значение `224` в этот момент - // обработчик UART0 восстановит значение `0` перед завершением -} -``` - -## Инвариант BASEPRI - -Инвариант, который фреймворк RTIC должен сохранять в том, что значение -BASEPRI в начале обработчика *прерывания* должно быть таким же, как и при выходе -из него. BASEPRI может изменяться в процессе выполнения обработчика прерывания, -но но выполнения обработчика прерывания в начале и конце не должно вызвать -наблюдаемого изменения BASEPRI. - -Этот инвариант нужен, чтобы избежать уеличения динамического приоритета до значений, -при которых обработчик не сможет быть вытеснен. Лучше всего это видно на следующем примере: - -``` rust -#[rtic::app(device = ..)] -mod app { - struct Resources { - #[init(0)] - x: u64, - } - - #[init] - fn init() { - // `foo` запустится сразу после завершения `init` - rtic::pend(Interrupt::UART0); - } - - #[task(binds = UART0, priority = 1)] - fn foo() { - // BASEPRI равен `0` в этот момент; динамический приоритет равен `1` - - // `bar` вытеснит `foo` в этот момент - rtic::pend(Interrupt::UART1); - - // BASEPRI равен `192` в этот момент (из-за бага); динамический приоритет равен `2` - // эта функция возвращается в `idle` - } - - #[task(binds = UART1, priority = 2, resources = [x])] - fn bar() { - // BASEPRI равен `0` (динамический приоритет = 2) - - x.lock(|x| { - // BASEPRI увеличен до `160` (динамический приоритет = 3) - - // .. - }); - - // BASEPRI восстановлен до `192` (динамический приоритет = 2) - } - - #[idle] - fn idle() -> ! { - // BASEPRI равен `192` (из-за бага); динамический приоритет = 2 - - // это не оказывает эффекта, из-за значени BASEPRI - // задача `foo` не будет выполнена снова никогда - rtic::pend(Interrupt::UART0); - - loop { - // .. - } - } - - #[task(binds = UART2, priority = 3, resources = [x])] - fn baz() { - // .. - } - -} -``` - -ВАЖНО: давайте например мы *забудем* восстановить `BASEPRI` в `UART1` -- из-за -какого нибудь бага в генераторе кода RTIC. - -``` rust -// код, сгенерированный RTIC - -mod app { - // .. - - #[no_mangle] - unsafe fn UART1() { - // статический приоритет этого прерывания (определен пользователем) - const PRIORITY: u8 = 2; - - // сделать снимок BASEPRI - let initial = basepri::read(); - - let priority = Cell::new(PRIORITY); - bar(bar::Context { - resources: bar::Resources::new(&priority), - // .. - }); - - // БАГ: ЗАБЫЛИ восстановить BASEPRI на значение из снимка - basepri::write(initial); - } -} -``` - -В результате, `idle` запустится на динамическом приоритете `2` и на самом деле -система больше никогда не перейдет на динамический приоритет ниже `2`. -Это не компромис для безопасности памяти программы, а влияет на диспетчеризацию задач: -в этом конкретном случае задачи с приоритетом `1` никогда не получат шанс на запуск. diff --git a/book/ru/src/internals/interrupt-configuration.md b/book/ru/src/internals/interrupt-configuration.md deleted file mode 100644 index 5631b374a3..0000000000 --- a/book/ru/src/internals/interrupt-configuration.md +++ /dev/null @@ -1,72 +0,0 @@ -# Настройка прерываний - -Прерывания - это основа работы программ на RTIC. Правильно настроить приоритеты -прерываний и убедиться, что они не изменяются во время выполнения обязательно -для безопасной работы программы. - -Фреймворк RTIC представляет приоритеты прерываний, как нечто, что должно быть определено -на этапе компиляции. Однако, статическая настройка должна быть зашита в соответствующие регистры -в процессе инициализации программы. Настройка прерываний происходит до запуска функции `init`. - -Этот пример дает представление о коде, запускаемом фреймворком RTIC: - -``` rust -#[rtic::app(device = lm3s6965)] -mod app { - #[init] - fn init(c: init::Context) { - // .. пользовательский код .. - } - - #[idle] - fn idle(c: idle::Context) -> ! { - // .. пользовательский код .. - } - - #[interrupt(binds = UART0, priority = 2)] - fn foo(c: foo::Context) { - // .. пользовательский код .. - } -} -``` - -Фреймворк генерирует точку входа в программу, которая выглядит примерно так: - -``` rust -// настоящая точку входа в программу -#[no_mangle] -unsafe fn main() -> ! { - // преобразует логические приоритеты в аппаратные / NVIC приоритеты - fn logical2hw(priority: u8) -> u8 { - use lm3s6965::NVIC_PRIO_BITS; - - // NVIC кодирует приоритеты верхними битами - // большие значения обозначают меньший приоритет - ((1 << NVIC_PRIORITY_BITS) - priority) << (8 - NVIC_PRIO_BITS) - } - - cortex_m::interrupt::disable(); - - let mut core = cortex_m::Peripheral::steal(); - - core.NVIC.enable(Interrupt::UART0); - - // значение, определенное пользователем - let uart0_prio = 2; - - // проверка на этапе компиляции, что определенный приоритет входит в поддерживаемый диапазон - let _ = [(); (1 << NVIC_PRIORITY_BITS) - (uart0_prio as usize)]; - - core.NVIC.set_priority(Interrupt::UART0, logical2hw(uart0_prio)); - - // вызов пользовательского кода - init(/* .. */); - - // .. - - cortex_m::interrupt::enable(); - - // вызов пользовательского кода - idle(/* .. */) -} -``` diff --git a/book/ru/src/internals/late-resources.md b/book/ru/src/internals/late-resources.md deleted file mode 100644 index 146c438d70..0000000000 --- a/book/ru/src/internals/late-resources.md +++ /dev/null @@ -1,113 +0,0 @@ -# Поздние ресурсы - -Некоторые ресурсы инициализируются во время выполнения после завершения функции `init`. -Важно то, что ресурсы (статические переменные) полностью инициализируются -до того, как задачи смогут запуститься, вот почему они должны быть инициализированы -пока прерывания отключены. - -Ниже показан пример кода, генерируемого фреймворком для инициализации позних ресурсов. - -``` rust -#[rtic::app(device = ..)] -mod app { - struct Resources { - x: Thing, - } - - #[init] - fn init() -> init::LateResources { - // .. - - init::LateResources { - x: Thing::new(..), - } - } - - #[task(binds = UART0, resources = [x])] - fn foo(c: foo::Context) { - let x: &mut Thing = c.resources.x; - - x.frob(); - - // .. - } - - // .. -} -``` - -Код, генерируемы фреймворком выглядит примерно так: - -``` rust -fn init(c: init::Context) -> init::LateResources { - // .. пользовательский код .. -} - -fn foo(c: foo::Context) { - // .. пользовательский код .. -} - -// Public API -pub mod init { - pub struct LateResources { - pub x: Thing, - } - - // .. -} - -pub mod foo { - pub struct Resources<'a> { - pub x: &'a mut Thing, - } - - pub struct Context<'a> { - pub resources: Resources<'a>, - // .. - } -} - -/// Детали реализации -mod app { - // неинициализированная статическая переменная - static mut x: MaybeUninit = MaybeUninit::uninit(); - - #[no_mangle] - unsafe fn main() -> ! { - cortex_m::interrupt::disable(); - - // .. - - let late = init(..); - - // инициализация поздних ресурсов - x.as_mut_ptr().write(late.x); - - cortex_m::interrupt::enable(); //~ compiler fence - - // исключения, прерывания и задачи могут вытеснить `main` в этой точке - - idle(..) - } - - #[no_mangle] - unsafe fn UART0() { - foo(foo::Context { - resources: foo::Resources { - // `x` уже инициализирована к этому моменту - x: &mut *x.as_mut_ptr(), - }, - // .. - }) - } -} -``` - -Важная деталь здесь то, что `interrupt::enable` ведет себя как *барьер компиляции*, который не дает компилятору переставить запись в `X` *после* -`interrupt::enable`. Если бы компилятор мог делать такие перестановки появились -бы гонки данных между этой записью и любой операцией `foo`, взаимодействующей с `X`. - -Архитектурам с более сложным конвейером инструкций нужен барьер памяти -(`atomic::fence`) вместо compiler fence для полной очистки операции записи -перед включением прерываний. Архитектура ARM Cortex-M не нуждается в барьере памяти -в одноядерном контексте. diff --git a/book/ru/src/internals/non-reentrancy.md b/book/ru/src/internals/non-reentrancy.md deleted file mode 100644 index 98eb00fb3d..0000000000 --- a/book/ru/src/internals/non-reentrancy.md +++ /dev/null @@ -1,79 +0,0 @@ -# Нереентерабельность - -В RTIC задачи-обработчики *не* могут использоваться повторно. Переиспользование задачи-обработчика -может сломать правила заимствования Rust и привести к *неопределенному поведению*. -Задача-обработчик теоретически может быть переиспользована одним из двух способов: программно или аппаратно. - -## Программно - -Чтобы переиспользовать задачу-обработчик программно, назначенный ей обработчик прерывания -должен быть вызван с помощью FFI (смотрите пример ниже). FFI требует `unsafe` код, -что уменьшает желание конечных пользователей вызывать обработчик прерывания. - -``` rust -#[rtic::app(device = ..)] -mod app { - #[init] - fn init(c: init::Context) { .. } - - #[interrupt(binds = UART0)] - fn foo(c: foo::Context) { - static mut X: u64 = 0; - - let x: &mut u64 = X; - - // .. - - //~ `bar` может вытеснить `foo` в этом месте - - // .. - } - - #[interrupt(binds = UART1, priority = 2)] - fn bar(c: foo::Context) { - extern "C" { - fn UART0(); - } - - // этот обработчик прерывания вызовет задачу-обработчик `foo`, что сломает - // ссылку на статическую переменную `X` - unsafe { UART0() } - } -} -``` - -Фреймворк RTIC должен сгенерировать код обработчика прерывания, который вызывает -определенные пользователем задачи-обработчики. Мы аккуратны в том, чтобы обеспечить -невозможность вызова этих обработчиков из пользовательского кода. - -Пример выше раскрывается в: - -``` rust -fn foo(c: foo::Context) { - // .. пользовательский код .. -} - -fn bar(c: bar::Context) { - // .. пользовательский код .. -} - -mod app { - // все в этом блоке невидимо для пользовательского кода - - #[no_mangle] - unsafe fn USART0() { - foo(..); - } - - #[no_mangle] - unsafe fn USART1() { - bar(..); - } -} -``` - -## Аппаратно - -Обработчик прерывания также может быть вызван без программного вмешательства. -Это может произойти, если один обработчик будет назначен двум или более прерываниям -в векторе прерываний, но синтаксиса для такого рода функциональности в RTIC нет. diff --git a/book/ru/src/internals/tasks.md b/book/ru/src/internals/tasks.md deleted file mode 100644 index 01380ba907..0000000000 --- a/book/ru/src/internals/tasks.md +++ /dev/null @@ -1,399 +0,0 @@ -# Программные задачи - -RTIC поддерживает программные и аппаратные задачи. Каждая аппаратная задача -назначается на отдельный обработчик прерывания. С другой стороны, несколько -программных задач могут управляться одним обработчиком прерывания -- -это сделано, чтобы минимизировать количество обработчиков прерывания, -используемых фреймворком. - -Фреймворк группирует задачи, для которых вызывается `spawn` по уровню приоритета, -и генерирует один *диспетчер задачи* для каждого уровня приоритета. -Каждый диспетчер запускается на отдельном обработчике прерывания, -а приоритет этого обработчика прерывания устанавливается так, чтобы соответствовать -уровню приоритета задач, управляемых диспетчером. - -Каждый диспетчер задач хранит *очередь* задач, *готовых* к выполнению; -эта очередь называется *очередью готовности*. Вызов программной задачи состоит -из добавления записи в очередь и вызова прерывания, который запускает соответствующий -диспетчер задач. Каждая запись в эту очередь содержит метку (`enum`), -которая идентифицирует задачу, которую необходимо выполнить и *указатель* -на сообщение, передаваемое задаче. - -Очередь готовности - неблокируемая очередь типа SPSC (один производитель - один потребитель). -Диспетчер задач владеет конечным потребителем в очереди; конечным производителем -считается ресурс, за который соперничают задачи, которые могут вызывать (`spawn`) другие задачи. - -## Дисметчер задач - -Давайте сначала глянем на код, генерируемый фреймворком для диспетчеризации задач. -Рассмотрим пример: - -``` rust -#[rtic::app(device = ..)] -mod app { - // .. - - #[interrupt(binds = UART0, priority = 2, spawn = [bar, baz])] - fn foo(c: foo::Context) { - foo.spawn.bar().ok(); - - foo.spawn.baz(42).ok(); - } - - #[task(capacity = 2, priority = 1)] - fn bar(c: bar::Context) { - // .. - } - - #[task(capacity = 2, priority = 1, resources = [X])] - fn baz(c: baz::Context, input: i32) { - // .. - } - - extern "C" { - fn UART1(); - } -} -``` - -Фреймворк создает следующий диспетчер задач, состоящий из обработчика прерывания и очереди готовности: - -``` rust -fn bar(c: bar::Context) { - // .. пользовательский код .. -} - -mod app { - use heapless::spsc::Queue; - use cortex_m::register::basepri; - - struct Ready { - task: T, - // .. - } - - /// вызываемые (`spawn`) задачи, выполняющиеся с уровнем приоритета `1` - enum T1 { - bar, - baz, - } - - // очередь готовности диспетчера задач - // `5-1=4` - представляет собой емкость этой очереди - static mut RQ1: Queue, 5> = Queue::new(); - - // обработчик прерывания, выбранный для диспетчеризации задач с приоритетом `1` - #[no_mangle] - unsafe UART1() { - // приоритет данного обработчика прерывания - const PRIORITY: u8 = 1; - - let snapshot = basepri::read(); - - while let Some(ready) = RQ1.split().1.dequeue() { - match ready.task { - T1::bar => { - // **ПРИМЕЧАНИЕ** упрощенная реализация - - // используется для отслеживания динамического приоритета - let priority = Cell::new(PRIORITY); - - // вызов пользовательского кода - bar(bar::Context::new(&priority)); - } - - T1::baz => { - // рассмотрим `baz` позднее - } - } - } - - // инвариант BASEPRI - basepri::write(snapshot); - } -} -``` - -## Вызов задачи - -Интерфейс `spawn` предоставлен пользователю как методы структурв `Spawn`. -Для каждой задачи существует своя структура `Spawn`. - -Код `Spawn`, генерируемый фреймворком для предыдущего примера выглядит так: - -``` rust -mod foo { - // .. - - 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` можно поставить в очередь - // `3-1=2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь - // эта очередь заполняется фреймворком до того, как запустится `init` - static mut bar_FQ: Queue<(), 3> = 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`. - -``` rust -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) - } - } - } - } - } -} -``` - -А теперь давайте взглянем на настоящую реализацию диспетчера задач: - -``` 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 => { - // ПРИМЕЧАНИЕ: `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` - } - - } - } - - // инвариант BASEPRI - basepri::write(snapshot); - } -} -``` - -`INPUTS` плюс `FQ`, список свободной памяти равняется эффективному пулу памяти. -Однако, вместо того *список свободной памяти* (связный список), чтобы отслеживать -пустые ячейки в буфере `INPUTS`, мы используем SPSC очередь; это позволяет нам -уменьшить количество критических секций. -На самом деле благодаря этому выбору код диспетчера задач неблокируемый. - -## Вместительность очереди - -Фреймворк RTIC использует несколько очередей, такие как очереди готовности и -списки свободной памяти. Когда список свободной памяти пуст, попытка выызова -(`spawn`) задачи приводит к ошибке; это условие проверяется во время выполнения. -Не все операции, произвожимые фреймворком с этими очередями проверяют их -пустоту / наличие места. Например, возвращение ячейки списка свободной памяти -(см. диспетчер задач) не проверяется, поскольку есть фиксированное количество -таких ячеек циркулирующих в системе, равное вместительности списка свободной памяти. -Аналогично, добавление записи в очередь готовности (см. `Spawn`) не проверяется, -потому что вместительность очереди выбрана фреймворком. - -Пользователи могут задавать вместительность программных задач; -эта вместительность - максимальное количество сообщений, которые можно -послать указанной задаче от задачи более высоким приоритетом до того, -как `spawn` вернет ошибку. Эта определяемая пользователем иместительность - -размер списка свободной памяти задачи (например `foo_FQ`), а также размер массива, -содержащего входные данные для задачи (например `foo_INPUTS`). - -Вместительность очереди готовности (например `RQ1`) вычисляется как *сумма* -вместительностей всех задач, управляемх диспетчером; эта сумма является также -количеством сообщений, которые очередь может хранить в худшем сценарии, когда -все возможные сообщения были посланы до того, как диспетчер задач получает шанс -на запуск. По этой причине получение ячейки списка свободной памяти при любой -операции `spawn` приводит к тому, что очередь готовности еще не заполнена, -поэтому вставка записи в список готовности может пропустить проверку "полна ли очередь?". - -В нашем запущенном примере задача `bar` не принимает входных данных, поэтому -мы можем пропустить проверку как `bar_INPUTS`, так и `bar_FQ` и позволить -пользователю посылать неограниченное число сообщений задаче, но если бы мы сделали это, -было бы невозможно превысить вместительность для `RQ1`, что позволяет нам -пропустить проверку "полна ли очередь?" при вызове задачи `baz`. -В разделе о [очереди таймера](timer-queue.html) мы увидим как -список свободной памяти используется для задач без входных данных. - -## Анализ приоритетов - -Очереди, использемые внутри интерфейса `spawn`, рассматриваются как обычные ресурсы -и для них тоже работает анализ приоритетов. Важно заметить, что это SPSC очереди, -и только один из конечных элементов становится ресурсом; другим конечным элементом -владеет диспетчер задач. - -Рассмотрим следующий пример: - -``` rust -#[rtic::app(device = ..)] -mod app { - #[idle(spawn = [foo, bar])] - fn idle(c: idle::Context) -> ! { - // .. - } - - #[task] - fn foo(c: foo::Context) { - // .. - } - - #[task] - fn bar(c: bar::Context) { - // .. - } - - #[task(priority = 2, spawn = [foo])] - fn baz(c: baz::Context) { - // .. - } - - #[task(priority = 3, spawn = [bar])] - fn quux(c: quux::Context) { - // .. - } -} -``` - -Вот как будет проходить анализ приоритетов: - -- `idle` (prio = 0) и `baz` (prio = 2) соревнуются за конечный потребитель - `foo_FQ`; это приводит к максимальному приоритету `2`. - -- `idle` (prio = 0) и `quux` (prio = 3) соревнуются за конечный потребитель - `bar_FQ`; это приводит к максимальному приоритету `3`. - -- `idle` (prio = 0), `baz` (prio = 2) и `quux` (prio = 3) соревнуются за - конечный производитель `RQ1`; это приводит к максимальному приоритету `3` diff --git a/book/ru/src/internals/timer-queue.md b/book/ru/src/internals/timer-queue.md deleted file mode 100644 index 9f2dc37e00..0000000000 --- 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, - } - - impl<'a> Schedule<'a> { - // `unsafe` и спрятано, потому что мы не хотим, чтобы пользовать сюда вмешивался - #[doc(hidden)] - pub unsafe fn priority(&self) -> &Cell { - self.priority - } - } -} - -mod app { - type Instant = ::Instant; - - // все задачи, которые могут быть запланированы (`schedule`) - enum T { - foo, - } - - struct NotReady { - index: u8, - instant: Instant, - task: T, - } - - // Очередь таймера - двоичная куча (min-heap) задач `NotReady` - static mut TQ: TimerQueue = ..; - const TQ_CEILING: u8 = 1; - - static mut foo_FQ: Queue = Queue::new(); - const foo_FQ_CEILING: u8 = 1; - - static mut foo_INPUTS: [MaybeUninit; 2] = - [MaybeUninit::uninit(), MaybeUninit::uninit()]; - - static mut foo_INSTANTS: [MaybeUninit; 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, - // ADDED - instant: Instant, - } - - impl<'a> Spawn<'a> { - pub unsafe fn priority(&self) -> &Cell { - &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) - } - } - } - } - } -} -``` diff --git a/book/ru/src/migration.md b/book/ru/src/migration.md deleted file mode 100644 index b7f2fa1175..0000000000 --- a/book/ru/src/migration.md +++ /dev/null @@ -1,4 +0,0 @@ -# Инструкции по миграции - -В этом разделе описывается как мигрировать между различными версиями RTIC. -Можно также использовать для сравнения версий. diff --git a/book/ru/src/migration/migration_rtic.md b/book/ru/src/migration/migration_rtic.md deleted file mode 100644 index 28813fe291..0000000000 --- a/book/ru/src/migration/migration_rtic.md +++ /dev/null @@ -1,48 +0,0 @@ -# Миграция с RTFM на RTIC - -В этом разделе описано, как обновить приложение, написанное на RTFM v0.5.x на RTIC той же версии. -Это необходимо из-за переименования фреймворка в соответствии с [RFC #33]. - -**Примечание:** Между RTFM v0.5.3 и RTIC v0.5.3 нет разниц в коде, это исключительно изменение имен. - -[RFC #33]: https://github.com/rtic-rs/rfcs/pull/33 - -## `Cargo.toml` - -Во-первых, зависимость `cortex-m-rtfm` должна быть изменена на `cortex-m-rtic`. - -``` toml -[dependencies] -# измените это -cortex-m-rtfm = "0.5.3" - -# на это -cortex-m-rtic = "0.5.3" -``` - -## Изменения в коде - -Единственное изменение в коде, которое нужно сделать - поменять все ссылки на `rtfm`, -чтобы они указывали на `rtic`: - -``` rust -// -// Измените это -// - -#[rtfm::app(/* .. */, monotonic = rtfm::cyccnt::CYCCNT)] -const APP: () = { - // ... - -}; - -// -// На это -// - -#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)] -const APP: () = { - // ... - -}; -``` diff --git a/book/ru/src/migration/migration_v4.md b/book/ru/src/migration/migration_v4.md deleted file mode 100644 index 0ff8039600..0000000000 --- a/book/ru/src/migration/migration_v4.md +++ /dev/null @@ -1,230 +0,0 @@ -# Миграция с v0.4.x на v0.5.0 - -Этот раздел описывает как обновить программы, написанные на RTIC v0.4.x -на версию v0.5.0 фреймворка. - -## `Cargo.toml` - -Во-первых, нужно обновить версию зависимости `cortex-m-rtic` до -`"0.5.0"`. Опцию `timer-queue` нужно удалить. - -``` toml -[dependencies.cortex-m-rtic] -# изменить это -version = "0.4.3" - -# на это -version = "0.5.0" - -# и удалить Cargo feature -features = ["timer-queue"] -# ^^^^^^^^^^^^^ -``` - -## Аргумент `Context` - -Все функции внутри элемента `#[rtic::app]` должны принимать первым аргументом -структуру `Context`. Этот тип `Context` будет содержать переменные, которые были магически -инъецированы в область видимости функции версией v0.4.x фреймворка: -`resources`, `spawn`, `schedule` -- эти переменные станут полями структуры `Context`. -Каждая функция элемента `#[rtic::app]` получит отдельный тип `Context`. - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - // change this - #[task(resources = [x], spawn = [a], schedule = [b])] - fn foo() { - resources.x.lock(|x| /* .. */); - spawn.a(message); - schedule.b(baseline); - } - - // into this - #[task(resources = [x], spawn = [a], schedule = [b])] - fn foo(mut cx: foo::Context) { - // ^^^^^^^^^^^^^^^^^^^^ - - cx.resources.x.lock(|x| /* .. */); - // ^^^ - - cx.spawn.a(message); - // ^^^ - - cx.schedule.b(message, baseline); - // ^^^ - } - - // change this - #[init] - fn init() { - // .. - } - - // into this - #[init] - fn init(cx: init::Context) { - // ^^^^^^^^^^^^^^^^^ - // .. - } - - // .. -}; -``` - -## Ресурсы - -Синтаксис, используемый, для определения ресурсов был изменен с переменных `static mut` -на структуру `Resources`. - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - // измените это - static mut X: u32 = 0; - static mut Y: u32 = (); // поздний ресурс - - // на это - struct Resources { - #[init(0)] // <- начальное значение - X: u32, // ПРИМЕЧАНИЕ: мы предлагаем изменить стиль именования на `snake_case` - - Y: u32, // поздний ресурс - } - - // .. -}; -``` - -## Периферия устройства - -Если ваша программа получала доступ к периферии в `#[init]` через -переменну `device`, вам нужно будет добавить `peripherals = true` в атрибут -`#[rtic::app]`, чтобы и дальше получать доступ к периферии через поле `device` структуры `init::Context`. - -Измените это: - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - #[init] - fn init() { - device.SOME_PERIPHERAL.write(something); - } - - // .. -}; -``` - -На это: - -``` rust -#[rtic::app(/* .. */, peripherals = true)] -// ^^^^^^^^^^^^^^^^^^ -const APP: () = { - #[init] - fn init(cx: init::Context) { - // ^^^^^^^^^^^^^^^^^ - cx.device.SOME_PERIPHERAL.write(something); - // ^^^ - } - - // .. -}; -``` - -## `#[interrupt]` и `#[exception]` - -Атрибуты `#[interrupt]` и `#[exception]` были удалены. Чтобы определять аппаратные задачи в v0.5.x -используте атрибут `#[task]` с аргументом `binds`. - -Измените это: - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - // аппаратные задачи - #[exception] - fn SVCall() { /* .. */ } - - #[interrupt] - fn UART0() { /* .. */ } - - // программные задачи - #[task] - fn foo() { /* .. */ } - - // .. -}; -``` - -На это: - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - #[task(binds = SVCall)] - // ^^^^^^^^^^^^^^ - fn svcall(cx: svcall::Context) { /* .. */ } - // ^^^^^^ мы предлагаем использовать `snake_case` имя здесь - - #[task(binds = UART0)] - // ^^^^^^^^^^^^^ - fn uart0(cx: uart0::Context) { /* .. */ } - - #[task] - fn foo(cx: foo::Context) { /* .. */ } - - // .. -}; -``` - -## `schedule` - -Интерфейс `schedule` больше не требует cargo опции `timer-queue`, которая была удалена. -Чтобы использовать интерфес `schedule`, нужно сначала определить -монотонный тамер, который будет использоваьт среды выполнения, с помощью аргумента `monotonic` -атрибута `#[rtic::app]`. Чтобы продолжить использовать счетчик циклов -(CYCCNT) в качестве монотонного таймера, как было в версии v0.4.x, добавьте -аргумент `monotonic = rtic::cyccnt::CYCCNT` в атрибут `#[rtic::app]`. - -Также были добавлены типы `Duration` и `Instant`, а трейт `U32Ext` был перемещен в модуль `rtic::cyccnt`. -Этот модуль доступен только на устройствах ARMv7-M+. -Удаление `timer-queue` также возвращает периферию `DWT` в структуру периферии ядра, -включить ее в работу можно внутри `init`. - -Измените это: - -``` rust -use rtic::{Duration, Instant, U32Ext}; - -#[rtic::app(/* .. */)] -const APP: () = { - #[task(schedule = [b])] - fn a() { - // .. - } -}; -``` - -На это: - -``` rust -use rtic::cyccnt::{Duration, Instant, U32Ext}; -// ^^^^^^^^ - -#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)] -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -const APP: () = { - #[init] - fn init(cx: init::Context) { - cx.core.DWT.enable_cycle_counter(); - // опционально, настройка запуска DWT без подключенного отладчика - cx.core.DCB.enable_trace(); - } - #[task(schedule = [b])] - fn a(cx: a::Context) { - // .. - } -}; -``` diff --git a/book/ru/src/migration/migration_v5.md b/book/ru/src/migration/migration_v5.md deleted file mode 100644 index 84bd9fb847..0000000000 --- a/book/ru/src/migration/migration_v5.md +++ /dev/null @@ -1,365 +0,0 @@ -# Миграция с v0.5.x на v1.0.0 - -Этот раздел описывает как обновиться с версии v0.5.x на v1.0.0 фреймворка RTIC. - -## `Cargo.toml` - увеличьте версию - -Измените версию `cortex-m-rtic` на `"0.6.0"`. - -## `mod` вместо `const` - -С поддержкой атрибутов над модулями трюк с `const APP` теперь не нужен. - -Измените - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - [код здесь] -}; -``` - -на - -``` rust -#[rtic::app(/* .. */)] -mod app { - [код здесь] -} -``` - -Так как теперь используется обычный модуль Rust, это значит, что можно использовать -обычный пользовательский код в этом модуле. -Также это значит, что `use`-выражения для ресурсов, используемые -в пользовательском коде должны быть перемещены внутрь `mod app`, -либо на них можно сослаться с помощью `super`. Например, измените: - -```rust -use some_crate::some_func; - -#[rtic::app(/* .. */)] -const APP: () = { - fn func() { - some_crate::some_func(); - } -}; -``` - -на - -```rust -#[rtic::app(/* .. */)] -mod app { - use some_crate::some_func; - - fn func() { - some_crate::some_func(); - } -} -``` - -или - -```rust -use some_crate::some_func; - -#[rtic::app(/* .. */)] -mod app { - fn func() { - super::some_crate::some_func(); - } -} -``` - -## Перенос диспетчеров из `extern "C"` в аргументы app. - -Измените - -``` rust -#[rtic::app(/* .. */)] -const APP: () = { - [код здесь] - - // RTIC требует, чтобы неиспользуемые прерывания были задекларированы в блоке extern, когда - // используются программные задачи; эти свободные прерывания будут использованы для управления - // программными задачами. - extern "C" { - fn SSI0(); - fn QEI0(); - } -}; -``` - -на - -``` rust -#[rtic::app(/* .. */, dispatchers = [SSI0, QEI0])] -mod app { - [код здесь] -} -``` - -Это работает и для ОЗУ-функций, см. examples/ramfunc.rs - - -## Структуры ресурсов - `#[shared]`, `#[local]` - -Ранее ресурсы RTIC должны были размещаться в структуре с именем "Resources": - -``` rust -struct Resources { - // Ресурсы определяются здесь -} -``` - -Начиная с RTIC v1.0.0 структуры ресурсов аннотируются подобно -`#[task]`, `#[init]`, `#[idle]`: аттрибутами `#[shared]` и `#[local]` - -``` rust -#[shared] -struct MySharedResources { - // Разделяемые задачами ресурсы определены здесь -} - -#[local] -struct MyLocalResources { - // Ресурсы, определенные здесь нельзя передавать между задачами; каждый из них локальный для единственной задачи -} -``` - -Эти структуры разработчик может называть по своему желанию. - -## `shared` и `local` аргументы в `#[task]`'ах - -В v1.0.0 ресурсы разделены на `shared` ресурсы и `local` ресурсы. -`#[task]`, `#[init]` и `#[idle]` больше не имеют аргумента `resources`; -они должны использовать аргументы `shared` и `local`. - -В v0.5.x: - -``` rust -struct Resources { - local_to_b: i64, - shared_by_a_and_b: i64, -} - -#[task(resources = [shared_by_a_and_b])] -fn a(_: a::Context) {} - -#[task(resources = [shared_by_a_and_b, local_to_b])] -fn b(_: b::Context) {} -``` - -В v1.0.0: - -``` rust -#[shared] -struct Shared { - shared_by_a_and_b: i64, -} - -#[local] -struct Local { - local_to_b: i64, -} - -#[task(shared = [shared_by_a_and_b])] -fn a(_: a::Context) {} - -#[task(shared = [shared_by_a_and_b], local = [local_to_b])] -fn b(_: b::Context) {} -``` - -## Симметричные блокировки - -Теперь RTIC использует симметричные блокировки, это значит, что метод `lock` нужно использовать для -всех доступов к `shared` ресурсам. Поскольку высокоприоритетные задачи имеют эксклюзивный доступ к ресурсу, -в старом коде можно было следующее: - -``` rust -#[task(priority = 2, resources = [r])] -fn foo(cx: foo::Context) { - cx.resources.r = /* ... */; -} - -#[task(resources = [r])] -fn bar(cx: bar::Context) { - cx.resources.r.lock(|r| r = /* ... */); -} -``` - -С симметричными блокировками нужно вызывать `lock` для обоих задач: - -``` rust -#[task(priority = 2, shared = [r])] -fn foo(cx: foo::Context) { - cx.shared.r.lock(|r| r = /* ... */); -} - -#[task(shared = [r])] -fn bar(cx: bar::Context) { - cx.shared.r.lock(|r| r = /* ... */); -} -``` - -Заметьте, что скорость работы не изменяется благодаря оптимизациям LLVM, которые убирают ненужные блокировки. - -## Неблокирующий доступ к ресурсам - -В RTIC 0.5 к ресурсам разделяемым задачами, запускаемыми с одинаковым -приоритетом, можно получить доступ *без* `lock` API. -Это все еще возможно в 0.6: ресурс `#[shared]` должен быть аннотирован -аттрибутом поля `#[lock_free]`. - -v0.5 код: - -``` rust -struct Resources { - counter: u64, -} - -#[task(resources = [counter])] -fn a(cx: a::Context) { - *cx.resources.counter += 1; -} - -#[task(resources = [counter])] -fn b(cx: b::Context) { - *cx.resources.counter += 1; -} -``` - -v1.0 код: - -``` rust -#[shared] -struct Shared { - #[lock_free] - counter: u64, -} - -#[task(shared = [counter])] -fn a(cx: a::Context) { - *cx.shared.counter += 1; -} - -#[task(shared = [counter])] -fn b(cx: b::Context) { - *cx.shared.counter += 1; -} -``` - -## нет преобразования `static mut` - -`static mut` переменные больше не преобразуются в безопасные `&'static mut` ссылки. -Вместо этого синтаксиса используйте аргумент `local` в `#[init]`. - -v0.5.x code: - -``` rust -#[init] -fn init(_: init::Context) { - static mut BUFFER: [u8; 1024] = [0; 1024]; - let buffer: &'static mut [u8; 1024] = BUFFER; -} -``` - -v1.0.0 code: - -``` rust -#[init(local = [ - buffer: [u8; 1024] = [0; 1024] -// type ^^^^^^^^^^^^ ^^^^^^^^^ initial value -])] -fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let buffer: &'static mut [u8; 1024] = cx.local.buffer; - - (Shared {}, Local {}, init::Monotonics()) -} -``` - -## Init всегда возвращает поздние ресурсы - -С целью сделать API более симметричным задача #[init] всегда возвращает поздние ресурсы. - -С этого: - -``` rust -#[rtic::app(device = lm3s6965)] -mod app { - #[init] - fn init(_: init::Context) { - rtic::pend(Interrupt::UART0); - } - - // [еще код] -} -``` - -на это: - - -``` rust -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct MySharedResources {} - - #[local] - struct MyLocalResources {} - - #[init] - fn init(_: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) { - rtic::pend(Interrupt::UART0); - - (MySharedResources, MyLocalResources, init::Monotonics()) - } - - // [more code] -} -``` - -## Вызов/планирование откуда угодно - -С этой новой возвожностью, старый код, такой как: - - -``` rust -#[task(spawn = [bar])] -fn foo(cx: foo::Context) { - cx.spawn.bar().unwrap(); -} - -#[task(schedule = [bar])] -fn bar(cx: bar::Context) { - cx.schedule.foo(/* ... */).unwrap(); -} -``` - -Теперь будет выглядеть так: - -``` rust -#[task] -fn foo(_c: foo::Context) { - bar::spawn().unwrap(); -} - -#[task] -fn bar(_c: bar::Context) { - foo::schedule(/* ... */).unwrap(); -} -``` - -Заметьте, что атрибуты `spawn` и `schedule` больше не нужны. - ---- - -## Дополнительно - -### Внешние задачи - -Как программные, так и аппаратные задачи теперь можно определять вне модуля `mod app`. -Ранее это было возможно только путем реализации обертки, вызывающей реализацию задачи. - -Смотреть примеры `examples/extern_binds.rs` и `examples/extern_spawn.rs`. - diff --git a/book/ru/src/preface.md b/book/ru/src/preface.md deleted file mode 100644 index 894b6b46ba..0000000000 --- a/book/ru/src/preface.md +++ /dev/null @@ -1,26 +0,0 @@ -
-
RTIC
- -

Real-Time Interrupt-driven Concurrency

- -

Конкурентный фреймворк для создания систем реального времени

- -# Введение - -Эта книга содержит документацию пользовательского уровня о фреймворке Real-Time Interrupt-driven Concurrency -(RTIC). Справочник по API можно найти [здесь](../../api/). - -Также известен как Real-Time For the Masses. - - - - - -Это документация по RTIC версии v1.0.x; за документацией по другим версиям: - -* v0.5.x [сюда](/0.5). -* v0.4.x [сюда](/0.4). - -{{#include ../../../README_ru.md:7:45}} - -{{#include ../../../README_ru.md:51:}}