aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README_ru.md118
-rw-r--r--book/ru/book.toml6
-rw-r--r--book/ru/src/README_RU.md94
-rw-r--r--book/ru/src/RTIC.svg109
-rw-r--r--book/ru/src/SUMMARY.md17
-rw-r--r--book/ru/src/by-example.md25
-rw-r--r--book/ru/src/by-example/app.md159
-rw-r--r--book/ru/src/by-example/new.md45
-rw-r--r--book/ru/src/by-example/resources.md177
-rw-r--r--book/ru/src/by-example/singletons.md26
-rw-r--r--book/ru/src/by-example/tasks.md113
-rw-r--r--book/ru/src/by-example/timer-queue.md122
-rw-r--r--book/ru/src/by-example/tips.md142
-rw-r--r--book/ru/src/by-example/types-send-sync.md50
-rw-r--r--book/ru/src/internals.md17
-rw-r--r--book/ru/src/internals/access.md158
-rw-r--r--book/ru/src/internals/ceilings.md93
-rw-r--r--book/ru/src/internals/critical-sections.md521
-rw-r--r--book/ru/src/internals/interrupt-configuration.md72
-rw-r--r--book/ru/src/internals/late-resources.md114
-rw-r--r--book/ru/src/internals/non-reentrancy.md79
-rw-r--r--book/ru/src/internals/tasks.md400
-rw-r--r--book/ru/src/internals/timer-queue.md373
-rw-r--r--book/ru/src/migration.md4
-rw-r--r--book/ru/src/migration/migration_rtic.md48
-rw-r--r--book/ru/src/migration/migration_v4.md230
-rw-r--r--book/ru/src/migration/migration_v5.md208
-rw-r--r--book/ru/src/preface.md22
28 files changed, 3116 insertions, 426 deletions
diff --git a/README_ru.md b/README_ru.md
new file mode 100644
index 00000000..918d03e3
--- /dev/null
+++ b/README_ru.md
@@ -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, будет лицензирован по условиям,
+указанным выше, без каких-либо дополнительных условий.
diff --git a/book/ru/book.toml b/book/ru/book.toml
index 6c3a5e64..98c5bf3f 100644
--- a/book/ru/book.toml
+++ b/book/ru/book.toml
@@ -1,5 +1,9 @@
[book]
-authors = ["Jorge Aparicio"]
+authors = ["Jorge Aparicio, Per Lindgren and The Real-Time Interrupt-driven Concurrency developers"]
multilingual = false
src = "src"
title = "Real-Time Interrupt-driven Concurrency"
+
+[output.html]
+git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic"
+git-repository-icon = "fa-github"
diff --git a/book/ru/src/README_RU.md b/book/ru/src/README_RU.md
deleted file mode 100644
index 4cc24e68..00000000
--- a/book/ru/src/README_RU.md
+++ /dev/null
@@ -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, лицензируется, как указано выше, без каких-либо дополнительных условий.
diff --git a/book/ru/src/RTIC.svg b/book/ru/src/RTIC.svg
new file mode 100644
index 00000000..1c65cba0
--- /dev/null
+++ b/book/ru/src/RTIC.svg
@@ -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>
diff --git a/book/ru/src/SUMMARY.md b/book/ru/src/SUMMARY.md
index 7df745eb..a387c9f0 100644
--- a/book/ru/src/SUMMARY.md
+++ b/book/ru/src/SUMMARY.md
@@ -1,16 +1,25 @@
# Summary
[Введение](./preface.md)
+
- [RTIC в примерах](./by-example.md)
- [Атрибут `app`](./by-example/app.md)
- [Ресурсы](./by-example/resources.md)
- - [Задачи](./by-example/tasks.md)
+ - [Программные задачи](./by-example/tasks.md)
- [Очередь таймера](./by-example/timer-queue.md)
- - [Одиночки](./by-example/singletons.md)
- [Типы, Send и Sync](./by-example/types-send-sync.md)
- [Создание нового проекта](./by-example/new.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)
- - [Ceiling analysis](./internals/ceilings.md)
- - [Диспетчер задач](./internals/tasks.md)
+ - [Настройка прерываний](./internals/interrupt-configuration.md)
+ - [Нереентерабельнось](./internals/non-reentrancy.md)
+ - [Контроль доступа](./internals/access.md)
+ - [Поздние ресурсы](./internals/late-resources.md)
+ - [Критические секции](./internals/critical-sections.md)
+ - [Анализ приоритетов](./internals/ceilings.md)
+ - [Программные задачи](./internals/tasks.md)
- [Очередь таймера](./internals/timer-queue.md)
diff --git a/book/ru/src/by-example.md b/book/ru/src/by-example.md
index 0e0fde2f..027716f1 100644
--- a/book/ru/src/by-example.md
+++ b/book/ru/src/by-example.md
@@ -1,16 +1,23 @@
# 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, Вы можете найти в [the embedded Rust book].
+Для запуска примеров на вашем ПК, вам понадобится программа `qemu-system-arm`.
+В [the embedded Rust book] есть инструкции по настройке среды для эмбеддед разработке,
+в том числе QEMU.
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html
+
+## Примеры из реальной жизни
+
+Ниже представлены примеры использования RTIC (RTFM) в реальных проектах.
+
+### RTFM V0.4.2
+
+- [etrombly/sandbox](https://github.com/etrombly/sandbox/tree/41d423bcdd0d8e42fd46b79771400a8ca349af55). Аппаратный дзэн-сад, рисующий картинки на песке. Картинки передаются по последовательному порту с помощью G-кода.
diff --git a/book/ru/src/by-example/app.md b/book/ru/src/by-example/app.md
index 04dd5b24..628819ad 100644
--- a/book/ru/src/by-example/app.md
+++ b/book/ru/src/by-example/app.md
@@ -1,77 +1,84 @@
-# The `app` attribute
+# Атрибут `app`
-Это наименьшая возможная программа на RTIC:
+Это простейшая из возможных программ на RTIC:
``` rust
{{#include ../../../../examples/smallest.rs}}
```
Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут
-нужно применять к `const`-элементам, содержащим элементы. Атрибут `app` имеет
-обязательный аргумент `device`, в качестве значения которому передается *путь*.
-Этот путь должен указывать на библиотеку *устройства*, сгенерированную с помощью
-[`svd2rust`] **v0.14.x**. Атрибут `app` развернется в удобную точку входа,
-поэтому нет необходимости использовать атрибут [`cortex_m_rt::entry`].
+должен применяться к элементу `mod`. Атрибут `app` имеет обязательный аргумент `device`,
+который принимает *путь* как значение. Это должен быть полный путь, указывающий на
+*крейт доступа к периферии* (PAC), сгенерированный с помощью [`svd2rust`] версии **v0.14.x**
+или новее. Более подробно в разделе [Создание нового проекта](./new.md).
+
+Атрибут `app` будет раскрыт в подходящую точку входа программы, поэтому
+атрибут [`cortex_m_rt::entry`] не нужен.
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
[`svd2rust`]: https://crates.io/crates/svd2rust
[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
-> **ОТСТУПЛЕНИЕ**: Некоторые из вас удивятся, почему мы используем ключевое слово `const` как
-> модуль, а не правильное `mod`. Причина в том, что использование атрибутов на
-> модулях требует feature gate, который требует ночную сборку. Чтобы заставить
-> RTIC работать на стабильной сборке, мы используем вместо него слово `const`.
-> Когда большая часть макросов 1.2 стабилизируются, мы прейдем от `const` к `mod` и в конце концов в атрибуту уровне приложения (`#![app]`).
-
## `init`
-Внутри псевдо-модуля атрибут `app` ожидает найти функцию инициализации, обозначенную
-атрибутом `init`. Эта функция должна иметь сигнатуру `[unsafe] fn()`.
+Внутри модуля `app` атрибут ожидает найти функцию инициализации, помеченную
+атрибутом `init`. Эта функция должна иметь сигнатуру
+`fn(init::Context) [-> init::LateResources]` (возвращаемый тип нужен не всегда).
-Эта функция инициализации будет первой частью запускаемого приложения.
-Функция `init` запустится *с отключенными прерываниями* и будет иметь эксклюзивный
-доступ к периферии Cortex-M и специфичной для устройства периферии через переменные
-`core` and `device`, которые внедряются в область видимости `init` атрибутом `app`.
-Не вся периферия Cortex-M доступна в `core`, потому что рантайм RTIC принимает владение
-частью из неё -- более подробно см. структуру [`rtic::Peripherals`].
+Эта функция инициализации будет первой частью программы, выполняемой при запуске.
+Функция `init` будет запущена *с отключенными прерываниями* и будет иметь эксклюзивный доступ
+к Cortex-M, в котором токен `bare_metal::CriticalSection` доступен как `cs`.
+Опционально, устройство-специфичные периферия доступна через поля `core` и `device` структуры
+`init::Context`.
-Переменные `static mut`, определённые в начале `init` будут преобразованы
-в ссылки `&'static mut` с безопасным доступом.
+`static mut` переменные, определенные в начале `init` будут преобразованы в
+`&'static mut` ссылки, безопасные для доступа. Обратите внимание, данная возможность может
+быть удалена в следующем релизе, см. `task_local` ресурсы.
[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html
-Пример ниже показывает типы переменных `core` и `device` и
-демонстрирует безопасный доступ к переменной `static mut`.
+Пример ниже показывает типы полей `core`, `device` и `cs`, и демонстрирует
+безопасный доступ к `static mut` переменной. Поле `device` доступно только
+когда аргумент `peripherals` установлен в `true` (по умолчанию).
+В редких случаях, когда вы захотите создать приложение с минимальным потреблением ресурсов,
+можно явно установить `peripherals` в `false`.
``` rust
{{#include ../../../../examples/init.rs}}
```
-Запуск примера напечатает `init` в консоли и завершит процесс QEMU.
+Запуск примера напечатате `init` в консоли, а затем завершит процесс QEMU.
``` console
$ cargo run --example init
-{{#include ../../../../ci/expected/init.run}}```
+{{#include ../../../../ci/expected/init.run}}
+```
## `idle`
-Функция, помеченная атрибутом `idle` может присутствовать в псевдо-модуле
-опционально. Эта функция используется как специальная *задача ожидания* и должна иметь
-сигнатуру `[unsafe] fn() - > !`.
+Функцию, помеченную атрибутом `idle` может опционально добавить в модуль.
+Эта функция используется как специальная *задача ожидания* и должна иметь сигнатуру
+`fn(idle::Context) - > !`.
-Когда она присутствует, рантайм запустит задачу `idle` после `init`. В отличие от
-`init`, `idle` запустится *с включенными прерываниями* и не может завершиться,
-поэтому будет работать бесконечно.
+Если она присутствует, задача `idle` будет запущена после `init`. В отличие от
+`init`, `idle` будет запущена *с включенными прерываниями* и она не может вернуть результат,
+а значит должна работать вечно.
-Когда функция `idle` не определена, рантайм устанавливает бит [SLEEPONEXIT], после чего
-отправляет микроконтроллер в состояние сна после выполнения `init`.
+Если функция `idle` не определена, среда вполнения устанавливает бит [SLEEPONEXIT], а затем
+отправляет микроконтроллер в сон после запуска `init`.
[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
{{#include ../../../../examples/idle.rs}}
@@ -79,23 +86,75 @@ $ cargo run --example init
``` console
$ cargo run --example idle
-{{#include ../../../../ci/expected/idle.run}}```
+{{#include ../../../../ci/expected/idle.run}}
+```
-## `interrupt` / `exception`
+## Аппаратные задачи
-Как Вы бы сделали с помощью библиотеки `cortex-m-rt`, Вы можете использовать атрибуты
-`interrupt` и `exception` внутри псевдо-модуля `app`, чтобы определить обработчики
-прерываний и исключений. В RTIC, мы называем обработчики прерываний и исключений
-*аппаратными* задачами.
+Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут `#[task]`,
+который можно применять к функциям. Этот атрибут берет аргумент `binds`, чье значение -
+это имя прерывания, которому будет назначен обработчик;
+функция, декорированная этим атрибутом становится обработчиком прерывания.
+В фреймворке такие типы задач именуются *аппаратными*, потому что они начинают
+выполняться в ответ на аппаратное событие.
+
+Пример ниже демонстрирует использование атрибута `#[task]`, чтобы объявить
+обработчик прерывания. Как и в случае с `#[init]` и `#[idle]` локальные `static
+mut` переменные безопасны для использования с аппаратной задачей.
``` rust
-{{#include ../../../../examples/interrupt.rs}}
+{{#include ../../../../examples/hardware.rs}}
```
``` console
-$ cargo run --example interrupt
-{{#include ../../../../ci/expected/interrupt.run}}```
+$ cargo run --example hardware
+{{#include ../../../../ci/expected/hardware.run}}
+```
+
+До сих пор все программы на RTIC, которые мы видели, не отличались от программ,
+которые можно написать, используя лишь крейт `cortex-m-rt`. С этого момента мы
+начинаем представлять возможности, уникальные для RTIC.
+
+## Приоритеты
+
+Статический приоритет каждого обработчика можно оределить в атрибуте `task`, используя
+аргумент `priority`. Задачи могут иметь приоритет в диапазоне `1..=(1 << NVIC_PRIO_BITS)`,
+где `NVIC_PRIO_BITS` - это константа, определенная в крейте `устройства`.
+Когда аргумент `priority` не указан, предполагается, что приоритет равен `1`.
+Задача `idle` имеет ненастраиваемый приоритет `0`, наименьший из возможных.
+
+> Более высокое значение означает более высокий приоритет в RTIC, что противоположно тому,
+> что указано в периферии NVIC Cortex-M.
+> Точнее, это значит, что число `10` обозначает приоритет **выше**, чем число `9`.
+
+Когда несколько задач готовы к запуску, задача с самым большим статическим
+приоритетом будет запущена первой. Приоритезацию задач можно рассматривать по
+такому сценарию: сигнал прерывания приходит во время выполнения задачи с низким приоритетом;
+сигнал переключает задачу с высоким приоритетом в режим ожидания.
+Разница в приоритетах приводи к тому, что задача с высоким приоритетом вытесняет задачу с низким:
+выполнение задачи с низким приоритетом замораживается и задача с высоким приоритетом выполняется,
+пока не будет завершена. Как только задача с высоким приоритетом будет остановлена,
+продолжится выполнение задачи с низким приоритетом.
+
+Следующий пример демонстрирует диспетчеризацию на основе приоритетов задач.
+
+``` rust
+{{#include ../../../../examples/preempt.rs}}
+```
+
+``` console
+$ cargo run --example preempt
+{{#include ../../../../ci/expected/preempt.run}}
+```
-До сих пор программы RTIC, которые мы видели не отличались от программ, которые
-можно написать, используя только библиотеку `cortex-m-rt`. В следующем разделе
-мы начнем знакомиться с функционалом, присущим только RTIC.
+Заметьте, что задача `gpiob` *не* вытесняет задачу `gpioc`, потому что ее приоритет
+*такой же*, как и у `gpioc`. Однако, как только `gpioc` возвращает результат,
+выполненяется задача `gpiob`, как более приоритетная по сравнению с `gpioa`.
+Выполнение `gpioa` возобновляется только после выхода из `gpiob`.
+
+Еще одно замечание по поводу приоритетов: выбор приоритета большего, чем поддерживает устройство
+(а именно `1 << NVIC_PRIO_BITS`) приведет к ошибке компиляции.
+Из-за ограничений языка, сообщение об ошибке далеко от понимания:
+вам скажут что-то похожее на "evaluation of constant value failed", а указатель на ошибку
+*не* покажет на проблемное значение прерывания --
+мы извиняемся за это!
diff --git a/book/ru/src/by-example/new.md b/book/ru/src/by-example/new.md
index cba84c16..fcf52370 100644
--- a/book/ru/src/by-example/new.md
+++ b/book/ru/src/by-example/new.md
@@ -16,19 +16,19 @@ $ cargo generate \
$ # следуйте остальным инструкциям
```
-2. Добавьте крейт устройства, сгенерированный с помощью [`svd2rust`] **v0.14.x**,
-или библиотеку отладочной платы, у которой в зависимостях одно из устройств.
+2. Добавьте крейт доступа к периферии (PAC), сгенерированный с помощью[`svd2rust`]
+ **v0.14.x**, или крейт отладочной платы, у которой в зависимостях один из таких PAC'ов.
Убедитесь, что опция `rt` крейта включена.
[`svd2rust`]: https://crates.io/crates/svd2rust
-В этом примере я покажу использование крейта устройства [`lm3s6965`].
+В этом примере я буду использовать крейт устройства [`lm3s6965`].
Эта библиотека не имеет Cargo-опции `rt`; эта опция всегда включена.
[`lm3s6965`]: https://crates.io/crates/lm3s6965
Этот крейт устройства предоставляет линковочный скрипт с макетом памяти
-целевого устройства, поэтому `memory.x` и `build.rs` не нужно удалять.
+целевого устройства, поэтому `memory.x` и `build.rs` нужно удалить.
``` console
$ cargo add lm3s6965 --vers 0.1.3
@@ -36,24 +36,40 @@ $ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs
```
-3. Добавьте библиотеку `cortex-m-rtic` как зависимость, и если необходимо,
-включите опцию `timer-queue`.
+3. Добавьте крейт `cortex-m-rtic` как зависимость.
``` 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
-$ curl \
- -L https://github.com/japaric/cortex-m-rtic/raw/v0.4.0-beta.1/examples/init.rs \
- > src/main.rs
+{{#include ../../../../examples/init.rs}}
+```
+
+Пример `init` использует устройство `lm3s6965`. Не забудьте настроить аргумент `device`
+в атрибуте макроса app так, чтобы он соответствовал пути к PAC-крейту, если он отличается,
+а также добавить перифериб и другие аргументы если необходимо.
+Несмотря на то, что в программе могут использоваться псевдонимы типов,
+здесь необходимо указать полный путь (из корня крейта). Для многих устройств,
+есть общий подход в крейтах реализации HAL (с псевдонимом `hal`) и крейтах поддержки
+отладочных плат реекспортиорвать PAC как `pac`, что приводит нас к образцу, аналогичному
+приведенному ниже:
+
+```rust
+use abcd123_hal as hal;
+//...
+
+#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
+mod app { /*...*/ }
```
-Этот пример зависит от библиотеки `panic-semihosting`:
+Пример `init` также зависит от крейта `panic-semihosting`:
``` console
$ cargo add panic-semihosting
@@ -64,4 +80,5 @@ $ cargo add panic-semihosting
``` console
$ # ПРИМЕЧАНИЕ: Я раскомментировал опцию `runner` в `.cargo/config`
$ cargo run
-{{#include ../../../../ci/expected/init.run}}```
+{{#include ../../../../ci/expected/init.run}}
+```
diff --git a/book/ru/src/by-example/resources.md b/book/ru/src/by-example/resources.md
index b53ef40e..70f798d2 100644
--- a/book/ru/src/by-example/resources.md
+++ b/book/ru/src/by-example/resources.md
@@ -1,22 +1,27 @@
-## Ресурсы
-
-Одно из ограничений атрибутов, предоставляемых библиотекой `cortex-m-rt` является
-то, что совместное использование данных (или периферии) между прерываниями,
-или прерыванием и функцией `init`, требуют `cortex_m::interrupt::Mutex`, который
-*всегда* требует отключения *всех* прерываний для доступа к данным. Отключение всех
-прерываний не всегда необходимо для безопасности памяти, но компилятор не имеет
-достаточно информации, чтобы оптимизировать доступ к разделяемым данным.
-
-Атрибут `app` имеет полную картину приложения, поэтому может оптимизировать доступ к
-`static`-переменным. В RTIC мы обращаемся к `static`-переменным, объявленным внутри
-псевдо-модуля `app` как к *ресурсам*. Чтобы получить доступ к ресурсу, контекст
-(`init`, `idle`, `interrupt` или `exception`) должен сначала определить
-аргумент `resources` в соответствующем атрибуте.
-
-В примере ниже два обработчика прерываний имеют доступ к одному и тому же ресурсу.
-Никакого `Mutex` в этом случае не требуется, потому что оба обработчика запускаются
-с одним приоритетом и никакого вытеснения быть не может.
-К ресурсу `SHARED` можно получить доступ только из этих двух прерываний.
+# Ресурсы
+
+Фреймворк предоставляет абстракцию для разделения данных между любыми контекстами,
+с которыми мы встречались в предыдущей главе (задачами-обработчиками, `init` и `idle`): ресурсы.
+
+Ресурсы - это данные, видимые только функциями, определенными внутри модуля `#[app]`.
+Фреймворк дает пользователю полный контроль за тем, какой контекст может
+получить доступ к какому ресурсу.
+
+Все ресурсы определены в одной структуре внутри модуля `#[app]`.
+Каждое поле структуры соответствует отдельному ресурсу.
+`struct`-ура должна быть аннотирована следующим атрибутом: `#[resources]`.
+
+Ресурсам могут быть опционально даны начальные значения с помощью атрибута `#[init]`.
+Ресурсы, которым не передано начально значение, называются
+*поздними* ресурсами, более детально они описаны в одном из разделов на этой странице.
+
+Каждый контекс (задача-обработчик, `init` или `idle`) должен указать ресурсы, к которым
+он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя
+аргумент `resources`. Этот аргумент принимает список имен ресурсов в качестве значения.
+Перечисленные ресурсы становятся доступны в контексте через поле `resources` структуры `Context`.
+
+Пример программы, показанной ниже содержит два обработчика прерывания, которые разделяют
+доступ к ресурсу под названием `shared`.
``` rust
{{#include ../../../../examples/resource.rs}}
@@ -27,41 +32,36 @@ $ cargo run --example resource
{{#include ../../../../ci/expected/resource.run}}
```
-## Приоритеты
+Заметьте, что к ресурсу `shared` нельзя получить доступ из `idle`. Попытка сделать это
+приведет к ошибке компиляции.
-Приоритет каждого прерывания можно определить в атрибутах `interrupt` и `exception`.
-Невозможно установить приоритет любым другим способом, потому что рантайм
-забирает владение прерыванием `NVIC`; также невозможно изменить приоритет
-обработчика / задачи в рантайме. Благодаря этому ограничению у фреймворка
-есть знание о *статических* приоритетах всех обработчиков прерываний и исключений.
+## `lock`
-Прерывания и исключения могут иметь приоритеты в интервале `1..=(1 << NVIC_PRIO_BITS)`,
-где `NVIC_PRIO_BITS` - константа, определённая в библиотеке `device`.
-Задача `idle` имеет приоритет `0`, наименьший.
+Критические секции необходимы для разделения изменяемых данных таким образом,
+чтобы избежать гонок данных.
-Ресурсы, совместно используемые обработчиками, работающими на разных приоритетах,
-требуют критических секций для безопасности памяти. Фреймворк проверяет, что
-критические секции используются, но *только где необходимы*: например,
-критические секции не нужны для обработчика с наивысшим приоритетом, имеющим
-доступ к ресурсу.
+Поле `resources`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого
+ресурса, доступного задаче.
-API критической секции, предоставляемое фреймворком RTIC (см. [`Mutex`]),
-основано на динамических приоритетах вместо отключения прерываний. Из этого следует,
-что критические секции не будут допускать *запуск некоторых* обработчиков,
-включая все соперничающие за ресурс, но будут позволять запуск обработчиков с
-большим приоритетом не соперничащих за ресурс.
+Единственный метод этого трейта, [`lock`], запускает свой аргумент-замыкание в критической секции.
[`Mutex`]: ../../../api/rtic/trait.Mutex.html
+[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
-В примере ниже у нас есть 3 обработчика прерываний с приоритетами от одного
-до трех. Два обработчика с низким приоритетом соперничают за ресурс `SHARED`.
-Обработчик с низшим приоритетом должен заблокировать ([`lock`]) ресурс
-`SHARED`, чтобы получить доступ к его данным, в то время как обработчик со
-средним приоритетом может напрямую получать доступ к его данным. Обработчик
-с наивысшим приоритетом может свободно вытеснять критическую секцию,
-созданную обработчиком с низшим приоритетом.
+Критическая секция, создаваемая интерфейсом `lock` основана на динамических приоритетах:
+она временно повышает динамический приоритет контекста до *максимального* приоритета,
+что не дает другим задачам возможности вытеснить критическую секцию.
+Этот протокол синхронизации известен как [Протокол немедленного максимального приоритета
+(ICPP)][icpp], и компилируется диспетчером RTIC с [Политикой ресурсов стека(SRP)][srp].
-[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
+[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
+[srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy
+
+В примере ниже у нас есть три обработчика прерываний с приоритетами от одного до трех.
+Два из обработчиков с более низким приоритетом соревнуются за ресурс `shared`,
+поэтому должны блокировать доступа к данным ресурса.
+Обработчик с наивысшим приоритетом, который не имеет доступа к ресурсу `shared`,
+может свободно вытеснять критическую секцию, созданную обработчиком с низким приоритетом.
``` rust
{{#include ../../../../examples/lock.rs}}
@@ -69,26 +69,32 @@ API критической секции, предоставляемое фрей
``` console
$ cargo run --example lock
-{{#include ../../../../ci/expected/lock.run}}```
+{{#include ../../../../ci/expected/lock.run}}
+```
+
+## Множественное блокировка
+
+Это расширение к `lock`, чтобы уменьшить количесво отступов, блокируемые ресурсы можно объединять в кортежи.
+Следующий пример это демонстрирует:
+
+``` rust
+{{#include ../../../../examples/multilock.rs}}
+```
## Поздние ресурсы
-В отличие от обычных `static`-переменных, к которым должно быть присвоено
-начальное значение, ресурсы можно инициализировать в рантайме.
-Мы называем ресурсы, инициализируемые в рантайме *поздними*. Поздние ресурсы
-полезны для *переноса* (как при передаче владения) периферии из `init` в
-обработчики прерываний и исключений.
+Поздние ресурсы - такие ресурсы, которым не передано начальное значение во время компиляции
+с помощью атрибута `#[init]`, но которые вместо этого инициализируются во время выполнения
+с помощью значений из структуры `init::LateResources`, возвращаемой функцией `init`.
-Поздние ресурсы определяются как обычные ресурсы, но им присваивается начальное
-значение `()` (the unit value). `init` должен вернуть начальные значения для
-всех поздних ресурсов, упакованные в структуру типа `init::LateResources`.
+Поздние ресурсы полезны, например, для *move* (передача владения) периферии,
+инициализированной в `init`, в задачи.
-В примере ниже использованы поздние ресурсы, чтобы установить неблокированный,
-односторонний канал между обработчиком прерывания `UART0` и функцией `idle`.
-Очередь типа один производитель-один потребитель [`Queue`] использована как канал.
-Очередь разделена на элементы потребителя и поизводителя в `init` и каждый элемент
-расположен в отдельном ресурсе; `UART0` владеет ресурсом произодителя, а `idle`
-владеет ресурсом потребителя.
+Пример ниже использует поздние ресурсы, чтобы установить неблокируемый односторонний канал
+между обработчиком прерывания `UART0` и задачей `idle`. Для канала использована очередь типа
+один производитель-один потребитель [`Queue`]. Структура очереди разделяется на потребителя
+и производителя в `init`, а затем каждая из частей располагается в отдельном ресурсу;
+`UART0` владеет ресурсом производителя, а `idle` владеет ресурсом потребителя.
[`Queue`]: ../../../api/heapless/spsc/struct.Queue.html
@@ -98,25 +104,46 @@ $ cargo run --example lock
``` console
$ cargo run --example late
-{{#include ../../../../ci/expected/late.run}}```
+{{#include ../../../../ci/expected/late.run}}
+```
+
+## Только разделяемый доступ
+
+По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (`&mut-`) к ресурсам,
+но возможно указать, что задаче достаточен разделяемый доступ (`&-`) к ресурсы с помощью синтакисиса
+`&resource_name` в списке `resources`.
-## `static`-ресурсы
+Преимущество указания разделяемого досупа (`&-`) к ресурсу в том, что для доступа к ресурсу
+не нужна блокировка, даже если за ресурс соревнуются несколько задач, запускаемые с
+разными приоритетами. Недостаток в том, что задача получает только разделяемую ссылку (`&-`)
+на ресурс, и ограничена операциями, возможными с ней, но там, где разделяемой ссылки достаточно,
+такой подход уменьшает количесво требуемых блокировок.
+В дополнение к простым неизменяемым данным, такой разделяемый доступ может быть полезен для
+ресурсов, безопасно реализующих внутреннюю мутабельность с самоблокировкой или атомарными операциями.
-Переменные типа `static` также можно использовать в качестве ресурсов. Задачи
-могут получать только (разделяемые) `&` ссылки на ресурсы, но блокировки не
-нужны для доступа к данным. Вы можете думать о `static`-ресурсах как о простых
-`static`-переменных, которые можно инициализировать в рантайме и иметь лучшие
-правила видимости: Вы можете контролировать, какие задачи получают доступ к
-переменной, чтобы переменная не была видна всем фунциям в область видимости,
-где она была объявлена.
+Заметьте, что в этом релизе RTIC невозможно запросить и эксклюзивный доступ (`&mut-`)
+и разделяемый (`&-`) для *одного и того же* ресурса из различных задач.
+Попытка это сделать приведет к ошибке компиляции.
-В примере ниже ключ загружен (или создан) в рантайме, а затем использован в двух
-задачах, запущенных на разных приоритетах.
+В примере ниже ключ (например криптографический ключ) загружается (или создается) во время выполнения,
+а затем используется двумя задачами, запускаемымы с различным приоритетом без каких-либо блокировок.
``` rust
-{{#include ../../../../examples/static.rs}}
+{{#include ../../../../examples/only-shared-access.rs}}
```
``` console
-$ cargo run --example static
-{{#include ../../../../ci/expected/static.run}}```
+$ cargo run --example only-shared-access
+{{#include ../../../../ci/expected/only-shared-access.run}}
+```
+
+## Неблокируемый доступ к изменяемым ресурсам
+
+Есть две других возможности доступа к ресурсам
+
+* `#[lock_free]`: могут быть несколько задач с одинаковым приоритетом,
+ получающие доступ к ресурсу без критических секций. Так как задачи с
+ одинаковым приоритетом никогда не могут вытеснить друг друга, это безопасно.
+* `#[task_local]`: в этом случае должна быть только одна задача, использующая
+ этот ресурс, так же как локальный `static mut` ресурс задачи, но (опционально) устанавливаемая с в init.
+
diff --git a/book/ru/src/by-example/singletons.md b/book/ru/src/by-example/singletons.md
deleted file mode 100644
index d6d60ef8..00000000
--- a/book/ru/src/by-example/singletons.md
+++ /dev/null
@@ -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}}```
diff --git a/book/ru/src/by-example/tasks.md b/book/ru/src/by-example/tasks.md
index 37828043..3c99d00e 100644
--- a/book/ru/src/by-example/tasks.md
+++ b/book/ru/src/by-example/tasks.md
@@ -1,22 +1,20 @@
# Программные задачи
-RTIC обрабатывает прерывания и исключения как *аппаратные* задачи. Аппаратные
-задачи могут вызываться устройством в ответ на события, такие как нажатие кнопки.
-RTIC также поддерживает *программные* задачи, порождаемые программой из любого
-контекста выполнения.
+В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события,
+RTIC также поддерживает *программные* задачи, которые могут порождаться
+приложением из любого контекста выполнения.
-Программным задачам также можно назначать приоритет и диспетчеризовать из
-обработчиков прерываний. RTIC требует определения свободных прерываний в блоке
-`extern`, когда используются программные задачи; эти свободные прерывания будут использованы, чтобы диспетчеризовать программные задачи. Преимущество программных
-задач перед аппаратными в том, что на один обработчик прерывания можно назначить
-множество задач.
+Программным задачам можно также назначать приоритет и, под капотом, они
+диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные
+прерывания, были указаны в аргументе `dispatchers` модуля `app`, если используются
+программные задачи; часть из этих свободных прерываний будут использованы для
+управления программными задачами. Преимущество программных задач над аппаратными
+в том, что множество задач можно назначить на один обработчик прерывания.
-Программные задачи определяются заданием функциям атрибута `task`. Чтобы было
-возможно вызывать программные задачи, имя задачи нужно передать в аргументе
-`spawn` контекста атрибута (`init`, `idle`, `interrupt`, etc.).
+Программные задачи также определяются атрибутом `task`, но аргумент `binds` опускается.
-В примере ниже продемонстрированы три программных задачи, запускаемые на 2-х
-разных приоритетах. Трем задачам назначены 2 обработчика прерываний.
+Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах.
+Три программные задачи привязаны к 2-м обработчикам прерываний.
``` rust
{{#include ../../../../examples/task.rs}}
@@ -24,15 +22,16 @@ RTIC также поддерживает *программные* задачи,
``` console
$ cargo run --example task
-{{#include ../../../../ci/expected/task.run}}```
+{{#include ../../../../ci/expected/task.run}}
+```
## Передача сообщений
-Другое преимущество программных задач - возможность передавать сообщения задачам
-во время их вызова. Тип полезной нагрузки сообщения должен быть определен в
-сигнатуре обработчика задачи.
+Другое преимущество программной задачи в том, что задачам можно передать сообщения
+в момент их запуска. Тип передаваемого сообщения должен быть определен в сигнатуре
+задачи-обработчика.
-Пример ниже демонстрирует три задачи, две из которых ожидают сообщения.
+Пример ниже демонстрирует три задачи, две из которых ожидают сообщение.
``` rust
{{#include ../../../../examples/message.rs}}
@@ -40,19 +39,23 @@ $ cargo run --example task
``` console
$ cargo run --example message
-{{#include ../../../../ci/expected/message.run}}```
+{{#include ../../../../ci/expected/message.run}}
+```
-## Ёмкость
+## Вместимость
-Диспетчеры задач *не* используют динамическое выделение памяти. Память
-необходимая для размещения сообщений, резервируется статически. Фреймворк
-зарезервирует достаточно памяти для каждого контекста, чтобы можно было вызвать
-каждую задачу как минимум единожды. Это разумно по умолчанию, но
-"внутреннюю" ёмкость каждой задачи можно контролировать используя аргумент
-`capacity` атрибута `task`.
+RTIC *не* производит никакого рода аллокаций памяти в куче.
+Память, необходимая для размещения сообщения резервируется статически.
+По-умолчанию фреймворк минимизирует выделение памяти программой таким образом,
+что каждая задача имеет "вместимость" для сообщения равную 1:
+это значит, что не более одного сообщения можно передать задаче перед тем, как
+у нее появится возможность к запуску. Это значение по-умолчанию можно
+изменить для каждой задачи, используя аргумент `capacity`.
+Этот аргумент принимает положительное целое, которое определяет как много
+сообщений буфер сообщений задачи может хранить.
-В примере ниже установлена ёмкость программной задачи `foo` на 4. Если ёмкость
-не определена, тогда второй вызов `spawn.foo` в `UART0` вызовет ошибку.
+Пример ниже устанавливает вместимость программной задачи `foo` равной 4.
+Если вместимость не установить, второй вызов `spawn.foo` в `UART0` приведет к ошибке (панике).
``` rust
{{#include ../../../../examples/capacity.rs}}
@@ -60,4 +63,54 @@ $ cargo run --example message
``` console
$ 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) {
+ // ..
+ }
+}
+```
diff --git a/book/ru/src/by-example/timer-queue.md b/book/ru/src/by-example/timer-queue.md
index 3c35e290..c8818d7d 100644
--- a/book/ru/src/by-example/timer-queue.md
+++ b/book/ru/src/by-example/timer-queue.md
@@ -1,57 +1,76 @@
# Очередь таймера
-Когда включена опция `timer-queue`, фреймворк RTIC включает
-*глобальную очередь таймера*, которую приложения могут использовать, чтобы
-*планировать* программные задачи на запуск через некоторое время в будущем.
-
-Чтобы была возможность планировать программную задачу, имя задачи должно
-присутствовать в аргументе `schedule` контекста атрибута. Когда задача
-планируется, момент ([`Instant`]), в который задачу нужно запустить, нужно передать
-как первый аргумент вызова `schedule`.
-
-[`Instant`]: ../../../api/rtic/struct.Instant.html
-
-Рантайм RTIC включает монотонный, растущий только вверх, 32-битный таймер,
-значение которого можно запросить конструктором `Instant::now`. Время ([`Duration`])
-можно передать в `Instant::now()`, чтобы получить `Instant` в будущем. Монотонный
-таймер отключен пока запущен `init`, поэтому `Instant::now()` всегда возвращает
-значение `Instant(0 /* циклов тактовой частоты */)`; таймер включается сразу перед
-включением прерываний и запуском `idle`.
-
-[`Duration`]: ../../../api/rtic/struct.Duration.html
-
-В примере ниже две задачи планируются из `init`: `foo` и `bar`. `foo` -
-запланирована на запуск через 8 миллионов тактов в будущем. Кроме того, `bar`
-запланирован на запуск через 4 миллиона тактов в будущем. `bar` запустится раньше
-`foo`, т.к. он запланирован на запуск первым.
-
-> **ВАЖНО**: Примеры, использующие API `schedule` или абстракцию `Instant`
-> **не** будут правильно работать на QEMU, потому что функциональность счетчика
-> тактов Cortex-M не реализована в `qemu-system-arm`.
+В отличие от интерфейса `spawn`, который немедленно передает программную задачу
+планировщику для немедленного запуска, интерфейс `schedule` можно использовать
+для планирования задачи к запуске через какое-то время в будущем.
+
+Чтобы использовать интерфейс `schedule`, предварительно должен быть определен
+монотонный таймер с помощью аргумента `monotonic` атрибута `#[app]`.
+Этот аргумент принимает путь к типу, реализующему трейт [`Monotonic`].
+Ассоциированный тип, `Instant`, этого трейта представляет метку времени в соответствущих
+единицах измерения и широко используется в интерфейсе `schedule` -- предлагается смоделировать
+этот тип позднее [один из таких есть в стандартной библиотеке][std-instant].
+
+Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов),
+разница двух `Instant`ов должна возвращать какой-то тип `Duration` (см. [`core::time::Duration`])
+и этот `Duration` должен реализовывать трейт `TryInto<u32>`.
+Реализация этого трейта должна конвертировать значение `Duration`, которое
+использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера
+(SYST)". Результат преобразований должен быть 32-битным целым.
+Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа.
+
+[`Monotonic`]: ../../../api/rtic/trait.Monotonic.html
+[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html
+[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
+
+Для целевых платформ ARMv7+ крейт `rtic` предоставляет реализацию `Monotonic`, основанную на
+встроенном CYCle CouNTer (CYCCNT). Заметьте, что это 32-битный таймер, работающий на
+частоте центрального процессора, и поэтому не подходит для отслеживания интервалов времени в секундах.
+
+Когда планируется задача, (определенный пользователем) `Instant`, в который задача должна быть
+выполнена, должен передаваться в качестве первого аргумента вызова `schedule`.
+
+К тому же, выбранный `monotonic` таймер, необходимо сконфигурировать и инициализировать в
+фазе работы `#[init]`. Заметьте, что *также* касается случая использования `CYCCNT`,
+предоставляемого крейтом `cortex-m-rtic`.
+
+Пример ниже планирует к выполнению две задачи из `init`: `foo` и `bar`. `foo` запланирована
+к запуску через 8 миллионов циклов в будущем. Далее, `bar` запланировано запустить через
+4 миллиона циклов в будущем. Таким образом, `bar` запустится до `foo`, так как и запланировано.
+
+> **DF:YJ**: Примеры, использующие интерфейс `schedule` или абстракцию `Instant`
+> **не будут** правильно работать на эмуляторе QEMU, поскольку счетчик циклов Cortex-M
+> функционально не был реализован в `qemu-system-arm`.
``` rust
{{#include ../../../../examples/schedule.rs}}
```
-Запуск программы на реальном оборудовании производит следующий вывод в консоли:
+Запусе программы на реальном оборудовании создает следующий вывод в консоли:
``` text
{{#include ../../../../ci/expected/schedule.run}}
```
+Когда интерфейс `schedule` используется, среда исполнения использует внутри
+обработчик прерываний `SysTick` и периферию системного таймера (`SYST`), поэтому ни
+тот ни другой нельзя использовать в программе. Это гарантируется изменением типа
+`init::Context.core` с `cortex_m::Peripherals` на `rtic::Peripherals`.
+Последняя структура содержит все поля из предыдущей кроме `SYST`.
+
## Периодические задачи
-Программные задачи имеют доступ к `Instant` в момент, когда были запланированы
-на запуск через переменную `scheduled`. Эта информация и API `schedule` могут
-быть использованы для реализации периодических задач, как показано в примере ниже.
+Программные задачи имеют доступ к моменту времени `Instant`, в который они были запланированы
+на выполнение переменной `scheduled`. Эта информация и интерфейс `schedule` можно использовать,
+чтобы реализовать периодические задачи, как показано ниже.
``` rust
{{#include ../../../../examples/periodic.rs}}
```
-Это вывод, произведенный примером. Заметьте, что есть смещение / колебание нуля
-даже если `schedule.foo` была вызвана в *конце* `foo`. Использование
-`Instant::now` вместо `scheduled` имело бы влияние на смещение / колебание.
+Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания
+даже несмотря на то, что `schedule.foo` была вызвана в *конце* `foo`. Использование
+`Instant::now` вместо `scheduled` вызвало бы дрейф / колебания.
``` text
{{#include ../../../../ci/expected/periodic.run}}
@@ -59,31 +78,30 @@
## Базовое время
-Для задач, планируемых из `init` мы имеем точную информацию о их планируемом
-(`scheduled`) времени. Для аппаратных задач нет `scheduled` времени, потому
-что эти задачи асинхронны по природе. Для аппаратных задач рантайм предоставляет
-время старта (`start`), которе отражает время, в которое обработчик прерывания
-был запущен.
+Для задач, вызываемых из `init` мы имеем точную информацию о их `scheduled` времени.
+Для аппаратных задач такого времени нет, поскольку они асинхронны по природе.
+Для аппаратных задач среда исполнения предоставляет время запуска (`start`), которое отражает
+время, в которое обработчик прерывания будет запущен.
-Заметьте, что `start` **не** равен времени возникновения события, вызвавшего
-задачу. В зависимости от приоритета задачи и загрузки системы время
-`start` может быть сильно отдалено от времени возникновения события.
+Заметьте, что `start` **не** равно времени прихода события, которое вызывает задачу.
+В зависимости от приоритета задачи и загрузки системы, время `start` может сильно отдалиться от
+времени прихода события.
-Какое по Вашему мнению будет значение `scheduled` для программных задач которые
-*вызываются*, вместо того чтобы планироваться? Ответ в том, что вызываемые
-задачи наследуют *базовое* время контекста, в котором вызваны. Бызовым для
-аппаратных задач является `start`, базовым для программных задач - `scheduled`
-и базовым для `init` - `start = Instant(0)`. `idle` на сомом деле не имеет
-базового времени но задачи, вызванные из него будут использовать `Instant::now()`
-как их базовое время.
+Какое по вашему мнению будет значение `scheduled` для программных задач, которые вызываются через
+`spawn` вместо планирования? Ответ в том, что вызываемые задачи наследуют
+*базовое* время того контекста, который их вызывает. Базовое время аппаратных задач -
+это их время `start`, базовое время программных задач - их время `scheduled`, а
+базовое время `init` - время старта системы, или нулевое
+(`Instant::zero()`). `idle` на самом деле не имеет базового времени, но задачи вызываемые из нее,
+используют `Instant::now()` в качестве базового.
-Пример ниже демонстрирует разное значение *базового времени*.
+Пример ниже демонстрирует разные смыслы *базового времени*.
``` rust
{{#include ../../../../examples/baseline.rs}}
```
-Запуск программы на реальном оборудовании произведет следующий вывод в консоли:
+Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
``` text
{{#include ../../../../ci/expected/baseline.run}}
diff --git a/book/ru/src/by-example/tips.md b/book/ru/src/by-example/tips.md
index 249e8f4d..cf66c4b7 100644
--- a/book/ru/src/by-example/tips.md
+++ b/book/ru/src/by-example/tips.md
@@ -2,10 +2,15 @@
## Обобщенное программирование (Generics)
-Ресурсы, совместно используемые двумя или более задачами, реализуют трейт `Mutex`
-во *всех* контекстах, даже в тех, где для доступа к данным не требуются
-критические секции. Это позволяет легко писать обобщенный код оперирующий
-ресурсами, который можно вызывать из различных задач. Вот такой пример:
+Все объекты, предоставляющие ресурысы реализуют трейт `rtic::Mutex`.
+Если ресурс не реализует его, можно обернуть его в новый тип [`rtic::Exclusive`],
+который реализует трейт `Mutex`. С помощью этого нового типа
+можно написать обобщенную функцию, которая работает с обобщенным ресурсом и
+вызывать его из различных задач, чтобы производить однотипные операции над
+похожим множеством ресурсов.
+Вот один такой пример:
+
+[`rtic::Exclusive`]: ../../../api/rtic/struct.Exclusive.html
``` rust
{{#include ../../../../examples/generics.rs}}
@@ -13,12 +18,29 @@
``` console
$ cargo run --example generics
-{{#include ../../../../ci/expected/generics.run}}```
+{{#include ../../../../ci/expected/generics.run}}
+```
+
+## Условная компиляция
+
+Вы можете использовать условную компиляцию (`#[cfg]`) на ресурсах (полях структуры
+`#[resources] struct Resources`) и задачах (элементах `fn`).
+Эффект использования атрибутов `#[cfg]` в том, что ресурс/ задача
+будут *не* доступны в соответствующих структурах `Context` если условие не выполняется.
+
+В примере ниже выводится сообщение каждый раз, когда вызывается задача `foo`, но только
+если программы скомпилирова с профилем `dev`.
+
+``` rust
+{{#include ../../../../examples/cfg.rs}}
+```
+
+``` console
+$ cargo run --example cfg --release
-Это также позволяет Вам изменять статические приоритеты задач без
-переписывания кода. Если Вы единообразно используете `lock`-и для доступа
-к данным в разделяемых ресурсах, тогда Ваш код продолжит компилироваться,
-когда Вы измените приоритет задач.
+$ cargo run --example cfg
+{{#include ../../../../ci/expected/cfg.run}}
+```
## Запуск задач из ОЗУ
@@ -31,10 +53,10 @@ RTIC v0.4.x была возможность взаимодействия с др
> очень мощные, но их легко использовать неправильно. Неверное использование
> любого из этих атрибутов может вызвать неопределенное поведение;
> Вам следует всегда предпочитать использование безопасных, высокоуровневых
-> атрибутов вокруг них, таких как атрибуты `interrupt` и `exception`
+> атрибутов вместо них, таких как атрибуты `interrupt` и `exception`
> из `cortex-m-rt`.
>
-> В особых случаях функций RAM нет безопасной абстракции в `cortex-m-rt`
+> В особых функций, размещаемых в ОЗУ нет безопасной абстракции в `cortex-m-rt`
> v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе.
[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
@@ -45,37 +67,105 @@ RTIC v0.4.x была возможность взаимодействия с др
{{#include ../../../../examples/ramfunc.rs}}
```
-Запуск этой программы произведет ожидаемый вывод.
+Запуск этой программы создаст ожидаемый вывод.
``` console
$ cargo run --example ramfunc
-{{#include ../../../../ci/expected/ramfunc.run}}```
+{{#include ../../../../ci/expected/ramfunc.run}}
+```
Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ
(`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`).
``` console
$ cargo nm --example ramfunc --release | grep ' foo::'
-{{#include ../../../../ci/expected/ramfunc.grep.foo}}```
+{{#include ../../../../ci/expected/ramfunc.grep.foo}}
+```
``` console
$ cargo nm --example ramfunc --release | grep ' bar::'
-{{#include ../../../../ci/expected/ramfunc.grep.bar}}```
+{{#include ../../../../ci/expected/ramfunc.grep.bar}}
+```
+
+## Обходной путь для быстрой передачи сообщений
-## `binds`
+Передача сообщений всегда вызывает копирование от отправителя в
+статическую переменную, а затем из статической переменной получателю.
+Таким образом, при передаче большого буфера, например `[u8; 128]`, передача сообщения
+вызывает два дорогих вызова `memcpy`. Чтобы минимизировать накладные расходы на передачу
+сообщения, можно использовать обходной путь: вместо передачи буфера по значению,
+можно передавать владеющий указатель на буфер.
-**ПРИМЕЧАНИЕ**: Требуется RTIC не ниже 0.4.2
+Можно использовать глобальный аллокатор, чтобы реализовать данный трюк (`alloc::Box`,
+`alloc::Rc`, и т.п.), либо использовать статически аллоцируемый пул памяти, например [`heapless::Pool`].
-Вы можете давать аппаратным задачам имена похожие на имена обычных задач.
-Для этого нужно использовать аргумент `binds`: Вы называете функцию
-по своему желанию и назначаете ей прерывание / исключение
-через аргумент `binds`. `Spawn` и другие служебные типы будут размещены в модуле,
-названном в соответствии с названием функции, а не прерывания / исключения.
-Давайте посмотрим пример:
+[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
+
+Здесь приведен пример использования `heapless::Pool` для "упаковки" буфера из 128 байт.
``` rust
-{{#include ../../../../examples/binds.rs}}
+{{#include ../../../../examples/pool.rs}}
```
+
``` console
-$ cargo run --example binds
-{{#include ../../../../ci/expected/binds.run}}``` \ No newline at end of file
+$ cargo run --example pool
+{{#include ../../../../ci/expected/pool.run}}
+```
+
+## Инспектирование раскрываемого кода
+
+`#[rtic::app]` - это процедурный макрос, который создает код.
+Если по какой-то причине вам нужно увидеть код, сгенерированный этим макросом,
+у вас есть два пути:
+
+Вы можете изучить файл `rtic-expansion.rs` внутри папки `target`. Этот файл
+содержит элемент `#[rtic::app]` в раскрытом виде (не всю вашу программу!)
+из *последней сборки* (с помощью `cargo build` или `cargo check`) RTIC программы.
+Раскрытый код не отформатирован по-умолчанию, но вы можете запустить `rustfmt`
+на нем перед тем, как читать.
+
+``` console
+$ cargo build --example foo
+
+$ rustfmt target/rtic-expansion.rs
+
+$ tail target/rtic-expansion.rs
+```
+
+``` rust
+#[doc = r" Implementation details"]
+mod app {
+ #[doc = r" Always include the device crate which contains the vector table"]
+ use lm3s6965 as _;
+ #[no_mangle]
+ unsafe extern "C" fn main() -> ! {
+ rtic::export::interrupt::disable();
+ let mut core: rtic::export::Peripherals = core::mem::transmute(());
+ core.SCB.scr.modify(|r| r | 1 << 1);
+ rtic::export::interrupt::enable();
+ loop {
+ rtic::export::wfi()
+ }
+ }
+}
+```
+
+Или, вы можете использовать подкоманду [`cargo-expand`]. Она раскроет
+*все* макросы, включая атрибут `#[rtic::app]`, и модули в вашем крейте и
+напечатает вывод в консоль.
+
+[`cargo-expand`]: https://crates.io/crates/cargo-expand
+
+``` console
+$ # создаст такой же вывод, как выше
+$ cargo expand --example smallest | tail
+```
+
+## Деструктуризация ресурса
+
+Если задача требует нескольких ресурсов, разбиение структуры ресурсов
+может улучшить читабельность. Вот два примера того, как это можно сделать:
+
+``` rust
+{{#include ../../../../examples/destructure.rs}}
+```
diff --git a/book/ru/src/by-example/types-send-sync.md b/book/ru/src/by-example/types-send-sync.md
index 85118897..755a379b 100644
--- a/book/ru/src/by-example/types-send-sync.md
+++ b/book/ru/src/by-example/types-send-sync.md
@@ -1,16 +1,15 @@
# Типы, Send и Sync
-Атрибут `app` вводит контекст, коллекцию переменных в каждую из функций.
-Все эти переменные имеют предсказуемые, неанонимные типы, поэтому Вы можете
-писать простые функции, получающие их как аргументы.
+Каждая функция в модуле `app` принимает структуру `Context` в качесте первого параметра.
+Все поля этих структур имеют предсказуемые, неанонимные типы,
+поэтому вы можете написать обычные функции, принимающие их как аргументы.
-Описание API определяет как эти типы эти типы генерируются из входных данных.
-Вы можете также сгенерировать документацию для Вашей бинарной библиотеки
-(`cargo doc --bin <name>`); в документации Вы найдете структуры `Context`
-(например `init::Context` и `idle::Context`), чьи поля представляют переменные
-включенные в каждую функцию.
+Справочник по API определяет как эти типы генерируются на основе входных данных.
+Вы можете также сгенерировать документацию к вашему крейту программы (`cargo doc --bin <name>`);
+в документации вы найдете структуры `Context` (например `init::Context` и
+`idle::Context`).
-В примере ниже сгенерированы разные типы с помощью атрибута `app`.
+Пример ниже показывает различные типы, сгенерированные атрибутом `app`.
``` rust
{{#include ../../../../examples/types.rs}}
@@ -18,39 +17,30 @@
## `Send`
-[`Send`] - маркерный типаж (trait) для "типов, которые можно передавать через границы
-потоков", как это определено в `core`. В контексте RTIC типаж `Send` необходим
+[`Send`] - это маркерный трейт для "типов, которые можно передавать через границы
+потоков", как это определено в `core`. В контексте RTIC трейт `Send` необходим
только там, где возможна передача значения между задачами, запускаемыми на
*разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений,
-в совместно используемых `static mut` ресурсах и инициализации поздних ресурсов.
+в разделяемых `static mut` ресурсах и при инициализации поздних ресурсов.
[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
-Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому Вам не
-стоит волноваться об этом. Более важно знать, где Вам *не* нужен типаж `Send`:
-в типах, передаваемых между задачами с *одинаковым* приоритетом. Это возникает
-в двух случаях: при передаче сообщений и в совместно используемых `static mut`
-ресурсах.
-
-В примере ниже показано, где можно использовать типы, не реализующие `Send`.
-
-``` rust
-{{#include ../../../../examples/not-send.rs}}
-```
+Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому вам не
+стоит волноваться об этом. В настоящий момент все передаваемые типы в RTIC должны быть `Send`, но
+это ограничение возможно будет ослаблено в будущем.
## `Sync`
-Похожая ситуация, [`Sync`] - маркерный типаж для "типов, на которых можно
-ссылаться в разных потоках", как это определено в `core`. В контексте RTIC
-типаж `Sync` необходим только там, где возможны две или более задачи,
-запускаемые на разных приоритетах, чтобы захватить разделяемую ссылку на
-ресурс. Это возникает только совместно используемых `static`-ресурсах.
+Аналогично, [`Sync`] - маркерный трейт для "типов, на которые можно безопасно разделять между потоками",
+как это определено в `core`. В контексте RTIC типаж `Sync` необходим только там,
+где возможно для двух или более задач, запускаемых на разных приоритетах получить разделяемую ссылку (`&-`) на
+ресурс. Это возникает только (`&-`) ресурсах с разделяемым доступом.
[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
Атрибут `app` проверит, что `Sync` реализован, где необходимо, но важно знать,
-где ограничение `Sync` не требуется: в `static`-ресурсах, разделяемых между
-задачами с *одинаковым* приоритетом.
+где ограничение `Sync` не требуется: в (`&-`) ресурсах с разделяемым доступом, за которые
+соперничают задачи с *одинаковым* приоритетом.
В примере ниже показано, где можно использовать типы, не реализующие `Sync`.
diff --git a/book/ru/src/internals.md b/book/ru/src/internals.md
index 4a47e775..48495b04 100644
--- a/book/ru/src/internals.md
+++ b/book/ru/src/internals.md
@@ -1,7 +1,14 @@
# Под капотом
-В этом разделе описывабтся внутренности фркймворка на *высоком уровне*.
-Низкоуровневые тонкости, такие как парсинг и кодогенерация производимые
-процедурным макросом (`#[app]`) здесь объясняться не будут. Мы сосредоточимся
-на анализе пользовательской спецификации и структурах данных, используемых
-рантаймом.
+**Этот раздел в настоящий момент находится в разработке,
+он появится снова, когда будет завершен**
+
+Этот раздел описывает внутренности фреймворка RTIC на *высоком уровне*.
+Низкоуровневые детали, такие как парсинг и генерация кода, выполняемые процедурным макросом
+(`#[app]`) объясняться не будут. Внимание будет сосредоточено на анализе
+спецификации пользователя и структурах данных, используемых на этапе выполнения.
+
+Мы настоятельно рекомендуем вам прочитать раздел о [конкуренции] в embedonomicon
+перед тем, как погружаться в материал.
+
+[конкуренции]: https://github.com/rust-embedded/embedonomicon/pull/48
diff --git a/book/ru/src/internals/access.md b/book/ru/src/internals/access.md
new file mode 100644
index 00000000..ea073a4d
--- /dev/null
+++ b/book/ru/src/internals/access.md
@@ -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,
+ },
+ // ..
+ });
+ }
+}
+```
diff --git a/book/ru/src/internals/ceilings.md b/book/ru/src/internals/ceilings.md
index 2c645a4d..df9901a2 100644
--- a/book/ru/src/internals/ceilings.md
+++ b/book/ru/src/internals/ceilings.md
@@ -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;
+
+ // ..
+ }
+
+ // ..
+}
+```
diff --git a/book/ru/src/internals/critical-sections.md b/book/ru/src/internals/critical-sections.md
new file mode 100644
index 00000000..e4c3d0ab
--- /dev/null
+++ b/book/ru/src/internals/critical-sections.md
@@ -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` никогда не получат шанс на запуск.
diff --git a/book/ru/src/internals/interrupt-configuration.md b/book/ru/src/internals/interrupt-configuration.md
new file mode 100644
index 00000000..5631b374
--- /dev/null
+++ b/book/ru/src/internals/interrupt-configuration.md
@@ -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(/* .. */)
+}
+```
diff --git a/book/ru/src/internals/late-resources.md b/book/ru/src/internals/late-resources.md
new file mode 100644
index 00000000..0fad0aec
--- /dev/null
+++ b/book/ru/src/internals/late-resources.md
@@ -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 не нуждается в барьере памяти
+в одноядерном контексте.
diff --git a/book/ru/src/internals/non-reentrancy.md b/book/ru/src/internals/non-reentrancy.md
new file mode 100644
index 00000000..98eb00fb
--- /dev/null
+++ b/book/ru/src/internals/non-reentrancy.md
@@ -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 нет.
diff --git a/book/ru/src/internals/tasks.md b/book/ru/src/internals/tasks.md
index 85f783fb..66503251 100644
--- a/book/ru/src/internals/tasks.md
+++ b/book/ru/src/internals/tasks.md
@@ -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`
diff --git a/book/ru/src/internals/timer-queue.md b/book/ru/src/internals/timer-queue.md
index 70592852..9f2dc37e 100644
--- a/book/ru/src/internals/timer-queue.md
+++ b/book/ru/src/internals/timer-queue.md
@@ -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)
+ }
+ }
+ }
+ }
+ }
+}
+```
diff --git a/book/ru/src/migration.md b/book/ru/src/migration.md
new file mode 100644
index 00000000..b7f2fa11
--- /dev/null
+++ b/book/ru/src/migration.md
@@ -0,0 +1,4 @@
+# Инструкции по миграции
+
+В этом разделе описывается как мигрировать между различными версиями RTIC.
+Можно также использовать для сравнения версий.
diff --git a/book/ru/src/migration/migration_rtic.md b/book/ru/src/migration/migration_rtic.md
new file mode 100644
index 00000000..28813fe2
--- /dev/null
+++ b/book/ru/src/migration/migration_rtic.md
@@ -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: () = {
+ // ...
+
+};
+```
diff --git a/book/ru/src/migration/migration_v4.md b/book/ru/src/migration/migration_v4.md
new file mode 100644
index 00000000..0ff80396
--- /dev/null
+++ b/book/ru/src/migration/migration_v4.md
@@ -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) {
+ // ..
+ }
+};
+```
diff --git a/book/ru/src/migration/migration_v5.md b/book/ru/src/migration/migration_v5.md
new file mode 100644
index 00000000..04aedc5f
--- /dev/null
+++ b/book/ru/src/migration/migration_v5.md
@@ -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`.
+
diff --git a/book/ru/src/preface.md b/book/ru/src/preface.md
index bfae0043..700560f4 100644
--- a/book/ru/src/preface.md
+++ b/book/ru/src/preface.md
@@ -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>
<p align="center">Конкурентный фреймворк для создания систем реального времени</p>
# Введение
-Эта книга содержит документацию уровня пользователя фреймворком Real-Time Interrupt-driven Concurrency
-(RTIC). Описание API можно найти [здесь](../../api/rtic/index.html).
+Эта книга содержит документацию пользовательского уровня о фреймворке Real-Time Interrupt-driven Concurrency
+(RTIC). Справочник по API можно найти [здесь](../../api/).
+
+Также известен как Real-Time For the Masses.
+
+<!--Оригинал данного руководства на [английском].-->
+
+<!--[английском]: ../en/index.html-->
+
+Это документация по RTIC версии v0.6.x; за документацией по другим версиям:
+
+* v0.5.x [сюда](/0.5).
+* v0.4.x [сюда](/0.4).
-{{#include README_RU.md:5:44}}
+{{#include ../../../README_ru.md:7:45}}
-{{#include README_RU.md:50:}}
+{{#include ../../../README_ru.md:51:}}