aboutsummaryrefslogtreecommitdiff
path: root/book/ru/src/internals
diff options
context:
space:
mode:
authorAndrey Zgarbul <zgarbul.andrey@gmail.com>2021-04-04 08:15:13 +0300
committerAndrey Zgarbul <zgarbul.andrey@gmail.com>2021-04-08 12:22:43 +0300
commit05bda2b1bd2e15f5a20cda1444992eb9b6c8887e (patch)
tree25330724d4e6d9ea3a62b592c07bfc5799c7da57 /book/ru/src/internals
parent83cdf00eecb0f14857b5e0f28e884b2120eabb18 (diff)
update russian translation of the book
Diffstat (limited to 'book/ru/src/internals')
-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
8 files changed, 1804 insertions, 6 deletions
diff --git a/book/ru/src/internals/access.md b/book/ru/src/internals/access.md
new file mode 100644
index 0000000..ea073a4
--- /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 2c645a4..df9901a 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 0000000..e4c3d0a
--- /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 0000000..5631b37
--- /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 0000000..0fad0ae
--- /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 0000000..98eb00f
--- /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 85f783f..6650325 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 7059285..9f2dc37 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)
+ }
+ }
+ }
+ }
+ }
+}
+```