update russian translation of the book

This commit is contained in:
Andrey Zgarbul 2021-04-04 08:15:13 +03:00
parent 83cdf00eec
commit 05bda2b1bd
28 changed files with 3108 additions and 418 deletions

118
README_ru.md Normal file
View file

@ -0,0 +1,118 @@
# Real-Time Interrupt-driven Concurrency
Конкурентный фреймворк для создания систем реального времени.
Также известный как Real-Time For the Masses.
[![crates.io](https://img.shields.io/crates/v/cortex-m-rtic)](https://crates.io/crates/cortex-m-rtic)
[![docs.rs](https://docs.rs/cortex-m-rtic/badge.svg)](https://docs.rs/cortex-m-rtic)
[![book](https://img.shields.io/badge/web-rtic.rs-red.svg?style=flat&label=book&colorB=d33847)](https://rtic.rs/)
[![rustc](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-lang/rust/releases/tag/1.36.0)
[![matrix](https://img.shields.io/matrix/rtic:matrix.org)](https://matrix.to/#/#rtic:matrix.org)
[![Meeting notes](https://hackmd.io/badge.svg)](https://hackmd.io/@xmis9JvZT8Gvo9lOEKyZ4Q/SkBJKsjuH)
## Возможности
- **Задачи** как единица конкуренции [^1]. Задачи могут *запускаться от событий*
(срабатывать в ответ на асинхронные воздействия) или вызываться по запросу программы.
- **Передача сообщений** между задачами. Если точнее, сообщения можно передавать
программным задачам в момент вызова.
- **Очередь таймера** [^2]. Программные задачи можно планировать на запуск в определенный
момент в будущем. Эту возможность можно использовать для создания периодических задач.
- Поддержка приоритета задач, и, как результат, **вытесняющей многозадачности**.
- **Эффективное, избавленное от гонок данных, разделение ресурсов** благодаря легкому
разбиению на *основанные на приоритетах* критические секции [^1].
- **Выполнение без Deadlock**, гарантируемое на этапе компиляции. Данная гарантия строже,
чем та, что предоставляется [стандартный абтракцией `Mutex`][std-mutex].
[std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
- **Минимальные расходы на диспетчеризацию**. Диспетчер задач иммет минимальную программную
базу; основная работа по диспетчеризации происходит аппаратно.
- **Высокоэффективное использование памяти**: Все задачи разделяют единый стек вызовов и
отсутствует ресурсоемкая зависисмость от динамического аллокатора.
- **Все Cortex-M устройства полностью поддерживаются**.
- К такой модели задач можно применять так называемый анализ WCET (Наихудшего времени выполнения),
а также техники анализа диспетчеризации. (Хотя мы еще не разработали дружественный к Rust'у
инструментарий для этого.)
## Требования
- Rust 1.51.0+
- Приложения должны быть написаны в редакции 2018.
## [Документация пользователя](https://rtic.rs)
## [Справочник по API](https://rtic.rs/stable/api/)
## Чат
Присоединяйтесь к нам, чтобы говорить о RTIC [в Matrix-комнате][matrix-room].
Записи еженедельных собраний можно найти в [HackMD][hackmd]
[matrix-room]: https://matrix.to/#/#rtic:matrix.org
[hackmd]: https://hackmd.io/@xmis9JvZT8Gvo9lOEKyZ4Q/SkBJKsjuH
## Внести вклад
Новые возможности и большие изменения следует проводить через процесс RFC в
[соответствующем RFC-репозитории][rfcs].
[rfcs]: https://github.com/rtic-rs/rfcs
## Благодарности
Этот крейт основан на [языке Real-Time For the Masses][rtfm-lang], созданном Embedded
Systems group в [Техническом Университете Luleå][ltu], под руководством
[Prof. Per Lindgren][per].
[rtfm-lang]: http://www.rtfm-lang.org/
[ltu]: https://www.ltu.se/?l=en
[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
## Ссылки
[^1]: Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
(2013, June). Real-time for the masses, step 1: Programming API and static
priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013
8th IEEE International Symposium on (pp. 110-113). IEEE.
[^2]: Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho,
L. M. (2016). Abstract timers and their implementation onto the arm cortex-m
family of mcus. ACM SIGBED Review, 13(1), 48-53.
## Лицензия
Все исходные тексты (включая примеры кода) лицензированы под одной из лицензий:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) или
[https://www.apache.org/licenses/LICENSE-2.0][L1])
- MIT license ([LICENSE-MIT](LICENSE-MIT) или
[https://opensource.org/licenses/MIT][L2])
[L1]: https://www.apache.org/licenses/LICENSE-2.0
[L2]: https://opensource.org/licenses/MIT
на ваш выбор.
Текст книги лицензирован по условиям лицензий
Creative Commons CC-BY-SA v4.0 ([LICENSE-CC-BY-SA](LICENSE-CC-BY-SA) или
[https://creativecommons.org/licenses/by-sa/4.0/legalcode][L3]).
[L3]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
### Условия участия
Если вы не укажете этого отдельно, любой вклад, который вы предоставите в эту работу,
как указано в тексте лицензии Apache-2.0, будет лицензирован по условиям,
указанным выше, без каких-либо дополнительных условий.

View file

@ -1,5 +1,9 @@
[book] [book]
authors = ["Jorge Aparicio"] authors = ["Jorge Aparicio, Per Lindgren and The Real-Time Interrupt-driven Concurrency developers"]
multilingual = false multilingual = false
src = "src" src = "src"
title = "Real-Time Interrupt-driven Concurrency" title = "Real-Time Interrupt-driven Concurrency"
[output.html]
git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic"
git-repository-icon = "fa-github"

View file

@ -1,94 +0,0 @@
# Real-Time Interrupt-driven Concurrency
Конкурентный фреймворк для создания систем реального времени.
## Возможности
- **Задачи** - единица конкуренции [^1]. Задачи могут *запускаться по событию*
(в ответ на асинхронный стимул) или вызываться программно по желанию.
- **Передача сообщений** между задачами. А именно, сообщения можно передавать
программным задачам в момент вызова.
- **Очередь таймера** [^2]. Программные задачи можно планировать на запуск в
определенный момент в будущем. Это свойство можно использовать, чтобы
реализовывать периодические задачи.
- Поддержка приоритетов задач, и таким образом, **вытесняющей многозадачности**.
- **Эффективное, свободное от гонок данных разделение памяти** через хорошо
разграниченные критические секции на *основе приоритетов* [^1].
- **Выполнение без взаимной блокировки задач**, гарантированное на этапе
компиляции. Это более сильная гарантия, чем предоставляемая
[стандартной абстракцией `Mutex`][std-mutex].
[std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
- **Минимальные затраты на диспетчеризацию**. Диспетчер задач имеет
минимальный след; основная часть работы по диспетчеризации делается аппаратно.
- **Высокоэффективное использование памяти**: Все задачи используют общий стек
вызовов и нет сильной зависимости от динамического распределителя памяти.
- **Все устройства Cortex-M полностью поддерживаются**.
- Эта модель задач поддается известному анализу методом WCET (наихудшего
времени исполнения) и техникам анализа диспетчеризации. (Хотя мы еще не
разработали для дружественных инструментов для этого).
## Требования
- Rust 1.31.0+
- Программы нужно писать используя 2018 edition.
## [User documentation](https://japaric.github.io/cortex-m-rtic/book)
## [API reference](https://japaric.github.io/cortex-m-rtic/api/rtic/index.html)
## Благодарности
Эта библиотека основана на [языке RTIC][rtic-lang], созданном Embedded
Systems group в [Техническом Университете Luleå][ltu], под рук.
[Prof. Per Lindgren][per].
[rtic-lang]: http://www.rtic-lang.org/
[ltu]: https://www.ltu.se/?l=en
[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
## Ссылки
[^1]: Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
(2013, June). Real-time for the masses, step 1: Programming API and static
priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013
8th IEEE International Symposium on (pp. 110-113). IEEE.
[^2]: Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho,
L. M. (2016). Abstract timers and their implementation onto the arm cortex-m
family of mcus. ACM SIGBED Review, 13(1), 48-53.
## Лицензия
Все исходные тексты (включая примеры кода) лицензированы либо под:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) или
[https://www.apache.org/licenses/LICENSE-2.0][L1])
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
[https://opensource.org/licenses/MIT][L2])
[L1]: https://www.apache.org/licenses/LICENSE-2.0
[L2]: https://opensource.org/licenses/MIT
на Ваше усмотрение.
Текст книги лицензирован по условиям лицензий
Creative Commons CC-BY-SA v4.0 ([LICENSE-CC-BY-SA](LICENSE-CC-BY-SA) или
[https://creativecommons.org/licenses/by-sa/4.0/legalcode][L3]).
[L3]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
### Contribution
Если вы явно не заявляете иначе, любой взнос, преднамеренно представленный
для включения в эту работу, как определено в лицензии Apache-2.0, лицензируется, как указано выше, без каких-либо дополнительных условий.

109
book/ru/src/RTIC.svg Normal file
View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
inkscape:export-ydpi="145.74001"
inkscape:export-xdpi="145.74001"
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248_2.png"
sodipodi:docname="RTIC.svg"
viewBox="0 0 375.55994 408.84339"
height="408.84338"
width="375.55994"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath18"
clipPathUnits="userSpaceOnUse"><path
id="path16"
d="M 0,500 H 500 V 0 H 0 Z" /></clipPath></defs><sodipodi:namedview
inkscape:current-layer="g10"
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:cy="229.27385"
inkscape:cx="150.39187"
inkscape:zoom="1.5119999"
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
inkscape:pagecheckerboard="false"
showgrid="false"
id="namedview4"
inkscape:window-height="1016"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff"
inkscape:document-rotation="0" /><g
transform="matrix(1.3333333,0,0,-1.3333333,-148.85309,622.34951)"
inkscape:label="45453_RTIC logo_JK"
inkscape:groupmode="layer"
id="g10"><g
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
style="opacity:1;fill:#4d4d4d;fill-opacity:1"
transform="matrix(7.464224,0,0,7.464224,393.30978,300.96457)"
id="g248"><path
id="path250"
style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,-10.421 -8.448,-18.868 -18.868,-18.868 -10.421,0 -18.868,8.447 -18.868,18.868 0,10.421 8.447,18.868 18.868,18.868 C -8.448,18.868 0,10.421 0,0" /></g><g
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
transform="matrix(7.464224,0,0,7.464224,292.89574,388.12804)"
id="g252"><path
sodipodi:nodetypes="cccccccccc"
id="path254"
style="fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C -0.604,5.477 -5.967,9.765 -6.856,10.442 -6.487,9.748 -5.71,8.123 -5.267,6.023 -4.92,4.374 -4.845,2.758 -5.043,1.221 -5.291,-0.701 -5.97,-2.505 -7.062,-4.14 c -0.294,-0.441 -0.601,-0.894 -0.926,-1.374 -3.428,-5.065 -8.25205,-11.907209 -7.04305,-17.843209 0.528,-2.592 2.166,-4.805 4.866,-6.583 -7.606,6.593 -2.20795,13.944209 1.62005,17.105209 C -5.253,-10.117 0.659,-5.974 0,0" /></g><g
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
transform="matrix(7.464224,0,0,7.464224,193.42458,186.62982)"
id="g256"><path
sodipodi:nodetypes="ccccccccccc"
id="path258"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -0.777,1.074 -1.303,2.263 -1.562,3.535 -1.212,5.951 3.488,12.895 6.92,17.966 0.325,0.48 0.632,0.933 0.926,1.374 2.464,3.693 2.333,7.549 1.789,10.135 -0.456,2.168 -1.27,3.828 -1.621,4.477 -0.038,0.028 -0.058,0.043 -0.058,0.043 0,0 -6.038,-7.951 -8.738,-12.258 C -5.045,20.964 -8.509,12.81 -5.274,5.863 -2.263,-0.605 2.4913395,-2.6700085 3.1613395,-2.9450085 1.7523395,-2.0240085 0.824,-1.138 0,0" /></g><g
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
transform="matrix(7.464224,0,0,7.464224,286.22601,210.85049)"
id="g260"><path
sodipodi:nodetypes="cccccccssc"
id="path262"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -0.199,-4.847 -3.7433301,-6.7788234 -3.7433301,-6.7788234 0,0 0.2005158,0.00584 0.4557728,0.023109 C -0.01255733,-5.7517164 4.496,-3.342 4.518,2.624 4.53,5.687 2.682,7.663 1.13,8.781 c -1.149,0.828 -2.309,1.321 -2.935,1.551 -0.396,-0.067 -2.392,-0.519 -2.51,-2.836 0,0 -3.5425677,-1.2008654 -3.56,-1.632 C -7.9046856,5.1298176 -6.9355723,4.1874599 -6.187,3.63 -5.1908601,2.8881772 0.199,4.847 0,0" /></g><g
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
transform="matrix(7.464224,0,0,7.464224,360.6426,228.88853)"
id="g264"><path
sodipodi:nodetypes="zcccccccccccczzz"
id="path266"
style="fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M -0.34917151,1.6816738 C -0.7974951,5.9368052 -3.1264734,7.1611735 -5.072,8.56 c 0,0 -0.8516082,3.022335 -1.7015402,3.1237 0,0 0.3570815,0.04169 0,0 -0.6687287,0.05444 -1.1522423,-0.270149 -1.9532423,-1.364149 0,0 -1.1502065,1.167917 -2.4848885,1.093235 C -12.505303,11.107968 -11.817,7.957 -11.818,7.928 c 0.64,-0.24 1.768,-0.729 2.886,-1.535 0.992,-0.715 1.781,-1.534 2.346,-2.437 0.707,-1.128 1.062,-2.389 1.057,-3.748 -0.006,-1.773 -0.433,-3.369 -1.267,-4.743 -0.712,-1.172 -1.724,-2.193 -3.01,-3.036 -1.181,-0.774 -2.329326,-1.2453139 -3.451326,-1.6013139 1.173268,0.050293 3.778241,0.431572 5.8646425,1.3359556 2.0864016,0.9043837 3.5682459,1.7417342 4.4081274,2.592566 0.8398814,0.8508318 3.08370818,2.6703347 2.63538459,6.9254661 z" /></g><path
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
sodipodi:nodetypes="ssss"
inkscape:export-ydpi="153.37898"
inkscape:export-xdpi="153.37898"
id="path1340"
d="m 227.38125,254.73726 c -0.52355,-1.50734 0.39304,-4.38366 2.33326,-6.47436 2.23581,-2.40923 7.33976,11.89073 4.18714,10.96111 -2.21547,-0.65328 -6.03712,-3.09534 -6.5204,-4.48675 z"
style="fill:#808080;fill-opacity:1;stroke:#cccccc;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -1,16 +1,25 @@
# Summary # Summary
[Введение](./preface.md) [Введение](./preface.md)
- [RTIC в примерах](./by-example.md) - [RTIC в примерах](./by-example.md)
- [Атрибут `app`](./by-example/app.md) - [Атрибут `app`](./by-example/app.md)
- [Ресурсы](./by-example/resources.md) - [Ресурсы](./by-example/resources.md)
- [Задачи](./by-example/tasks.md) - [Программные задачи](./by-example/tasks.md)
- [Очередь таймера](./by-example/timer-queue.md) - [Очередь таймера](./by-example/timer-queue.md)
- [Одиночки](./by-example/singletons.md)
- [Типы, Send и Sync](./by-example/types-send-sync.md) - [Типы, Send и Sync](./by-example/types-send-sync.md)
- [Создание нового проекта](./by-example/new.md) - [Создание нового проекта](./by-example/new.md)
- [Советы и хитрости](./by-example/tips.md) - [Советы и хитрости](./by-example/tips.md)
- [Инструкции по миграции](./migration.md)
- [v0.5.x на v0.6.x](./migration/migration_v5.md)
- [v0.4.x на v0.5.x](./migration/migration_v4.md)
- [RTFM на RTIC](./migration/migration_rtic.md)
- [Под капотом](./internals.md) - [Под капотом](./internals.md)
- [Ceiling analysis](./internals/ceilings.md) - [Настройка прерываний](./internals/interrupt-configuration.md)
- [Диспетчер задач](./internals/tasks.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) - [Очередь таймера](./internals/timer-queue.md)

View file

@ -1,16 +1,23 @@
# RTIC в примерах # RTIC в примерах
Эта часть книги представляет фреймворк Real-Time Interrupt-driven Concurrency (RTIC) В этой части книги фреймворк Real-Time Interrupt-driven Concurrency (RTIC) представляется
новым пользователям через примеры с растущей сложностью. новым пользователям путем прохода по примерам от простых к более сложным.
Все примеры в этой книге можно найти в [репозитории] проекта на GitHub, Все примеры в этой части книги можно найти в [репозитарии] проекта.
и большинство примеров можно запустить на эмуляторе QEMU, поэтому никакого Большинство из них можно пройти, запустив их на эмуляторе QEMU без специального оборудования.
специального оборудования не требуется их выполнять.
[репозитории]: https://github.com/japaric/cortex-m-rtic [репозитарии]: https://github.com/rtic-rs/cortex-m-rtic
Чтобы запустить примеры на Вашем ноутбуке / ПК, Вам нужна программа Для запуска примеров на вашем ПК, вам понадобится программа `qemu-system-arm`.
`qemu-system-arm`. Инструкции по настройке окружения для разработки В [the embedded Rust book] есть инструкции по настройке среды для эмбеддед разработке,
встраиваемых устройств, в том числе QEMU, Вы можете найти в [the embedded Rust book]. в том числе QEMU.
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html [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-кода.

View file

@ -1,77 +1,84 @@
# The `app` attribute # Атрибут `app`
Это наименьшая возможная программа на RTIC: Это простейшая из возможных программ на RTIC:
``` rust ``` rust
{{#include ../../../../examples/smallest.rs}} {{#include ../../../../examples/smallest.rs}}
``` ```
Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут
нужно применять к `const`-элементам, содержащим элементы. Атрибут `app` имеет должен применяться к элементу `mod`. Атрибут `app` имеет обязательный аргумент `device`,
обязательный аргумент `device`, в качестве значения которому передается *путь*. который принимает *путь* как значение. Это должен быть полный путь, указывающий на
Этот путь должен указывать на библиотеку *устройства*, сгенерированную с помощью *крейт доступа к периферии* (PAC), сгенерированный с помощью [`svd2rust`] версии **v0.14.x**
[`svd2rust`] **v0.14.x**. Атрибут `app` развернется в удобную точку входа, или новее. Более подробно в разделе [Создание нового проекта](./new.md).
поэтому нет необходимости использовать атрибут [`cortex_m_rt::entry`].
Атрибут `app` будет раскрыт в подходящую точку входа программы, поэтому
атрибут [`cortex_m_rt::entry`] не нужен.
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html [`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
[`svd2rust`]: https://crates.io/crates/svd2rust [`svd2rust`]: https://crates.io/crates/svd2rust
[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html [`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
> **ОТСТУПЛЕНИЕ**: Некоторые из вас удивятся, почему мы используем ключевое слово `const` как
> модуль, а не правильное `mod`. Причина в том, что использование атрибутов на
> модулях требует feature gate, который требует ночную сборку. Чтобы заставить
> RTIC работать на стабильной сборке, мы используем вместо него слово `const`.
> Когда большая часть макросов 1.2 стабилизируются, мы прейдем от `const` к `mod` и в конце концов в атрибуту уровне приложения (`#![app]`).
## `init` ## `init`
Внутри псевдо-модуля атрибут `app` ожидает найти функцию инициализации, обозначенную Внутри модуля `app` атрибут ожидает найти функцию инициализации, помеченную
атрибутом `init`. Эта функция должна иметь сигнатуру `[unsafe] fn()`. атрибутом `init`. Эта функция должна иметь сигнатуру
`fn(init::Context) [-> init::LateResources]` (возвращаемый тип нужен не всегда).
Эта функция инициализации будет первой частью запускаемого приложения. Эта функция инициализации будет первой частью программы, выполняемой при запуске.
Функция `init` запустится *с отключенными прерываниями* и будет иметь эксклюзивный Функция `init` будет запущена *с отключенными прерываниями* и будет иметь эксклюзивный доступ
доступ к периферии Cortex-M и специфичной для устройства периферии через переменные к Cortex-M, в котором токен `bare_metal::CriticalSection` доступен как `cs`.
`core` and `device`, которые внедряются в область видимости `init` атрибутом `app`. Опционально, устройство-специфичные периферия доступна через поля `core` и `device` структуры
Не вся периферия Cortex-M доступна в `core`, потому что рантайм RTIC принимает владение `init::Context`.
частью из неё -- более подробно см. структуру [`rtic::Peripherals`].
Переменные `static mut`, определённые в начале `init` будут преобразованы `static mut` переменные, определенные в начале `init` будут преобразованы в
в ссылки `&'static mut` с безопасным доступом. `&'static mut` ссылки, безопасные для доступа. Обратите внимание, данная возможность может
быть удалена в следующем релизе, см. `task_local` ресурсы.
[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html [`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html
Пример ниже показывает типы переменных `core` и `device` и Пример ниже показывает типы полей `core`, `device` и `cs`, и демонстрирует
демонстрирует безопасный доступ к переменной `static mut`. безопасный доступ к `static mut` переменной. Поле `device` доступно только
когда аргумент `peripherals` установлен в `true` (по умолчанию).
В редких случаях, когда вы захотите создать приложение с минимальным потреблением ресурсов,
можно явно установить `peripherals` в `false`.
``` rust ``` rust
{{#include ../../../../examples/init.rs}} {{#include ../../../../examples/init.rs}}
``` ```
Запуск примера напечатает `init` в консоли и завершит процесс QEMU. Запуск примера напечатате `init` в консоли, а затем завершит процесс QEMU.
``` console ``` console
$ cargo run --example init $ cargo run --example init
{{#include ../../../../ci/expected/init.run}}``` {{#include ../../../../ci/expected/init.run}}
```
## `idle` ## `idle`
Функция, помеченная атрибутом `idle` может присутствовать в псевдо-модуле Функцию, помеченную атрибутом `idle` может опционально добавить в модуль.
опционально. Эта функция используется как специальная *задача ожидания* и должна иметь Эта функция используется как специальная *задача ожидания* и должна иметь сигнатуру
сигнатуру `[unsafe] fn() - > !`. `fn(idle::Context) - > !`.
Когда она присутствует, рантайм запустит задачу `idle` после `init`. В отличие от Если она присутствует, задача `idle` будет запущена после `init`. В отличие от
`init`, `idle` запустится *с включенными прерываниями* и не может завершиться, `init`, `idle` будет запущена *с включенными прерываниями* и она не может вернуть результат,
поэтому будет работать бесконечно. а значит должна работать вечно.
Когда функция `idle` не определена, рантайм устанавливает бит [SLEEPONEXIT], после чего Если функция `idle` не определена, среда вполнения устанавливает бит [SLEEPONEXIT], а затем
отправляет микроконтроллер в состояние сна после выполнения `init`. отправляет микроконтроллер в сон после запуска `init`.
[SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit [SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
Как и в `init`, переменные `static mut`будут преобразованы в ссылки `&'static mut` Как и в `init`, `static mut` переменные будут трансформированы в `&'static mut` ссылки,
с безопасным доступом. безопасные для доступа. Обратите внимание, данная возможность может
быть удалена в следующем релизе, см. `task_local` ресурсы.
В примере ниже показан запуск `idle` после `init`. Пример ниже показывает, что `idle` запускается после `init`.
**Примечание:** Цикл `loop {}` в функци ожидания не может быть пустым, так как это сломает
микроконтроллер, из-за того, что LLVM компилирует пустые циклы в инструкцию `UDF` в release mode.
Чтобы избежать неопределенного поведения, цикл должен включать "side-effect"
путем вставки ассемблерной инструкции (например, `WFI`) или ключевого слова `continue`.
``` rust ``` rust
{{#include ../../../../examples/idle.rs}} {{#include ../../../../examples/idle.rs}}
@ -79,23 +86,75 @@ $ cargo run --example init
``` console ``` console
$ cargo run --example idle $ cargo run --example idle
{{#include ../../../../ci/expected/idle.run}}``` {{#include ../../../../ci/expected/idle.run}}
```
## `interrupt` / `exception` ## Аппаратные задачи
Как Вы бы сделали с помощью библиотеки `cortex-m-rt`, Вы можете использовать атрибуты Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут `#[task]`,
`interrupt` и `exception` внутри псевдо-модуля `app`, чтобы определить обработчики который можно применять к функциям. Этот атрибут берет аргумент `binds`, чье значение -
прерываний и исключений. В RTIC, мы называем обработчики прерываний и исключений это имя прерывания, которому будет назначен обработчик;
*аппаратными* задачами. функция, декорированная этим атрибутом становится обработчиком прерывания.
В фреймворке такие типы задач именуются *аппаратными*, потому что они начинают
выполняться в ответ на аппаратное событие.
Пример ниже демонстрирует использование атрибута `#[task]`, чтобы объявить
обработчик прерывания. Как и в случае с `#[init]` и `#[idle]` локальные `static
mut` переменные безопасны для использования с аппаратной задачей.
``` rust ``` rust
{{#include ../../../../examples/interrupt.rs}} {{#include ../../../../examples/hardware.rs}}
``` ```
``` console ``` console
$ cargo run --example interrupt $ cargo run --example hardware
{{#include ../../../../ci/expected/interrupt.run}}``` {{#include ../../../../ci/expected/hardware.run}}
```
До сих пор программы RTIC, которые мы видели не отличались от программ, которые До сих пор все программы на RTIC, которые мы видели, не отличались от программ,
можно написать, используя только библиотеку `cortex-m-rt`. В следующем разделе которые можно написать, используя лишь крейт `cortex-m-rt`. С этого момента мы
мы начнем знакомиться с функционалом, присущим только RTIC. начинаем представлять возможности, уникальные для 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", а указатель на ошибку
*не* покажет на проблемное значение прерывания --
мы извиняемся за это!

View file

@ -16,19 +16,19 @@ $ cargo generate \
$ # следуйте остальным инструкциям $ # следуйте остальным инструкциям
``` ```
2. Добавьте крейт устройства, сгенерированный с помощью [`svd2rust`] **v0.14.x**, 2. Добавьте крейт доступа к периферии (PAC), сгенерированный с помощью[`svd2rust`]
или библиотеку отладочной платы, у которой в зависимостях одно из устройств. **v0.14.x**, или крейт отладочной платы, у которой в зависимостях один из таких PAC'ов.
Убедитесь, что опция `rt` крейта включена. Убедитесь, что опция `rt` крейта включена.
[`svd2rust`]: https://crates.io/crates/svd2rust [`svd2rust`]: https://crates.io/crates/svd2rust
В этом примере я покажу использование крейта устройства [`lm3s6965`]. В этом примере я буду использовать крейт устройства [`lm3s6965`].
Эта библиотека не имеет Cargo-опции `rt`; эта опция всегда включена. Эта библиотека не имеет Cargo-опции `rt`; эта опция всегда включена.
[`lm3s6965`]: https://crates.io/crates/lm3s6965 [`lm3s6965`]: https://crates.io/crates/lm3s6965
Этот крейт устройства предоставляет линковочный скрипт с макетом памяти Этот крейт устройства предоставляет линковочный скрипт с макетом памяти
целевого устройства, поэтому `memory.x` и `build.rs` не нужно удалять. целевого устройства, поэтому `memory.x` и `build.rs` нужно удалить.
``` console ``` console
$ cargo add lm3s6965 --vers 0.1.3 $ cargo add lm3s6965 --vers 0.1.3
@ -36,24 +36,40 @@ $ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs $ rm memory.x build.rs
``` ```
3. Добавьте библиотеку `cortex-m-rtic` как зависимость, и если необходимо, 3. Добавьте крейт `cortex-m-rtic` как зависимость.
включите опцию `timer-queue`.
``` console ``` console
$ cargo add cortex-m-rtic --allow-prerelease --upgrade=none $ cargo add cortex-m-rtic --allow-prerelease
``` ```
4. Напишите программу RTIC. 4. Напишите свою RTIC программу.
Здесь я буду использовать пример `init` из библиотеки `cortex-m-rtic`. Здесь я буду использовать пример `init` из крейта `cortex-m-rtic`.
Примеры находтся в папке `examples`, а содержание `init.rs` показано здесь:
``` console ``` console
$ curl \ {{#include ../../../../examples/init.rs}}
-L https://github.com/japaric/cortex-m-rtic/raw/v0.4.0-beta.1/examples/init.rs \
> src/main.rs
``` ```
Этот пример зависит от библиотеки `panic-semihosting`: Пример `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 ``` console
$ cargo add panic-semihosting $ cargo add panic-semihosting
@ -64,4 +80,5 @@ $ cargo add panic-semihosting
``` console ``` console
$ # ПРИМЕЧАНИЕ: Я раскомментировал опцию `runner` в `.cargo/config` $ # ПРИМЕЧАНИЕ: Я раскомментировал опцию `runner` в `.cargo/config`
$ cargo run $ cargo run
{{#include ../../../../ci/expected/init.run}}``` {{#include ../../../../ci/expected/init.run}}
```

View file

@ -1,22 +1,27 @@
## Ресурсы # Ресурсы
Одно из ограничений атрибутов, предоставляемых библиотекой `cortex-m-rt` является Фреймворк предоставляет абстракцию для разделения данных между любыми контекстами,
то, что совместное использование данных (или периферии) между прерываниями, с которыми мы встречались в предыдущей главе (задачами-обработчиками, `init` и `idle`): ресурсы.
или прерыванием и функцией `init`, требуют `cortex_m::interrupt::Mutex`, который
*всегда* требует отключения *всех* прерываний для доступа к данным. Отключение всех
прерываний не всегда необходимо для безопасности памяти, но компилятор не имеет
достаточно информации, чтобы оптимизировать доступ к разделяемым данным.
Атрибут `app` имеет полную картину приложения, поэтому может оптимизировать доступ к Ресурсы - это данные, видимые только функциями, определенными внутри модуля `#[app]`.
`static`-переменным. В RTIC мы обращаемся к `static`-переменным, объявленным внутри Фреймворк дает пользователю полный контроль за тем, какой контекст может
псевдо-модуля `app` как к *ресурсам*. Чтобы получить доступ к ресурсу, контекст получить доступ к какому ресурсу.
(`init`, `idle`, `interrupt` или `exception`) должен сначала определить
аргумент `resources` в соответствующем атрибуте.
В примере ниже два обработчика прерываний имеют доступ к одному и тому же ресурсу. Все ресурсы определены в одной структуре внутри модуля `#[app]`.
Никакого `Mutex` в этом случае не требуется, потому что оба обработчика запускаются Каждое поле структуры соответствует отдельному ресурсу.
с одним приоритетом и никакого вытеснения быть не может. `struct`-ура должна быть аннотирована следующим атрибутом: `#[resources]`.
К ресурсу `SHARED` можно получить доступ только из этих двух прерываний.
Ресурсам могут быть опционально даны начальные значения с помощью атрибута `#[init]`.
Ресурсы, которым не передано начально значение, называются
*поздними* ресурсами, более детально они описаны в одном из разделов на этой странице.
Каждый контекс (задача-обработчик, `init` или `idle`) должен указать ресурсы, к которым
он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя
аргумент `resources`. Этот аргумент принимает список имен ресурсов в качестве значения.
Перечисленные ресурсы становятся доступны в контексте через поле `resources` структуры `Context`.
Пример программы, показанной ниже содержит два обработчика прерывания, которые разделяют
доступ к ресурсу под названием `shared`.
``` rust ``` rust
{{#include ../../../../examples/resource.rs}} {{#include ../../../../examples/resource.rs}}
@ -27,68 +32,69 @@ $ cargo run --example resource
{{#include ../../../../ci/expected/resource.run}} {{#include ../../../../ci/expected/resource.run}}
``` ```
## Приоритеты Заметьте, что к ресурсу `shared` нельзя получить доступ из `idle`. Попытка сделать это
приведет к ошибке компиляции.
Приоритет каждого прерывания можно определить в атрибутах `interrupt` и `exception`. ## `lock`
Невозможно установить приоритет любым другим способом, потому что рантайм
забирает владение прерыванием `NVIC`; также невозможно изменить приоритет
обработчика / задачи в рантайме. Благодаря этому ограничению у фреймворка
есть знание о *статических* приоритетах всех обработчиков прерываний и исключений.
Прерывания и исключения могут иметь приоритеты в интервале `1..=(1 << NVIC_PRIO_BITS)`, Критические секции необходимы для разделения изменяемых данных таким образом,
где `NVIC_PRIO_BITS` - константа, определённая в библиотеке `device`. чтобы избежать гонок данных.
Задача `idle` имеет приоритет `0`, наименьший.
Ресурсы, совместно используемые обработчиками, работающими на разных приоритетах, Поле `resources`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого
требуют критических секций для безопасности памяти. Фреймворк проверяет, что ресурса, доступного задаче.
критические секции используются, но *только где необходимы*: например,
критические секции не нужны для обработчика с наивысшим приоритетом, имеющим
доступ к ресурсу.
API критической секции, предоставляемое фреймворком RTIC (см. [`Mutex`]), Единственный метод этого трейта, [`lock`], запускает свой аргумент-замыкание в критической секции.
основано на динамических приоритетах вместо отключения прерываний. Из этого следует,
что критические секции не будут допускать *запуск некоторых* обработчиков,
включая все соперничающие за ресурс, но будут позволять запуск обработчиков с
большим приоритетом не соперничащих за ресурс.
[`Mutex`]: ../../../api/rtic/trait.Mutex.html [`Mutex`]: ../../../api/rtic/trait.Mutex.html
В примере ниже у нас есть 3 обработчика прерываний с приоритетами от одного
до трех. Два обработчика с низким приоритетом соперничают за ресурс `SHARED`.
Обработчик с низшим приоритетом должен заблокировать ([`lock`]) ресурс
`SHARED`, чтобы получить доступ к его данным, в то время как обработчик со
средним приоритетом может напрямую получать доступ к его данным. Обработчик
с наивысшим приоритетом может свободно вытеснять критическую секцию,
созданную обработчиком с низшим приоритетом.
[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock [`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 ``` rust
{{#include ../../../../examples/lock.rs}} {{#include ../../../../examples/lock.rs}}
``` ```
``` console ``` console
$ cargo run --example lock $ cargo run --example lock
{{#include ../../../../ci/expected/lock.run}}``` {{#include ../../../../ci/expected/lock.run}}
```
## Множественное блокировка
Это расширение к `lock`, чтобы уменьшить количесво отступов, блокируемые ресурсы можно объединять в кортежи.
Следующий пример это демонстрирует:
``` rust
{{#include ../../../../examples/multilock.rs}}
```
## Поздние ресурсы ## Поздние ресурсы
В отличие от обычных `static`-переменных, к которым должно быть присвоено Поздние ресурсы - такие ресурсы, которым не передано начальное значение во время компиляции
начальное значение, ресурсы можно инициализировать в рантайме. с помощью атрибута `#[init]`, но которые вместо этого инициализируются во время выполнения
Мы называем ресурсы, инициализируемые в рантайме *поздними*. Поздние ресурсы с помощью значений из структуры `init::LateResources`, возвращаемой функцией `init`.
полезны для *переноса* (как при передаче владения) периферии из `init` в
обработчики прерываний и исключений.
Поздние ресурсы определяются как обычные ресурсы, но им присваивается начальное Поздние ресурсы полезны, например, для *move* (передача владения) периферии,
значение `()` (the unit value). `init` должен вернуть начальные значения для инициализированной в `init`, в задачи.
всех поздних ресурсов, упакованные в структуру типа `init::LateResources`.
В примере ниже использованы поздние ресурсы, чтобы установить неблокированный, Пример ниже использует поздние ресурсы, чтобы установить неблокируемый односторонний канал
односторонний канал между обработчиком прерывания `UART0` и функцией `idle`. между обработчиком прерывания `UART0` и задачей `idle`. Для канала использована очередь типа
Очередь типа один производитель-один потребитель [`Queue`] использована как канал. один производитель-один потребитель [`Queue`]. Структура очереди разделяется на потребителя
Очередь разделена на элементы потребителя и поизводителя в `init` и каждый элемент и производителя в `init`, а затем каждая из частей располагается в отдельном ресурсу;
расположен в отдельном ресурсе; `UART0` владеет ресурсом произодителя, а `idle` `UART0` владеет ресурсом производителя, а `idle` владеет ресурсом потребителя.
владеет ресурсом потребителя.
[`Queue`]: ../../../api/heapless/spsc/struct.Queue.html [`Queue`]: ../../../api/heapless/spsc/struct.Queue.html
@ -98,25 +104,46 @@ $ cargo run --example lock
``` console ``` console
$ cargo run --example late $ cargo run --example late
{{#include ../../../../ci/expected/late.run}}``` {{#include ../../../../ci/expected/late.run}}
```
## `static`-ресурсы ## Только разделяемый доступ
Переменные типа `static` также можно использовать в качестве ресурсов. Задачи По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (`&mut-`) к ресурсам,
могут получать только (разделяемые) `&` ссылки на ресурсы, но блокировки не но возможно указать, что задаче достаточен разделяемый доступ (`&-`) к ресурсы с помощью синтакисиса
нужны для доступа к данным. Вы можете думать о `static`-ресурсах как о простых `&resource_name` в списке `resources`.
`static`-переменных, которые можно инициализировать в рантайме и иметь лучшие
правила видимости: Вы можете контролировать, какие задачи получают доступ к
переменной, чтобы переменная не была видна всем фунциям в область видимости,
где она была объявлена.
В примере ниже ключ загружен (или создан) в рантайме, а затем использован в двух Преимущество указания разделяемого досупа (`&-`) к ресурсу в том, что для доступа к ресурсу
задачах, запущенных на разных приоритетах. не нужна блокировка, даже если за ресурс соревнуются несколько задач, запускаемые с
разными приоритетами. Недостаток в том, что задача получает только разделяемую ссылку (`&-`)
на ресурс, и ограничена операциями, возможными с ней, но там, где разделяемой ссылки достаточно,
такой подход уменьшает количесво требуемых блокировок.
В дополнение к простым неизменяемым данным, такой разделяемый доступ может быть полезен для
ресурсов, безопасно реализующих внутреннюю мутабельность с самоблокировкой или атомарными операциями.
Заметьте, что в этом релизе RTIC невозможно запросить и эксклюзивный доступ (`&mut-`)
и разделяемый (`&-`) для *одного и того же* ресурса из различных задач.
Попытка это сделать приведет к ошибке компиляции.
В примере ниже ключ (например криптографический ключ) загружается (или создается) во время выполнения,
а затем используется двумя задачами, запускаемымы с различным приоритетом без каких-либо блокировок.
``` rust ``` rust
{{#include ../../../../examples/static.rs}} {{#include ../../../../examples/only-shared-access.rs}}
``` ```
``` console ``` console
$ cargo run --example static $ cargo run --example only-shared-access
{{#include ../../../../ci/expected/static.run}}``` {{#include ../../../../ci/expected/only-shared-access.run}}
```
## Неблокируемый доступ к изменяемым ресурсам
Есть две других возможности доступа к ресурсам
* `#[lock_free]`: могут быть несколько задач с одинаковым приоритетом,
получающие доступ к ресурсу без критических секций. Так как задачи с
одинаковым приоритетом никогда не могут вытеснить друг друга, это безопасно.
* `#[task_local]`: в этом случае должна быть только одна задача, использующая
этот ресурс, так же как локальный `static mut` ресурс задачи, но (опционально) устанавливаемая с в init.

View file

@ -1,26 +0,0 @@
# Одиночки
Атрибут `app` знает о библиотеке [`owned-singleton`] и её атрибуте [`Singleton`].
Когда этот атрибут применяется к одному из ресурсов, рантайм производит для Вас
`unsafe` инициализацию одиночки, проверяя, что только один экземпляр одиночки
когда-либо создан.
[`owned-singleton`]: ../../api/owned_singleton/index.html
[`Singleton`]: ../../api/owned_singleton_macros/attr.Singleton.html
Заметьте, что когда Вы используете атрибут `Singleton`, Вым нужно иметь
`owned_singleton` в зависимостях.
В примере ниже атрибутом `Singleton` аннотирован массив памяти,
а экземпляр одиночки использован как фиксированный по размеру пул памяти
с помощью одной из абстракций [`alloc-singleton`].
[`alloc-singleton`]: https://crates.io/crates/alloc-singleton
``` rust
{{#include ../../../../examples/singleton.rs}}
```
``` console
$ cargo run --example singleton
{{#include ../../../../ci/expected/singleton.run}}```

View file

@ -1,22 +1,20 @@
# Программные задачи # Программные задачи
RTIC обрабатывает прерывания и исключения как *аппаратные* задачи. Аппаратные В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события,
задачи могут вызываться устройством в ответ на события, такие как нажатие кнопки. RTIC также поддерживает *программные* задачи, которые могут порождаться
RTIC также поддерживает *программные* задачи, порождаемые программой из любого приложением из любого контекста выполнения.
контекста выполнения.
Программным задачам также можно назначать приоритет и диспетчеризовать из Программным задачам можно также назначать приоритет и, под капотом, они
обработчиков прерываний. RTIC требует определения свободных прерываний в блоке диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные
`extern`, когда используются программные задачи; эти свободные прерывания будут использованы, чтобы диспетчеризовать программные задачи. Преимущество программных прерывания, были указаны в аргументе `dispatchers` модуля `app`, если используются
задач перед аппаратными в том, что на один обработчик прерывания можно назначить программные задачи; часть из этих свободных прерываний будут использованы для
множество задач. управления программными задачами. Преимущество программных задач над аппаратными
в том, что множество задач можно назначить на один обработчик прерывания.
Программные задачи определяются заданием функциям атрибута `task`. Чтобы было Программные задачи также определяются атрибутом `task`, но аргумент `binds` опускается.
возможно вызывать программные задачи, имя задачи нужно передать в аргументе
`spawn` контекста атрибута (`init`, `idle`, `interrupt`, etc.).
В примере ниже продемонстрированы три программных задачи, запускаемые на 2-х Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах.
разных приоритетах. Трем задачам назначены 2 обработчика прерываний. Три программные задачи привязаны к 2-м обработчикам прерываний.
``` rust ``` rust
{{#include ../../../../examples/task.rs}} {{#include ../../../../examples/task.rs}}
@ -24,15 +22,16 @@ RTIC также поддерживает *программные* задачи,
``` console ``` console
$ cargo run --example task $ cargo run --example task
{{#include ../../../../ci/expected/task.run}}``` {{#include ../../../../ci/expected/task.run}}
```
## Передача сообщений ## Передача сообщений
Другое преимущество программных задач - возможность передавать сообщения задачам Другое преимущество программной задачи в том, что задачам можно передать сообщения
во время их вызова. Тип полезной нагрузки сообщения должен быть определен в в момент их запуска. Тип передаваемого сообщения должен быть определен в сигнатуре
сигнатуре обработчика задачи. задачи-обработчика.
Пример ниже демонстрирует три задачи, две из которых ожидают сообщения. Пример ниже демонстрирует три задачи, две из которых ожидают сообщение.
``` rust ``` rust
{{#include ../../../../examples/message.rs}} {{#include ../../../../examples/message.rs}}
@ -40,19 +39,23 @@ $ cargo run --example task
``` console ``` console
$ cargo run --example message $ cargo run --example message
{{#include ../../../../ci/expected/message.run}}``` {{#include ../../../../ci/expected/message.run}}
```
## Ёмкость ## Вместимость
Диспетчеры задач *не* используют динамическое выделение памяти. Память RTIC *не* производит никакого рода аллокаций памяти в куче.
необходимая для размещения сообщений, резервируется статически. Фреймворк Память, необходимая для размещения сообщения резервируется статически.
зарезервирует достаточно памяти для каждого контекста, чтобы можно было вызвать По-умолчанию фреймворк минимизирует выделение памяти программой таким образом,
каждую задачу как минимум единожды. Это разумно по умолчанию, но что каждая задача имеет "вместимость" для сообщения равную 1:
"внутреннюю" ёмкость каждой задачи можно контролировать используя аргумент это значит, что не более одного сообщения можно передать задаче перед тем, как
`capacity` атрибута `task`. у нее появится возможность к запуску. Это значение по-умолчанию можно
изменить для каждой задачи, используя аргумент `capacity`.
Этот аргумент принимает положительное целое, которое определяет как много
сообщений буфер сообщений задачи может хранить.
В примере ниже установлена ёмкость программной задачи `foo` на 4. Если ёмкость Пример ниже устанавливает вместимость программной задачи `foo` равной 4.
не определена, тогда второй вызов `spawn.foo` в `UART0` вызовет ошибку. Если вместимость не установить, второй вызов `spawn.foo` в `UART0` приведет к ошибке (панике).
``` rust ``` rust
{{#include ../../../../examples/capacity.rs}} {{#include ../../../../examples/capacity.rs}}
@ -60,4 +63,54 @@ $ cargo run --example message
``` console ``` console
$ cargo run --example capacity $ cargo run --example capacity
{{#include ../../../../ci/expected/capacity.run}}``` {{#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) {
// ..
}
}
```

View file

@ -1,57 +1,76 @@
# Очередь таймера # Очередь таймера
Когда включена опция `timer-queue`, фреймворк RTIC включает В отличие от интерфейса `spawn`, который немедленно передает программную задачу
*глобальную очередь таймера*, которую приложения могут использовать, чтобы планировщику для немедленного запуска, интерфейс `schedule` можно использовать
*планировать* программные задачи на запуск через некоторое время в будущем. для планирования задачи к запуске через какое-то время в будущем.
Чтобы была возможность планировать программную задачу, имя задачи должно Чтобы использовать интерфейс `schedule`, предварительно должен быть определен
присутствовать в аргументе `schedule` контекста атрибута. Когда задача монотонный таймер с помощью аргумента `monotonic` атрибута `#[app]`.
планируется, момент ([`Instant`]), в который задачу нужно запустить, нужно передать Этот аргумент принимает путь к типу, реализующему трейт [`Monotonic`].
как первый аргумент вызова `schedule`. Ассоциированный тип, `Instant`, этого трейта представляет метку времени в соответствущих
единицах измерения и широко используется в интерфейсе `schedule` -- предлагается смоделировать
этот тип позднее [один из таких есть в стандартной библиотеке][std-instant].
[`Instant`]: ../../../api/rtic/struct.Instant.html Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов),
разница двух `Instant`ов должна возвращать какой-то тип `Duration` (см. [`core::time::Duration`])
и этот `Duration` должен реализовывать трейт `TryInto<u32>`.
Реализация этого трейта должна конвертировать значение `Duration`, которое
использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера
(SYST)". Результат преобразований должен быть 32-битным целым.
Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа.
Рантайм RTIC включает монотонный, растущий только вверх, 32-битный таймер, [`Monotonic`]: ../../../api/rtic/trait.Monotonic.html
значение которого можно запросить конструктором `Instant::now`. Время ([`Duration`]) [std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html
можно передать в `Instant::now()`, чтобы получить `Instant` в будущем. Монотонный [`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
таймер отключен пока запущен `init`, поэтому `Instant::now()` всегда возвращает
значение `Instant(0 /* циклов тактовой частоты */)`; таймер включается сразу перед
включением прерываний и запуском `idle`.
[`Duration`]: ../../../api/rtic/struct.Duration.html Для целевых платформ ARMv7+ крейт `rtic` предоставляет реализацию `Monotonic`, основанную на
встроенном CYCle CouNTer (CYCCNT). Заметьте, что это 32-битный таймер, работающий на
частоте центрального процессора, и поэтому не подходит для отслеживания интервалов времени в секундах.
В примере ниже две задачи планируются из `init`: `foo` и `bar`. `foo` - Когда планируется задача, (определенный пользователем) `Instant`, в который задача должна быть
запланирована на запуск через 8 миллионов тактов в будущем. Кроме того, `bar` выполнена, должен передаваться в качестве первого аргумента вызова `schedule`.
запланирован на запуск через 4 миллиона тактов в будущем. `bar` запустится раньше
`foo`, т.к. он запланирован на запуск первым.
> **ВАЖНО**: Примеры, использующие API `schedule` или абстракцию `Instant` К тому же, выбранный `monotonic` таймер, необходимо сконфигурировать и инициализировать в
> **не** будут правильно работать на QEMU, потому что функциональность счетчика фазе работы `#[init]`. Заметьте, что *также* касается случая использования `CYCCNT`,
> тактов Cortex-M не реализована в `qemu-system-arm`. предоставляемого крейтом `cortex-m-rtic`.
Пример ниже планирует к выполнению две задачи из `init`: `foo` и `bar`. `foo` запланирована
к запуску через 8 миллионов циклов в будущем. Далее, `bar` запланировано запустить через
4 миллиона циклов в будущем. Таким образом, `bar` запустится до `foo`, так как и запланировано.
> **DF:YJ**: Примеры, использующие интерфейс `schedule` или абстракцию `Instant`
> **не будут** правильно работать на эмуляторе QEMU, поскольку счетчик циклов Cortex-M
> функционально не был реализован в `qemu-system-arm`.
``` rust ``` rust
{{#include ../../../../examples/schedule.rs}} {{#include ../../../../examples/schedule.rs}}
``` ```
Запуск программы на реальном оборудовании производит следующий вывод в консоли: Запусе программы на реальном оборудовании создает следующий вывод в консоли:
``` text ``` text
{{#include ../../../../ci/expected/schedule.run}} {{#include ../../../../ci/expected/schedule.run}}
``` ```
Когда интерфейс `schedule` используется, среда исполнения использует внутри
обработчик прерываний `SysTick` и периферию системного таймера (`SYST`), поэтому ни
тот ни другой нельзя использовать в программе. Это гарантируется изменением типа
`init::Context.core` с `cortex_m::Peripherals` на `rtic::Peripherals`.
Последняя структура содержит все поля из предыдущей кроме `SYST`.
## Периодические задачи ## Периодические задачи
Программные задачи имеют доступ к `Instant` в момент, когда были запланированы Программные задачи имеют доступ к моменту времени `Instant`, в который они были запланированы
на запуск через переменную `scheduled`. Эта информация и API `schedule` могут на выполнение переменной `scheduled`. Эта информация и интерфейс `schedule` можно использовать,
быть использованы для реализации периодических задач, как показано в примере ниже. чтобы реализовать периодические задачи, как показано ниже.
``` rust ``` rust
{{#include ../../../../examples/periodic.rs}} {{#include ../../../../examples/periodic.rs}}
``` ```
Это вывод, произведенный примером. Заметьте, что есть смещение / колебание нуля Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания
даже если `schedule.foo` была вызвана в *конце* `foo`. Использование даже несмотря на то, что `schedule.foo` была вызвана в *конце* `foo`. Использование
`Instant::now` вместо `scheduled` имело бы влияние на смещение / колебание. `Instant::now` вместо `scheduled` вызвало бы дрейф / колебания.
``` text ``` text
{{#include ../../../../ci/expected/periodic.run}} {{#include ../../../../ci/expected/periodic.run}}
@ -59,31 +78,30 @@
## Базовое время ## Базовое время
Для задач, планируемых из `init` мы имеем точную информацию о их планируемом Для задач, вызываемых из `init` мы имеем точную информацию о их `scheduled` времени.
(`scheduled`) времени. Для аппаратных задач нет `scheduled` времени, потому Для аппаратных задач такого времени нет, поскольку они асинхронны по природе.
что эти задачи асинхронны по природе. Для аппаратных задач рантайм предоставляет Для аппаратных задач среда исполнения предоставляет время запуска (`start`), которое отражает
время старта (`start`), которе отражает время, в которое обработчик прерывания время, в которое обработчик прерывания будет запущен.
был запущен.
Заметьте, что `start` **не** равен времени возникновения события, вызвавшего Заметьте, что `start` **не** равно времени прихода события, которое вызывает задачу.
задачу. В зависимости от приоритета задачи и загрузки системы время В зависимости от приоритета задачи и загрузки системы, время `start` может сильно отдалиться от
`start` может быть сильно отдалено от времени возникновения события. времени прихода события.
Какое по Вашему мнению будет значение `scheduled` для программных задач которые Какое по вашему мнению будет значение `scheduled` для программных задач, которые вызываются через
*вызываются*, вместо того чтобы планироваться? Ответ в том, что вызываемые `spawn` вместо планирования? Ответ в том, что вызываемые задачи наследуют
задачи наследуют *базовое* время контекста, в котором вызваны. Бызовым для *базовое* время того контекста, который их вызывает. Базовое время аппаратных задач -
аппаратных задач является `start`, базовым для программных задач - `scheduled` это их время `start`, базовое время программных задач - их время `scheduled`, а
и базовым для `init` - `start = Instant(0)`. `idle` на сомом деле не имеет базовое время `init` - время старта системы, или нулевое
базового времени но задачи, вызванные из него будут использовать `Instant::now()` (`Instant::zero()`). `idle` на самом деле не имеет базового времени, но задачи вызываемые из нее,
как их базовое время. используют `Instant::now()` в качестве базового.
Пример ниже демонстрирует разное значение *базового времени*. Пример ниже демонстрирует разные смыслы *базового времени*.
``` rust ``` rust
{{#include ../../../../examples/baseline.rs}} {{#include ../../../../examples/baseline.rs}}
``` ```
Запуск программы на реальном оборудовании произведет следующий вывод в консоли: Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
``` text ``` text
{{#include ../../../../ci/expected/baseline.run}} {{#include ../../../../ci/expected/baseline.run}}

View file

@ -2,10 +2,15 @@
## Обобщенное программирование (Generics) ## Обобщенное программирование (Generics)
Ресурсы, совместно используемые двумя или более задачами, реализуют трейт `Mutex` Все объекты, предоставляющие ресурысы реализуют трейт `rtic::Mutex`.
во *всех* контекстах, даже в тех, где для доступа к данным не требуются Если ресурс не реализует его, можно обернуть его в новый тип [`rtic::Exclusive`],
критические секции. Это позволяет легко писать обобщенный код оперирующий который реализует трейт `Mutex`. С помощью этого нового типа
ресурсами, который можно вызывать из различных задач. Вот такой пример: можно написать обобщенную функцию, которая работает с обобщенным ресурсом и
вызывать его из различных задач, чтобы производить однотипные операции над
похожим множеством ресурсов.
Вот один такой пример:
[`rtic::Exclusive`]: ../../../api/rtic/struct.Exclusive.html
``` rust ``` rust
{{#include ../../../../examples/generics.rs}} {{#include ../../../../examples/generics.rs}}
@ -13,12 +18,29 @@
``` console ``` console
$ cargo run --example generics $ cargo run --example generics
{{#include ../../../../ci/expected/generics.run}}``` {{#include ../../../../ci/expected/generics.run}}
```
Это также позволяет Вам изменять статические приоритеты задач без ## Условная компиляция
переписывания кода. Если Вы единообразно используете `lock`-и для доступа
к данным в разделяемых ресурсах, тогда Ваш код продолжит компилироваться, Вы можете использовать условную компиляцию (`#[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}}
```
## Запуск задач из ОЗУ ## Запуск задач из ОЗУ
@ -31,10 +53,10 @@ RTIC v0.4.x была возможность взаимодействия с др
> очень мощные, но их легко использовать неправильно. Неверное использование > очень мощные, но их легко использовать неправильно. Неверное использование
> любого из этих атрибутов может вызвать неопределенное поведение; > любого из этих атрибутов может вызвать неопределенное поведение;
> Вам следует всегда предпочитать использование безопасных, высокоуровневых > Вам следует всегда предпочитать использование безопасных, высокоуровневых
> атрибутов вокруг них, таких как атрибуты `interrupt` и `exception` > атрибутов вместо них, таких как атрибуты `interrupt` и `exception`
> из `cortex-m-rt`. > из `cortex-m-rt`.
> >
> В особых случаях функций RAM нет безопасной абстракции в `cortex-m-rt` > В особых функций, размещаемых в ОЗУ нет безопасной абстракции в `cortex-m-rt`
> v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе. > v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе.
[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100 [RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
@ -45,37 +67,105 @@ RTIC v0.4.x была возможность взаимодействия с др
{{#include ../../../../examples/ramfunc.rs}} {{#include ../../../../examples/ramfunc.rs}}
``` ```
Запуск этой программы произведет ожидаемый вывод. Запуск этой программы создаст ожидаемый вывод.
``` console ``` console
$ cargo run --example ramfunc $ cargo run --example ramfunc
{{#include ../../../../ci/expected/ramfunc.run}}``` {{#include ../../../../ci/expected/ramfunc.run}}
```
Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ
(`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`). (`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`).
``` console ``` console
$ cargo nm --example ramfunc --release | grep ' foo::' $ cargo nm --example ramfunc --release | grep ' foo::'
{{#include ../../../../ci/expected/ramfunc.grep.foo}}``` {{#include ../../../../ci/expected/ramfunc.grep.foo}}
```
``` console ``` console
$ cargo nm --example ramfunc --release | grep ' bar::' $ cargo nm --example ramfunc --release | grep ' bar::'
{{#include ../../../../ci/expected/ramfunc.grep.bar}}``` {{#include ../../../../ci/expected/ramfunc.grep.bar}}
```
## `binds` ## Обходной путь для быстрой передачи сообщений
**ПРИМЕЧАНИЕ**: Требуется RTIC не ниже 0.4.2 Передача сообщений всегда вызывает копирование от отправителя в
статическую переменную, а затем из статической переменной получателю.
Таким образом, при передаче большого буфера, например `[u8; 128]`, передача сообщения
вызывает два дорогих вызова `memcpy`. Чтобы минимизировать накладные расходы на передачу
сообщения, можно использовать обходной путь: вместо передачи буфера по значению,
можно передавать владеющий указатель на буфер.
Вы можете давать аппаратным задачам имена похожие на имена обычных задач. Можно использовать глобальный аллокатор, чтобы реализовать данный трюк (`alloc::Box`,
Для этого нужно использовать аргумент `binds`: Вы называете функцию `alloc::Rc`, и т.п.), либо использовать статически аллоцируемый пул памяти, например [`heapless::Pool`].
по своему желанию и назначаете ей прерывание / исключение
через аргумент `binds`. `Spawn` и другие служебные типы будут размещены в модуле, [`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
названном в соответствии с названием функции, а не прерывания / исключения.
Давайте посмотрим пример: Здесь приведен пример использования `heapless::Pool` для "упаковки" буфера из 128 байт.
``` rust ``` rust
{{#include ../../../../examples/binds.rs}} {{#include ../../../../examples/pool.rs}}
``` ```
``` console ``` console
$ cargo run --example binds $ cargo run --example pool
{{#include ../../../../ci/expected/binds.run}}``` {{#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}}
```

View file

@ -1,16 +1,15 @@
# Типы, Send и Sync # Типы, Send и Sync
Атрибут `app` вводит контекст, коллекцию переменных в каждую из функций. Каждая функция в модуле `app` принимает структуру `Context` в качесте первого параметра.
Все эти переменные имеют предсказуемые, неанонимные типы, поэтому Вы можете Все поля этих структур имеют предсказуемые, неанонимные типы,
писать простые функции, получающие их как аргументы. поэтому вы можете написать обычные функции, принимающие их как аргументы.
Описание API определяет как эти типы эти типы генерируются из входных данных. Справочник по API определяет как эти типы генерируются на основе входных данных.
Вы можете также сгенерировать документацию для Вашей бинарной библиотеки Вы можете также сгенерировать документацию к вашему крейту программы (`cargo doc --bin <name>`);
(`cargo doc --bin <name>`); в документации Вы найдете структуры `Context` в документации вы найдете структуры `Context` (например `init::Context` и
(например `init::Context` и `idle::Context`), чьи поля представляют переменные `idle::Context`).
включенные в каждую функцию.
В примере ниже сгенерированы разные типы с помощью атрибута `app`. Пример ниже показывает различные типы, сгенерированные атрибутом `app`.
``` rust ``` rust
{{#include ../../../../examples/types.rs}} {{#include ../../../../examples/types.rs}}
@ -18,39 +17,30 @@
## `Send` ## `Send`
[`Send`] - маркерный типаж (trait) для "типов, которые можно передавать через границы [`Send`] - это маркерный трейт для "типов, которые можно передавать через границы
потоков", как это определено в `core`. В контексте RTIC типаж `Send` необходим потоков", как это определено в `core`. В контексте RTIC трейт `Send` необходим
только там, где возможна передача значения между задачами, запускаемыми на только там, где возможна передача значения между задачами, запускаемыми на
*разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений, *разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений,
в совместно используемых `static mut` ресурсах и инициализации поздних ресурсов. в разделяемых `static mut` ресурсах и при инициализации поздних ресурсов.
[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому Вам не Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому вам не
стоит волноваться об этом. Более важно знать, где Вам *не* нужен типаж `Send`: стоит волноваться об этом. В настоящий момент все передаваемые типы в RTIC должны быть `Send`, но
в типах, передаваемых между задачами с *одинаковым* приоритетом. Это возникает это ограничение возможно будет ослаблено в будущем.
в двух случаях: при передаче сообщений и в совместно используемых `static mut`
ресурсах.
В примере ниже показано, где можно использовать типы, не реализующие `Send`.
``` rust
{{#include ../../../../examples/not-send.rs}}
```
## `Sync` ## `Sync`
Похожая ситуация, [`Sync`] - маркерный типаж для "типов, на которых можно Аналогично, [`Sync`] - маркерный трейт для "типов, на которые можно безопасно разделять между потоками",
ссылаться в разных потоках", как это определено в `core`. В контексте RTIC как это определено в `core`. В контексте RTIC типаж `Sync` необходим только там,
типаж `Sync` необходим только там, где возможны две или более задачи, где возможно для двух или более задач, запускаемых на разных приоритетах получить разделяемую ссылку (`&-`) на
запускаемые на разных приоритетах, чтобы захватить разделяемую ссылку на ресурс. Это возникает только (`&-`) ресурсах с разделяемым доступом.
ресурс. Это возникает только совместно используемых `static`-ресурсах.
[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
Атрибут `app` проверит, что `Sync` реализован, где необходимо, но важно знать, Атрибут `app` проверит, что `Sync` реализован, где необходимо, но важно знать,
где ограничение `Sync` не требуется: в `static`-ресурсах, разделяемых между где ограничение `Sync` не требуется: в (`&-`) ресурсах с разделяемым доступом, за которые
задачами с *одинаковым* приоритетом. соперничают задачи с *одинаковым* приоритетом.
В примере ниже показано, где можно использовать типы, не реализующие `Sync`. В примере ниже показано, где можно использовать типы, не реализующие `Sync`.

View file

@ -1,7 +1,14 @@
# Под капотом # Под капотом
В этом разделе описывабтся внутренности фркймворка на *высоком уровне*. **Этот раздел в настоящий момент находится в разработке,
Низкоуровневые тонкости, такие как парсинг и кодогенерация производимые он появится снова, когда будет завершен**
процедурным макросом (`#[app]`) здесь объясняться не будут. Мы сосредоточимся
на анализе пользовательской спецификации и структурах данных, используемых Этот раздел описывает внутренности фреймворка RTIC на *высоком уровне*.
рантаймом. Низкоуровневые детали, такие как парсинг и генерация кода, выполняемые процедурным макросом
(`#[app]`) объясняться не будут. Внимание будет сосредоточено на анализе
спецификации пользователя и структурах данных, используемых на этапе выполнения.
Мы настоятельно рекомендуем вам прочитать раздел о [конкуренции] в embedonomicon
перед тем, как погружаться в материал.
[конкуренции]: https://github.com/rust-embedded/embedonomicon/pull/48

View file

@ -0,0 +1,158 @@
# Контроль доступа
Одна из основ 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,
},
// ..
});
}
}
```

View file

@ -1,3 +1,92 @@
# Ceiling analysis # Анализ приоритетов
**TODO** *Поиск максимального приоритета* ресурса (*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;
// ..
}
// ..
}
```

View file

@ -0,0 +1,521 @@
# Критические секции
Когда ресурсы (статические переменные) разделяются между двумя или более задачами,
которые выполняются с разными приоритетами, некая форма запрета изменений
необходима, чтобы изменять память без гонки данных. В 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<R>(&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<R, F>(&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<u8>,
}
impl<'a> x<'a> {
pub unsafe fn new(priority: &'a Cell<u8>) -> Self {
x { priority }
}
pub unsafe fn priority(&self) -> &Cell<u8> {
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<R>(&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` никогда не получат шанс на запуск.

View file

@ -0,0 +1,72 @@
# Настройка прерываний
Прерывания - это основа работы программ на 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(/* .. */)
}
```

View file

@ -0,0 +1,114 @@
# Поздние ресурсы
Некоторые ресурсы инициализируются во время выполнения после завершения функции `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<Thing> = 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` ведет себя как like a *compiler
fence*, которое не дает компилятору пореставить запись в `X` *после*
`interrupt::enable`. Если бы компилятор мог делать такие перестановки появились
бы гонки данных между этой записью и любой операцией `foo`, взаимодействующей с `X`.
Архитектурам с более сложным конвейером инструкций нужен барьер памяти
(`atomic::fence`) вместо compiler fence для полной очистки операции записи
перед включением прерываний. Архитектура ARM Cortex-M не нуждается в барьере памяти
в одноядерном контексте.

View file

@ -0,0 +1,79 @@
# Нереентерабельность
В 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 нет.

View file

@ -1,3 +1,399 @@
# Task dispatcher # Программные задачи
**TODO** 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<T> {
task: T,
// ..
}
/// вызываемые (`spawn`) задачи, выполняющиеся с уровнем приоритета `1`
enum T1 {
bar,
baz,
}
// очередь готовности диспетчера задач
// `U4` - целое число, представляющее собой емкость этой очереди
static mut RQ1: Queue<Ready<T1>, U4> = 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<u8>,
}
impl<'a> Spawn<'a> {
// `unsafe` и спрятано, поскольку сы не хотит, чтобы пользователь вмешивался сюда
#[doc(hidden)]
pub unsafe fn priority(&self) -> &Cell<u8> {
self.priority
}
}
}
mod app {
// ..
// Поиск максимального приоритета для конечного производителя `RQ1`
const RQ1_CEILING: u8 = 2;
// используется, чтобы отследить сколько еще сообщений для `bar` можно поставить в очередь
// `U2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь
// эта очередь заполняется фреймворком до того, как запустится `init`
static mut bar_FQ: Queue<(), U2> = Queue::new();
// Поиск максимального приоритета для конечного потребителя `bar_FQ`
const bar_FQ_CEILING: u8 = 2;
// приоритет-ориентированная критическая секция
//
// это запускае переданное замыкание `f` с динамическим приоритетом не ниже
// `ceiling`
fn lock(priority: &Cell<u8>, ceiling: u8, f: impl FnOnce()) {
// ..
}
impl<'a> foo::Spawn<'a> {
/// Вызывает задачу `bar`
pub fn bar(&self) -> Result<(), ()> {
unsafe {
match lock(self.priority(), bar_FQ_CEILING, || {
bar_FQ.split().1.dequeue()
}) {
Some(()) => {
lock(self.priority(), RQ1_CEILING, || {
// помещаем задачу в очередь готовности
RQ1.split().1.enqueue_unchecked(Ready {
task: T1::bar,
// ..
})
});
// вызываем прерывание, которое запускает диспетчер задач
rtic::pend(Interrupt::UART0);
}
None => {
// достигнута максимальная вместительность; неудачный вызов
Err(())
}
}
}
}
}
}
```
Использование `bar_FQ` для ограничения числа задач `bar`, которые могут бы вызваны,
может показаться искусственным, но это будет иметь больше смысла, когда мы поговорим
о вместительности задач.
## Сообщения
Мы пропустили, как на самом деле работает передача сообщений, поэтому давайте вернемся
к реализации `spawn`, но в этот раз для задачи `baz`, которая принимает сообщение типа `u64`.
``` rust
fn baz(c: baz::Context, input: u64) {
// .. пользовательский код ..
}
mod app {
// ..
// Теперь мы покажем все содержимое структуры `Ready`
struct Ready {
task: Task,
// индекс сообщения; используется с буфером `INPUTS`
index: u8,
}
// память, зарезервированная для хранения сообщений, переданных `baz`
static mut baz_INPUTS: [MaybeUninit<u64>; 2] =
[MaybeUninit::uninit(), MaybeUninit::uninit()];
// список свободной памяти: используется для отслеживания свободных ячеек в массиве `baz_INPUTS`
// эта очередь инициализируется значениями `0` и `1` перед запуском `init`
static mut baz_FQ: Queue<u8, U2> = Queue::new();
// Поиск максимального приоритета для конечного потребителя `baz_FQ`
const baz_FQ_CEILING: u8 = 2;
impl<'a> foo::Spawn<'a> {
/// Spawns the `baz` task
pub fn baz(&self, message: u64) -> Result<(), u64> {
unsafe {
match lock(self.priority(), baz_FQ_CEILING, || {
baz_FQ.split().1.dequeue()
}) {
Some(index) => {
// ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера
baz_INPUTS[index as usize].write(message);
lock(self.priority(), RQ1_CEILING, || {
// помещаем задачу в очередь готовности
RQ1.split().1.enqueue_unchecked(Ready {
task: T1::baz,
index,
});
});
// вызываем прерывание, которое запускает диспетчер задач
rtic::pend(Interrupt::UART0);
}
None => {
// достигнута максимальная вместительность; неудачный вызов
Err(message)
}
}
}
}
}
}
```
А теперь давайте взглянем на настоящую реализацию диспетчера задач:
``` 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`

View file

@ -1,3 +1,372 @@
# Timer queue # Очередь таймера
**TODO** Функциональность очередь таймера позволяет пользователю планировать задачи на запуск
в опреленное время в будущем. Неудивительно, что эта функция также реализуется с помощью очереди:
очередь приоритетов, где запланированные задачи сортируются в порядке аозрастания времени.
Эта функция требует таймер, способный устанавливать прерывания истечения времени.
Таймер используется для пуска прерывания, когда настает запланированное время задачи;
в этот момент задача удаляется из очереди таймера и помещается в очередь готовности.
Давайте посмотрим, как это реализовано в коде. Рассмотрим следующую программу:
``` rust
#[rtic::app(device = ..)]
mod app {
// ..
#[task(capacity = 2, schedule = [foo])]
fn foo(c: foo::Context, x: u32) {
// запланировать задачу на повторный запуск через 1 млн. тактов
c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok();
}
extern "C" {
fn UART0();
}
}
```
## `schedule`
Давайте сначала взглянем на интерфейс `schedule`.
``` rust
mod foo {
pub struct Schedule<'a> {
priority: &'a Cell<u8>,
}
impl<'a> Schedule<'a> {
// `unsafe` и спрятано, потому что мы не хотим, чтобы пользовать сюда вмешивался
#[doc(hidden)]
pub unsafe fn priority(&self) -> &Cell<u8> {
self.priority
}
}
}
mod app {
type Instant = <path::to::user::monotonic::timer as rtic::Monotonic>::Instant;
// все задачи, которые могут быть запланированы (`schedule`)
enum T {
foo,
}
struct NotReady {
index: u8,
instant: Instant,
task: T,
}
// Очередь таймера - двоичная куча (min-heap) задач `NotReady`
static mut TQ: TimerQueue<U2> = ..;
const TQ_CEILING: u8 = 1;
static mut foo_FQ: Queue<u8, U2> = Queue::new();
const foo_FQ_CEILING: u8 = 1;
static mut foo_INPUTS: [MaybeUninit<u32>; 2] =
[MaybeUninit::uninit(), MaybeUninit::uninit()];
static mut foo_INSTANTS: [MaybeUninit<Instant>; 2] =
[MaybeUninit::uninit(), MaybeUninit::uninit()];
impl<'a> foo::Schedule<'a> {
fn foo(&self, instant: Instant, input: u32) -> Result<(), u32> {
unsafe {
let priority = self.priority();
if let Some(index) = lock(priority, foo_FQ_CEILING, || {
foo_FQ.split().1.dequeue()
}) {
// `index` - владеющий укачатель на ячейки в этих буферах
foo_INSTANTS[index as usize].write(instant);
foo_INPUTS[index as usize].write(input);
let nr = NotReady {
index,
instant,
task: T::foo,
};
lock(priority, TQ_CEILING, || {
TQ.enqueue_unchecked(nr);
});
} else {
// Не осталось места, чтобы разместить входные данные / instant
Err(input)
}
}
}
}
}
```
Это очень похоже на реализацию `Spawn`. На самом деле одни и те же буфер
`INPUTS` и список сободной памяти (`FQ`) используются совместно интерфейсами
`spawn` и `schedule`. Главное отличие между ними в том, что `schedule` также
размещает `Instant`, момент на который задача запланирована на запуск,
в отдельном буфере (`foo_INSTANTS` в нашем случае).
`TimerQueue::enqueue_unchecked` делает немного больше работы, чем
просто добавление записи в min-heap: он также вызывает прерывание
системного таймера (`SysTick`), если новая запись оказывается первой в очереди.
## Системный таймер
Прерывание системного таймера (`SysTick`) заботится о двух вещах:
передаче задач, которых становятся готовыми из очереди таймера в очередь готовности
и установке прерывания истечения времени, когда наступит запланированное
время следующей задачи.
Давайте посмотрим на соответствующий код.
``` rust
mod app {
#[no_mangle]
fn SysTick() {
const PRIORITY: u8 = 1;
let priority = &Cell::new(PRIORITY);
while let Some(ready) = lock(priority, TQ_CEILING, || TQ.dequeue()) {
match ready.task {
T::foo => {
// переместить эту задачу в очередь готовности `RQ1`
lock(priority, RQ1_CEILING, || {
RQ1.split().0.enqueue_unchecked(Ready {
task: T1::foo,
index: ready.index,
})
});
// вызвать диспетчер задач
rtic::pend(Interrupt::UART0);
}
}
}
}
}
```
Выглядит похоже на диспетчер задач, за исключением того, что
вместо запуска готовой задачи, она лишь переносится в очередь готовности,
что ведет к ее запуску с нужным приоритетом.
`TimerQueue::dequeue` установит новое прерывание истечения времени, если вернет
`None`. Он сязан с `TimerQueue::enqueue_unchecked`, который вызывает это
прерывание; на самом деле, `enqueue_unchecked` передает задачу установки
нового прерывание истечения времени обработчику `SysTick`.
## Точность и диапазон `cyccnt::Instant` и `cyccnt::Duration`
RTIC предоставляет реализацию `Monotonic`, основанную на счетчике тактов `DWT` (Data Watchpoint and Trace). `Instant::now` возвращает снимок таймера; эти снимки
DWT (`Instant`ы) используются для сортировки записей в очереди таймера.
Счетчик тактов - 32-битный счетчик, работающий на частоте ядра.
Этот счетчик обнуляется каждые `(1 << 32)` тактов; у нас нет прерывания,
ассоциированног с этим счетчиком, поэтому ничего ужасного не случится,
когда он пройдет оборот.
Чтобы упорядочить `Instant`ы в очереди, нам нужно сравнить 32-битные целые.
Чтобы учесть обороты, мы используем разницу между двумя `Instant`ами, `a - b`,
и рассматриваем результат как 32-битное знаковое целое.
Если результат меньше нуля, значит `b` более поздний `Instant`;
если результат больше нуля, значит `b` более ранний `Instant`.
Это значит, что планирование задачи на `Instant`, который на `(1 << 31) - 1` тактов
больше, чем запланированное время (`Instant`) первой (самой ранней) записи
в очереди приведет к тому, что задача будет помещена в неправильное
место в очереди. У нас есть несколько debug assertions в коде, чтобы
предотвратить эту пользовательскую ошибку, но этого нельзя избежать,
поскольку пользователь может написать
`(instant + duration_a) + duration_b` и переполнить `Instant`.
Системный таймер, `SysTick` - 24-битный счетчик также работающий
на частоте процессора. Когда следующая планируемая задача более, чем в
`1 << 24` тактов в будущем, прерывание устанавливается на время в пределах
`1 << 24` тактов. Этот процесс может происходить несколько раз, пока
следующая запланированная задача не будет в диапазоне счетчика `SysTick`.
Подведем итог, оба `Instant` и `Duration` имеют разрешение 1 такт ядра, и `Duration` эффективно имеет (полуоткрытый) диапазон `0..(1 << 31)` (не включая максимум) тактов ядра.
## Вместительность очереди
Вместительность очереди таймера рассчитывается как сумма вместительностей
всех планируемых (`schedule`) задач. Как и в случае очередей готовности,
это значит, что как только мы затребовали пустую ячейку в буфере `INPUTS`,
мы гарантируем, что способны передать задачу в очередь таймера;
это позволяет нам опустить проверки времени выполнения.
## Приоритет системного таймера
Приориет системного таймера не может быть установлен пользователем;
он выбирается фреймворком.
Чтобы убедиться, что низкоприоритетные задачи не препятствуют
запуску высокоприоритетных, мы выбираем приоритет системного таймера
максимальным из всех планируемых задач.
Чтобы понять, почему это нужно, рассмотрим вариант, когда две ранее
запланированные задачи с приоритетами `2` и `3` становятся готовыми в
примерно одинаковое время, но низкоприоритетная задача перемещается
в очередь готовности первой.
Если бы приоритет системного таймера был, например, равен `1`,
тогда после перемещения низкоприоритетной (`2`) задачи, это бы привело
к завершению (из-за того, что приоритет выше приоритета системного таймера)
ожидания выполнения высокоприоритетной задачи (`3`).
Чтобы избежать такого сценария, системный таймер должен работать на
приоритете, равном наивысшему из приоритетов планируемых задач;
в этом примере это `3`.
## Анализ приоритетов
Очередь таймера - это ресурс, разделяемый всеми задачами, которые могут
планировать (`schedule`) задачи и обработчиком `SysTick`.
Также интерфейс `schedule` соперничает с интерфейсом `spawn`
за списки свободной памяти. Все это должно уситываться в анализе приоритетов.
Чтобы проиллюстрировать, рассмотрим следующий пример:
``` rust
#[rtic::app(device = ..)]
mod app {
#[task(priority = 3, spawn = [baz])]
fn foo(c: foo::Context) {
// ..
}
#[task(priority = 2, schedule = [foo, baz])]
fn bar(c: bar::Context) {
// ..
}
#[task(priority = 1)]
fn baz(c: baz::Context) {
// ..
}
}
```
Анализ приоритетов происходил бы вот так:
- `foo` (prio = 3) и `baz` (prio = 1) планируемые задачи, поэтому
`SysTick` должен работать на максимальном из этих двух приоритетов, т.е. `3`.
- `foo::Spawn` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
конечный потребитель `baz_FQ`; это приводит к максимальному приоритету `3`.
- `bar::Schedule` (prio = 2) имеет экслюзивный доступ к
конечному потребителю `foo_FQ`; поэтому максимальный приоритет `foo_FQ` фактически `2`.
- `SysTick` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
очередь таймера `TQ`; это приводит к максимальному приоритету `3`.
- `SysTick` (prio = 3) и `foo::Spawn` (prio = 3) оба имеют неблокируемый
доступ к очереди готовности `RQ3`, что хранит записи `foo`;
поэтому максимальный приоритет `RQ3` фактически `3`.
- `SysTick` имеет эксклюзивный доступ к очереди готовности `RQ1`,
которая хранит записи `baz`; поэтому максимальный приоритет `RQ1` фактически `3`.
## Изменения в реализации `spawn`
Когда интерфейс `schedule` используется, реализация `spawn` немного
изменяется, чтобы отслеживать baseline задач. Как можете видеть в
реализации `schedule` есть буферы `INSTANTS`, используемые, чтобы
хранить время, в которое задача была запланирована навыполнение;
этот `Instant` читается диспетчером задач и передается в пользовательский
код, как часть контекста задачи.
``` rust
mod app {
// ..
#[no_mangle]
unsafe UART1() {
const PRIORITY: u8 = 1;
let snapshot = basepri::read();
while let Some(ready) = RQ1.split().1.dequeue() {
match ready.task {
Task::baz => {
let input = baz_INPUTS[ready.index as usize].read();
// ADDED
let instant = baz_INSTANTS[ready.index as usize].read();
baz_FQ.split().0.enqueue_unchecked(ready.index);
let priority = Cell::new(PRIORITY);
// ИЗМЕНЕНО instant передан как часть контекста задачи
baz(baz::Context::new(&priority, instant), input)
}
Task::bar => {
// выглядит также как ветка для `baz`
}
}
}
// инвариант BASEPRI
basepri::write(snapshot);
}
}
```
И наоборот, реализации `spawn` нужно писать значение в буфер `INSTANTS`.
Записанное значение располагается в структуре `Spawn` и это либо
время `start` аппаратной задачи, либо время `scheduled` программной задачи.
``` rust
mod foo {
// ..
pub struct Spawn<'a> {
priority: &'a Cell<u8>,
// ADDED
instant: Instant,
}
impl<'a> Spawn<'a> {
pub unsafe fn priority(&self) -> &Cell<u8> {
&self.priority
}
// ADDED
pub unsafe fn instant(&self) -> Instant {
self.instant
}
}
}
mod app {
impl<'a> foo::Spawn<'a> {
/// Spawns the `baz` task
pub fn baz(&self, message: u64) -> Result<(), u64> {
unsafe {
match lock(self.priority(), baz_FQ_CEILING, || {
baz_FQ.split().1.dequeue()
}) {
Some(index) => {
baz_INPUTS[index as usize].write(message);
// ADDED
baz_INSTANTS[index as usize].write(self.instant());
lock(self.priority(), RQ1_CEILING, || {
RQ1.split().1.enqueue_unchecked(Ready {
task: Task::foo,
index,
});
});
rtic::pend(Interrupt::UART0);
}
None => {
// достигнута максимальная вместительность; неудачный вызов
Err(message)
}
}
}
}
}
}
```

4
book/ru/src/migration.md Normal file
View file

@ -0,0 +1,4 @@
# Инструкции по миграции
В этом разделе описывается как мигрировать между различными версиями RTIC.
Можно также использовать для сравнения версий.

View file

@ -0,0 +1,48 @@
# Миграция с 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: () = {
// ...
};
```

View file

@ -0,0 +1,230 @@
# Миграция с 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) {
// ..
}
};
```

View file

@ -0,0 +1,208 @@
# Миграция с v0.5.x на v0.6.0
Этот раздел описывает как обновиться с версии v0.5.x на v0.6.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`-выражения для ресурсов (и т.п.) могут понадобиться.
## Перенос диспетчеров из `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
## 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 {
#[init]
fn init(_: init::Context) -> init::LateResources {
rtic::pend(Interrupt::UART0);
init::LateResources {}
}
// [еще код]
}
```
## Структура Resources - `#[resources]`
Ранее ресурсы RTIC должны были располагаться в структуре с именем "Resources":
``` rust
struct Resources {
// Ресурсы определены здесь
}
```
В RTIC v0.6.0 структура ресурсов аннотируется также, как и
`#[task]`, `#[init]`, `#[idle]`: атрибутом `#[resources]`
``` rust
#[resources]
struct Resources {
// Ресурсы определены здесь
}
```
На самом деле, имя структуры предоставлено на усмотрение разработчика:
``` rust
#[resources]
struct Whateveryouwant {
// Ресурсы определены здесь
}
```
будет работать так же хороршо.
## Вызов/планирование откуда угодно
С этой новой возвожностью, старый код, такой как:
``` 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` больше не нужны.
## Симметричные блокировки
Теперь RTIC использует симметричные блокировки, это значит, что метод `lock` нужно использовать для
всех доступов к ресурсам. Поскольку высокоприоритетные задачи имеют эксклюзивный доступ к ресурсу,
в старом коде можно было следующее:
``` 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, resources = [r])]
fn foo(cx: foo::Context) {
cx.resources.r.lock(|r| r = /* ... */);
}
#[task(resources = [r])]
fn bar(cx: bar::Context) {
cx.resources.r.lock(|r| r = /* ... */);
}
```
Заметьте, что скорость работы не изменяется благодаря оптимизациям LLVM, которые убирают ненужные блокировки.
---
## Дополнительно
### Внешние задачи
Как программные, так и аппаратные задачи теперь можно определять вне модуля `mod app`.
Ранее это было возможно только путем реализации обертки, вызывающей реализацию задачи.
Смотреть примеры `examples/extern_binds.rs` и `examples/extern_spawn.rs`.

View file

@ -1,12 +1,26 @@
<div align="center"><img width="300" height="300" src="RTIC.svg"></div>
<div style="font-size: 6em; font-weight: bolder;" align="center">RTIC</div>
<h1 align="center">Real-Time Interrupt-driven Concurrency</h1> <h1 align="center">Real-Time Interrupt-driven Concurrency</h1>
<p align="center">Конкурентный фреймворк для создания систем реального времени</p> <p align="center">Конкурентный фреймворк для создания систем реального времени</p>
# Введение # Введение
Эта книга содержит документацию уровня пользователя фреймворком Real-Time Interrupt-driven Concurrency Эта книга содержит документацию пользовательского уровня о фреймворке Real-Time Interrupt-driven Concurrency
(RTIC). Описание API можно найти [здесь](../../api/rtic/index.html). (RTIC). Справочник по API можно найти [здесь](../../api/).
{{#include README_RU.md:5:44}} Также известен как Real-Time For the Masses.
{{#include README_RU.md:50:}} <!--Оригинал данного руководства на [английском].-->
<!--[английском]: ../en/index.html-->
Это документация по RTIC версии v0.6.x; за документацией по другим версиям:
* v0.5.x [сюда](/0.5).
* v0.4.x [сюда](/0.4).
{{#include ../../../README_ru.md:7:45}}
{{#include ../../../README_ru.md:51:}}