aboutsummaryrefslogtreecommitdiff
path: root/book/ru/src/internals
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2023-03-04 21:10:24 +0000
committerGitHub <noreply@github.com>2023-03-04 21:10:24 +0000
commit7c7d6558f6d9c50fbb4d2487c98c9a5be15f2f7b (patch)
tree80a47f0dc40059014e9448c4c2eb34c54dff45fe /book/ru/src/internals
parent1c5db277e4161470136dbd2a11e914ff1d383581 (diff)
parent98c5490d94950608d31cd5ad9dd260f2f853735c (diff)
Merge #694
694: RTIC 2 r=AfoHT a=korken89 Co-authored-by: Emil Fresk <emil.fresk@gmail.com> Co-authored-by: Per Lindgren <per.lindgren@ltu.se>
Diffstat (limited to 'book/ru/src/internals')
-rw-r--r--book/ru/src/internals/access.md158
-rw-r--r--book/ru/src/internals/ceilings.md92
-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.md113
-rw-r--r--book/ru/src/internals/non-reentrancy.md79
-rw-r--r--book/ru/src/internals/tasks.md399
-rw-r--r--book/ru/src/internals/timer-queue.md372
8 files changed, 0 insertions, 1806 deletions
diff --git a/book/ru/src/internals/access.md b/book/ru/src/internals/access.md
deleted file mode 100644
index ea073a4..0000000
--- a/book/ru/src/internals/access.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# Контроль доступа
-
-Одна из основ RTIC - контроль доступа. Контроль того, какая часть программы
-может получить доступ к какой статической переменной - инструмент обеспечения
-безопасности памяти.
-
-Статические переменные используются для разделения состояний между обработчиками
-прерываний, или между обработчиком прерывания и нижним контекстом выполнения, `main`.
-В обычном Rust коде трудно обеспечить гранулированный контроль за тем, какие функции
-могут получать доступ к статическим переменным, поскольку к статическим переменным
-можно получить доступ из любой функции, находящейся в той же области видимости,
-в которой они определены. Модули дают частичный контроль над доступом
-к статическим переменным, но они недостаточно гибкие.
-
-Чтобы добиться полного контроля за тем, что задачи могут получить доступ
-только к статическим переменным (ресурсам), которые им были указаны в RTIC атрибуте,
-фреймворк RTIC производит трансформацию структуры кода.
-Эта трансформация состоит из размещения ресурсов (статических переменных), определенных
-пользователем *внутри* модуля, а пользовательского кода *вне* модуля.
-Это делает невозможным обращение пользовательского кода к статическим переменным.
-
-Затем доступ к ресурсам предоставляется каждой задаче с помощью структуры `Resources`,
-чьи поля соответствуют ресурсам, к которым получает доступ задача.
-Есть лишь одна такая структура на задачу и структура `Resources` инициализируется
-либо уникальной ссылкой (`&mut-`) на статическую переменную, либо с помощью прокси-ресурса (см.
-раздел [критические секции](critical-sections.html)).
-
-Код ниже - пример разных трансформаций структуры кода, происходящих за сценой:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- static mut X: u64: 0;
- static mut Y: bool: 0;
-
- #[init(resources = [Y])]
- fn init(c: init::Context) {
- // .. пользовательский код ..
- }
-
- #[interrupt(binds = UART0, resources = [X])]
- fn foo(c: foo::Context) {
- // .. пользовательский код ..
- }
-
- #[interrupt(binds = UART1, resources = [X, Y])]
- fn bar(c: bar::Context) {
- // .. пользовательский код ..
- }
-
- // ..
-}
-```
-
-Фреймворк создает код, подобный этому:
-
-``` rust
-fn init(c: init::Context) {
- // .. пользовательский код ..
-}
-
-fn foo(c: foo::Context) {
- // .. пользовательский код ..
-}
-
-fn bar(c: bar::Context) {
- // .. пользовательский код ..
-}
-
-// Публичное API
-pub mod init {
- pub struct Context<'a> {
- pub resources: Resources<'a>,
- // ..
- }
-
- pub struct Resources<'a> {
- pub Y: &'a mut bool,
- }
-}
-
-pub mod foo {
- pub struct Context<'a> {
- pub resources: Resources<'a>,
- // ..
- }
-
- pub struct Resources<'a> {
- pub X: &'a mut u64,
- }
-}
-
-pub mod bar {
- pub struct Context<'a> {
- pub resources: Resources<'a>,
- // ..
- }
-
- pub struct Resources<'a> {
- pub X: &'a mut u64,
- pub Y: &'a mut bool,
- }
-}
-
-/// Детали реализации
-mod app {
- // все, что внутри этого модуля спрятано от пользовательского кода
-
- static mut X: u64 = 0;
- static mut Y: bool = 0;
-
- // настоящая точка входа в программу
- unsafe fn main() -> ! {
- interrupt::disable();
-
- // ..
-
- // вызов пользовательского кода; передача ссылок на статические переменные
- init(init::Context {
- resources: init::Resources {
- X: &mut X,
- },
- // ..
- });
-
- // ..
-
- interrupt::enable();
-
- // ..
- }
-
- // обработчик прерывания,с которым связан `foo`
- #[no_mangle]
- unsafe fn UART0() {
- // вызов пользовательского кода; передача ссылок на статические переменные
- foo(foo::Context {
- resources: foo::Resources {
- X: &mut X,
- },
- // ..
- });
- }
-
- // обработчик прерывания,с которым связан `bar`
- #[no_mangle]
- unsafe fn UART1() {
- // вызов пользовательского кода; передача ссылок на статические переменные
- bar(bar::Context {
- resources: bar::Resources {
- X: &mut X,
- Y: &mut Y,
- },
- // ..
- });
- }
-}
-```
diff --git a/book/ru/src/internals/ceilings.md b/book/ru/src/internals/ceilings.md
deleted file mode 100644
index df9901a..0000000
--- a/book/ru/src/internals/ceilings.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Анализ приоритетов
-
-*Поиск максимального приоритета* ресурса (*ceiling*) - поиск динамического
-приоритета, который любая задача должна иметь, чтобы безопасно работать с
-памятью ресурсов. Анализ приоритетов - относительно прост,
-но критичен для безопасности памяти RTIC программ.
-
-Для расчета максимального приоритета ресурса мы должны сначала составить
-список задач, имеющих доступ к ресурсу -- так как фреймворк RTIC
-форсирует контроль доступа к ресурсам на этапе компиляции, он
-также имеет доступ к этой информации на этапе компиляции.
-Максимальный приоритет ресурса - просто наивысший логический приоритет
-среди этих задач.
-
-`init` и `idle` не настоящие задачи, но у них есть доступ к ресурсам,
-поэтому они должны учитываться при анализе приоритетов.
-`idle` учитывается как задача, имеющая логический приоритет `0`,
-в то время как `init` полностью исключается из анализа --
-причина этому в том, что `init` никогда не использует (не нуждается) критические
-секции для доступа к статическим переменным.
-
-В предыдущем разделе мы показывали, что разделяемые ресусы
-могут быть представлены уникальными ссылками (`&mut-`) или скрываться за
-прокси в зависимости от того, имеет ли задача к ним доступ.
-Какой из вариантов представляется задаче зависит от приоритета задачи и
-максимального приоритета ресурса.
-Если приоритет задачи такой же, как максимальный приоритет ресурса, тогда
-задача получает уникальную ссылку (`&mut-`) на память ресурса,
-в противном случае задача получает прокси -- это также касается `idle`.
-`init` особеннвй: он всегда получает уникальные ссылки (`&mut-`) на ресурсы.
-
-Пример для иллюстрации анализа приоритетов:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- struct Resources {
- // доступен из `foo` (prio = 1) и `bar` (prio = 2)
- // -> CEILING = 2
- #[init(0)]
- x: u64,
-
- // доступен из `idle` (prio = 0)
- // -> CEILING = 0
- #[init(0)]
- y: u64,
- }
-
- #[init(resources = [x])]
- fn init(c: init::Context) {
- // уникальная ссылка, потому что это `init`
- let x: &mut u64 = c.resources.x;
-
- // уникальная ссылка, потому что это `init`
- let y: &mut u64 = c.resources.y;
-
- // ..
- }
-
- // PRIORITY = 0
- #[idle(resources = [y])]
- fn idle(c: idle::Context) -> ! {
- // уникальная ссылка, потому что
- // приоритет (0) == максимальному приоритету ресурса (0)
- let y: &'static mut u64 = c.resources.y;
-
- loop {
- // ..
- }
- }
-
- #[interrupt(binds = UART0, priority = 1, resources = [x])]
- fn foo(c: foo::Context) {
- // прокси-ресурс, потому что
- // приоритет задач (1) < максимальному приоритету ресурса (2)
- let x: resources::x = c.resources.x;
-
- // ..
- }
-
- #[interrupt(binds = UART1, priority = 2, resources = [x])]
- fn bar(c: foo::Context) {
- // уникальная ссылка, потому что
- // приоритет задачи (2) == максимальному приоритету ресурса (2)
- let x: &mut u64 = c.resources.x;
-
- // ..
- }
-
- // ..
-}
-```
diff --git a/book/ru/src/internals/critical-sections.md b/book/ru/src/internals/critical-sections.md
deleted file mode 100644
index e4c3d0a..0000000
--- a/book/ru/src/internals/critical-sections.md
+++ /dev/null
@@ -1,521 +0,0 @@
-# Критические секции
-
-Когда ресурсы (статические переменные) разделяются между двумя или более задачами,
-которые выполняются с разными приоритетами, некая форма запрета изменений
-необходима, чтобы изменять память без гонки данных. В RTIC мы используем
-основанные на приоритетах критические секции, чтобы гарантировать запрет изменений
-(см. [Протокол немедленного максимального приоритета][icpp]).
-
-[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
-
-Критическия секция состоит во временном увеличении *динамического* приоритета задачи.
-Пока задача находится в критической секции, все другие задачи, которые могут
-послать запрос переменной *не могут запуститься*.
-
-Насколько большим должен быть динамический приориткт, чтобы гарантировать запрет изменений
-определенного ресурса? [Анализ приоритетов](ceilings.html) отвечает на этот вопрос
-и будет обсужден в следующем разделе. В этом разделе мы сфокусируемся
-на реализации критической секции.
-
-## Прокси-ресурсы
-
-Для упрощения, давайте взглянем на ресурс, разделяемый двумя задачами,
-запускаемыми с разными приоритетами. Очевидно, что одна задача может вытеснить
-другую; чтобы предотвратить гонку данных задача с *низким приоритетом* должна
-использовать критическую секцию, когда необходимо изменять разделяемую память.
-С другой стороны, высокоприоритетная задача может напрямую изменять
-разделяемую память, поскольку не может быть вытеснена низкоприоритетной задачей.
-Чтобы заставить использовать критическую секцию на задаче с низким приоритетом,
-мы предоставляем *прокси-ресурсы*, в которых мы отдаем уникальную ссылку
-(`&mut-`) высокоприоритетной задаче.
-
-Пример ниже показывает разные типы, передаваемые каждой задаче:
-
-``` rust
-#[rtic::app(device = ..)]
-mut app {
- struct Resources {
- #[init(0)]
- x: u64,
- }
-
- #[interrupt(binds = UART0, priority = 1, resources = [x])]
- fn foo(c: foo::Context) {
- // прокси-ресурс
- let mut x: resources::x = c.resources.x;
-
- x.lock(|x: &mut u64| {
- // критическая секция
- *x += 1
- });
- }
-
- #[interrupt(binds = UART1, priority = 2, resources = [x])]
- fn bar(c: bar::Context) {
- let mut x: &mut u64 = c.resources.x;
-
- *x += 1;
- }
-
- // ..
-}
-```
-
-Теперь давайте посмотрим. как эти типы создаются фреймворком.
-
-``` rust
-fn foo(c: foo::Context) {
- // .. пользовательский код ..
-}
-
-fn bar(c: bar::Context) {
- // .. пользовательский код ..
-}
-
-pub mod resources {
- pub struct x {
- // ..
- }
-}
-
-pub mod foo {
- pub struct Resources {
- pub x: resources::x,
- }
-
- pub struct Context {
- pub resources: Resources,
- // ..
- }
-}
-
-pub mod bar {
- pub struct Resources<'a> {
- pub x: &'a mut u64,
- }
-
- pub struct Context {
- pub resources: Resources,
- // ..
- }
-}
-
-mod app {
- static mut x: u64 = 0;
-
- impl rtic::Mutex for resources::x {
- type T = u64;
-
- fn lock<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
deleted file mode 100644
index 5631b37..0000000
--- a/book/ru/src/internals/interrupt-configuration.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# Настройка прерываний
-
-Прерывания - это основа работы программ на RTIC. Правильно настроить приоритеты
-прерываний и убедиться, что они не изменяются во время выполнения обязательно
-для безопасной работы программы.
-
-Фреймворк RTIC представляет приоритеты прерываний, как нечто, что должно быть определено
-на этапе компиляции. Однако, статическая настройка должна быть зашита в соответствующие регистры
-в процессе инициализации программы. Настройка прерываний происходит до запуска функции `init`.
-
-Этот пример дает представление о коде, запускаемом фреймворком RTIC:
-
-``` rust
-#[rtic::app(device = lm3s6965)]
-mod app {
- #[init]
- fn init(c: init::Context) {
- // .. пользовательский код ..
- }
-
- #[idle]
- fn idle(c: idle::Context) -> ! {
- // .. пользовательский код ..
- }
-
- #[interrupt(binds = UART0, priority = 2)]
- fn foo(c: foo::Context) {
- // .. пользовательский код ..
- }
-}
-```
-
-Фреймворк генерирует точку входа в программу, которая выглядит примерно так:
-
-``` rust
-// настоящая точку входа в программу
-#[no_mangle]
-unsafe fn main() -> ! {
- // преобразует логические приоритеты в аппаратные / NVIC приоритеты
- fn logical2hw(priority: u8) -> u8 {
- use lm3s6965::NVIC_PRIO_BITS;
-
- // NVIC кодирует приоритеты верхними битами
- // большие значения обозначают меньший приоритет
- ((1 << NVIC_PRIORITY_BITS) - priority) << (8 - NVIC_PRIO_BITS)
- }
-
- cortex_m::interrupt::disable();
-
- let mut core = cortex_m::Peripheral::steal();
-
- core.NVIC.enable(Interrupt::UART0);
-
- // значение, определенное пользователем
- let uart0_prio = 2;
-
- // проверка на этапе компиляции, что определенный приоритет входит в поддерживаемый диапазон
- let _ = [(); (1 << NVIC_PRIORITY_BITS) - (uart0_prio as usize)];
-
- core.NVIC.set_priority(Interrupt::UART0, logical2hw(uart0_prio));
-
- // вызов пользовательского кода
- init(/* .. */);
-
- // ..
-
- cortex_m::interrupt::enable();
-
- // вызов пользовательского кода
- idle(/* .. */)
-}
-```
diff --git a/book/ru/src/internals/late-resources.md b/book/ru/src/internals/late-resources.md
deleted file mode 100644
index 146c438..0000000
--- a/book/ru/src/internals/late-resources.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Поздние ресурсы
-
-Некоторые ресурсы инициализируются во время выполнения после завершения функции `init`.
-Важно то, что ресурсы (статические переменные) полностью инициализируются
-до того, как задачи смогут запуститься, вот почему они должны быть инициализированы
-пока прерывания отключены.
-
-Ниже показан пример кода, генерируемого фреймворком для инициализации позних ресурсов.
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- struct Resources {
- x: Thing,
- }
-
- #[init]
- fn init() -> init::LateResources {
- // ..
-
- init::LateResources {
- x: Thing::new(..),
- }
- }
-
- #[task(binds = UART0, resources = [x])]
- fn foo(c: foo::Context) {
- let x: &mut Thing = c.resources.x;
-
- x.frob();
-
- // ..
- }
-
- // ..
-}
-```
-
-Код, генерируемы фреймворком выглядит примерно так:
-
-``` rust
-fn init(c: init::Context) -> init::LateResources {
- // .. пользовательский код ..
-}
-
-fn foo(c: foo::Context) {
- // .. пользовательский код ..
-}
-
-// Public API
-pub mod init {
- pub struct LateResources {
- pub x: Thing,
- }
-
- // ..
-}
-
-pub mod foo {
- pub struct Resources<'a> {
- pub x: &'a mut Thing,
- }
-
- pub struct Context<'a> {
- pub resources: Resources<'a>,
- // ..
- }
-}
-
-/// Детали реализации
-mod app {
- // неинициализированная статическая переменная
- static mut x: MaybeUninit<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` ведет себя как *барьер компиляции*, который не дает компилятору переставить запись в `X` *после*
-`interrupt::enable`. Если бы компилятор мог делать такие перестановки появились
-бы гонки данных между этой записью и любой операцией `foo`, взаимодействующей с `X`.
-
-Архитектурам с более сложным конвейером инструкций нужен барьер памяти
-(`atomic::fence`) вместо compiler fence для полной очистки операции записи
-перед включением прерываний. Архитектура ARM Cortex-M не нуждается в барьере памяти
-в одноядерном контексте.
diff --git a/book/ru/src/internals/non-reentrancy.md b/book/ru/src/internals/non-reentrancy.md
deleted file mode 100644
index 98eb00f..0000000
--- a/book/ru/src/internals/non-reentrancy.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Нереентерабельность
-
-В RTIC задачи-обработчики *не* могут использоваться повторно. Переиспользование задачи-обработчика
-может сломать правила заимствования Rust и привести к *неопределенному поведению*.
-Задача-обработчик теоретически может быть переиспользована одним из двух способов: программно или аппаратно.
-
-## Программно
-
-Чтобы переиспользовать задачу-обработчик программно, назначенный ей обработчик прерывания
-должен быть вызван с помощью FFI (смотрите пример ниже). FFI требует `unsafe` код,
-что уменьшает желание конечных пользователей вызывать обработчик прерывания.
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- #[init]
- fn init(c: init::Context) { .. }
-
- #[interrupt(binds = UART0)]
- fn foo(c: foo::Context) {
- static mut X: u64 = 0;
-
- let x: &mut u64 = X;
-
- // ..
-
- //~ `bar` может вытеснить `foo` в этом месте
-
- // ..
- }
-
- #[interrupt(binds = UART1, priority = 2)]
- fn bar(c: foo::Context) {
- extern "C" {
- fn UART0();
- }
-
- // этот обработчик прерывания вызовет задачу-обработчик `foo`, что сломает
- // ссылку на статическую переменную `X`
- unsafe { UART0() }
- }
-}
-```
-
-Фреймворк RTIC должен сгенерировать код обработчика прерывания, который вызывает
-определенные пользователем задачи-обработчики. Мы аккуратны в том, чтобы обеспечить
-невозможность вызова этих обработчиков из пользовательского кода.
-
-Пример выше раскрывается в:
-
-``` rust
-fn foo(c: foo::Context) {
- // .. пользовательский код ..
-}
-
-fn bar(c: bar::Context) {
- // .. пользовательский код ..
-}
-
-mod app {
- // все в этом блоке невидимо для пользовательского кода
-
- #[no_mangle]
- unsafe fn USART0() {
- foo(..);
- }
-
- #[no_mangle]
- unsafe fn USART1() {
- bar(..);
- }
-}
-```
-
-## Аппаратно
-
-Обработчик прерывания также может быть вызван без программного вмешательства.
-Это может произойти, если один обработчик будет назначен двум или более прерываниям
-в векторе прерываний, но синтаксиса для такого рода функциональности в RTIC нет.
diff --git a/book/ru/src/internals/tasks.md b/book/ru/src/internals/tasks.md
deleted file mode 100644
index 01380ba..0000000
--- a/book/ru/src/internals/tasks.md
+++ /dev/null
@@ -1,399 +0,0 @@
-# Программные задачи
-
-RTIC поддерживает программные и аппаратные задачи. Каждая аппаратная задача
-назначается на отдельный обработчик прерывания. С другой стороны, несколько
-программных задач могут управляться одним обработчиком прерывания --
-это сделано, чтобы минимизировать количество обработчиков прерывания,
-используемых фреймворком.
-
-Фреймворк группирует задачи, для которых вызывается `spawn` по уровню приоритета,
-и генерирует один *диспетчер задачи* для каждого уровня приоритета.
-Каждый диспетчер запускается на отдельном обработчике прерывания,
-а приоритет этого обработчика прерывания устанавливается так, чтобы соответствовать
-уровню приоритета задач, управляемых диспетчером.
-
-Каждый диспетчер задач хранит *очередь* задач, *готовых* к выполнению;
-эта очередь называется *очередью готовности*. Вызов программной задачи состоит
-из добавления записи в очередь и вызова прерывания, который запускает соответствующий
-диспетчер задач. Каждая запись в эту очередь содержит метку (`enum`),
-которая идентифицирует задачу, которую необходимо выполнить и *указатель*
-на сообщение, передаваемое задаче.
-
-Очередь готовности - неблокируемая очередь типа SPSC (один производитель - один потребитель).
-Диспетчер задач владеет конечным потребителем в очереди; конечным производителем
-считается ресурс, за который соперничают задачи, которые могут вызывать (`spawn`) другие задачи.
-
-## Дисметчер задач
-
-Давайте сначала глянем на код, генерируемый фреймворком для диспетчеризации задач.
-Рассмотрим пример:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- // ..
-
- #[interrupt(binds = UART0, priority = 2, spawn = [bar, baz])]
- fn foo(c: foo::Context) {
- foo.spawn.bar().ok();
-
- foo.spawn.baz(42).ok();
- }
-
- #[task(capacity = 2, priority = 1)]
- fn bar(c: bar::Context) {
- // ..
- }
-
- #[task(capacity = 2, priority = 1, resources = [X])]
- fn baz(c: baz::Context, input: i32) {
- // ..
- }
-
- extern "C" {
- fn UART1();
- }
-}
-```
-
-Фреймворк создает следующий диспетчер задач, состоящий из обработчика прерывания и очереди готовности:
-
-``` rust
-fn bar(c: bar::Context) {
- // .. пользовательский код ..
-}
-
-mod app {
- use heapless::spsc::Queue;
- use cortex_m::register::basepri;
-
- struct Ready<T> {
- task: T,
- // ..
- }
-
- /// вызываемые (`spawn`) задачи, выполняющиеся с уровнем приоритета `1`
- enum T1 {
- bar,
- baz,
- }
-
- // очередь готовности диспетчера задач
- // `5-1=4` - представляет собой емкость этой очереди
- static mut RQ1: Queue<Ready<T1>, 5> = Queue::new();
-
- // обработчик прерывания, выбранный для диспетчеризации задач с приоритетом `1`
- #[no_mangle]
- unsafe UART1() {
- // приоритет данного обработчика прерывания
- const PRIORITY: u8 = 1;
-
- let snapshot = basepri::read();
-
- while let Some(ready) = RQ1.split().1.dequeue() {
- match ready.task {
- T1::bar => {
- // **ПРИМЕЧАНИЕ** упрощенная реализация
-
- // используется для отслеживания динамического приоритета
- let priority = Cell::new(PRIORITY);
-
- // вызов пользовательского кода
- bar(bar::Context::new(&priority));
- }
-
- T1::baz => {
- // рассмотрим `baz` позднее
- }
- }
- }
-
- // инвариант BASEPRI
- basepri::write(snapshot);
- }
-}
-```
-
-## Вызов задачи
-
-Интерфейс `spawn` предоставлен пользователю как методы структурв `Spawn`.
-Для каждой задачи существует своя структура `Spawn`.
-
-Код `Spawn`, генерируемый фреймворком для предыдущего примера выглядит так:
-
-``` rust
-mod foo {
- // ..
-
- pub struct Context<'a> {
- pub spawn: Spawn<'a>,
- // ..
- }
-
- pub struct Spawn<'a> {
- // отслеживает динамический приоритет задачи
- priority: &'a Cell<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` можно поставить в очередь
- // `3-1=2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь
- // эта очередь заполняется фреймворком до того, как запустится `init`
- static mut bar_FQ: Queue<(), 3> = Queue::new();
-
- // Поиск максимального приоритета для конечного потребителя `bar_FQ`
- const bar_FQ_CEILING: u8 = 2;
-
- // приоритет-ориентированная критическая секция
- //
- // это запускае переданное замыкание `f` с динамическим приоритетом не ниже
- // `ceiling`
- fn lock(priority: &Cell<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, 3> = Queue::new();
-
- // Поиск максимального приоритета для конечного потребителя `baz_FQ`
- const baz_FQ_CEILING: u8 = 2;
-
- impl<'a> foo::Spawn<'a> {
- /// Spawns the `baz` task
- pub fn baz(&self, message: u64) -> Result<(), u64> {
- unsafe {
- match lock(self.priority(), baz_FQ_CEILING, || {
- baz_FQ.split().1.dequeue()
- }) {
- Some(index) => {
- // ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера
- baz_INPUTS[index as usize].write(message);
-
- lock(self.priority(), RQ1_CEILING, || {
- // помещаем задачу в очередь готовности
- RQ1.split().1.enqueue_unchecked(Ready {
- task: T1::baz,
- index,
- });
- });
-
- // вызываем прерывание, которое запускает диспетчер задач
- rtic::pend(Interrupt::UART0);
- }
-
- None => {
- // достигнута максимальная вместительность; неудачный вызов
- Err(message)
- }
- }
- }
- }
- }
-}
-```
-
-А теперь давайте взглянем на настоящую реализацию диспетчера задач:
-
-``` rust
-mod app {
- // ..
-
- #[no_mangle]
- unsafe UART1() {
- const PRIORITY: u8 = 1;
-
- let snapshot = basepri::read();
-
- while let Some(ready) = RQ1.split().1.dequeue() {
- match ready.task {
- Task::baz => {
- // ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера
- let input = baz_INPUTS[ready.index as usize].read();
-
- // сообщение было прочитано, поэтому можно вернуть ячейку обратно
- // чтобы освободить очередь
- // (диспетчер задач имеет эксклюзивный доступ к
- // последнему элементу очереди)
- baz_FQ.split().0.enqueue_unchecked(ready.index);
-
- let priority = Cell::new(PRIORITY);
- baz(baz::Context::new(&priority), input)
- }
-
- Task::bar => {
- // выглядит также как ветка для `baz`
- }
-
- }
- }
-
- // инвариант BASEPRI
- basepri::write(snapshot);
- }
-}
-```
-
-`INPUTS` плюс `FQ`, список свободной памяти равняется эффективному пулу памяти.
-Однако, вместо того *список свободной памяти* (связный список), чтобы отслеживать
-пустые ячейки в буфере `INPUTS`, мы используем SPSC очередь; это позволяет нам
-уменьшить количество критических секций.
-На самом деле благодаря этому выбору код диспетчера задач неблокируемый.
-
-## Вместительность очереди
-
-Фреймворк RTIC использует несколько очередей, такие как очереди готовности и
-списки свободной памяти. Когда список свободной памяти пуст, попытка выызова
-(`spawn`) задачи приводит к ошибке; это условие проверяется во время выполнения.
-Не все операции, произвожимые фреймворком с этими очередями проверяют их
-пустоту / наличие места. Например, возвращение ячейки списка свободной памяти
-(см. диспетчер задач) не проверяется, поскольку есть фиксированное количество
-таких ячеек циркулирующих в системе, равное вместительности списка свободной памяти.
-Аналогично, добавление записи в очередь готовности (см. `Spawn`) не проверяется,
-потому что вместительность очереди выбрана фреймворком.
-
-Пользователи могут задавать вместительность программных задач;
-эта вместительность - максимальное количество сообщений, которые можно
-послать указанной задаче от задачи более высоким приоритетом до того,
-как `spawn` вернет ошибку. Эта определяемая пользователем иместительность -
-размер списка свободной памяти задачи (например `foo_FQ`), а также размер массива,
-содержащего входные данные для задачи (например `foo_INPUTS`).
-
-Вместительность очереди готовности (например `RQ1`) вычисляется как *сумма*
-вместительностей всех задач, управляемх диспетчером; эта сумма является также
-количеством сообщений, которые очередь может хранить в худшем сценарии, когда
-все возможные сообщения были посланы до того, как диспетчер задач получает шанс
-на запуск. По этой причине получение ячейки списка свободной памяти при любой
-операции `spawn` приводит к тому, что очередь готовности еще не заполнена,
-поэтому вставка записи в список готовности может пропустить проверку "полна ли очередь?".
-
-В нашем запущенном примере задача `bar` не принимает входных данных, поэтому
-мы можем пропустить проверку как `bar_INPUTS`, так и `bar_FQ` и позволить
-пользователю посылать неограниченное число сообщений задаче, но если бы мы сделали это,
-было бы невозможно превысить вместительность для `RQ1`, что позволяет нам
-пропустить проверку "полна ли очередь?" при вызове задачи `baz`.
-В разделе о [очереди таймера](timer-queue.html) мы увидим как
-список свободной памяти используется для задач без входных данных.
-
-## Анализ приоритетов
-
-Очереди, использемые внутри интерфейса `spawn`, рассматриваются как обычные ресурсы
-и для них тоже работает анализ приоритетов. Важно заметить, что это SPSC очереди,
-и только один из конечных элементов становится ресурсом; другим конечным элементом
-владеет диспетчер задач.
-
-Рассмотрим следующий пример:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- #[idle(spawn = [foo, bar])]
- fn idle(c: idle::Context) -> ! {
- // ..
- }
-
- #[task]
- fn foo(c: foo::Context) {
- // ..
- }
-
- #[task]
- fn bar(c: bar::Context) {
- // ..
- }
-
- #[task(priority = 2, spawn = [foo])]
- fn baz(c: baz::Context) {
- // ..
- }
-
- #[task(priority = 3, spawn = [bar])]
- fn quux(c: quux::Context) {
- // ..
- }
-}
-```
-
-Вот как будет проходить анализ приоритетов:
-
-- `idle` (prio = 0) и `baz` (prio = 2) соревнуются за конечный потребитель
- `foo_FQ`; это приводит к максимальному приоритету `2`.
-
-- `idle` (prio = 0) и `quux` (prio = 3) соревнуются за конечный потребитель
- `bar_FQ`; это приводит к максимальному приоритету `3`.
-
-- `idle` (prio = 0), `baz` (prio = 2) и `quux` (prio = 3) соревнуются за
- конечный производитель `RQ1`; это приводит к максимальному приоритету `3`
diff --git a/book/ru/src/internals/timer-queue.md b/book/ru/src/internals/timer-queue.md
deleted file mode 100644
index 9f2dc37..0000000
--- a/book/ru/src/internals/timer-queue.md
+++ /dev/null
@@ -1,372 +0,0 @@
-# Очередь таймера
-
-Функциональность очередь таймера позволяет пользователю планировать задачи на запуск
-в опреленное время в будущем. Неудивительно, что эта функция также реализуется с помощью очереди:
-очередь приоритетов, где запланированные задачи сортируются в порядке аозрастания времени.
-Эта функция требует таймер, способный устанавливать прерывания истечения времени.
-Таймер используется для пуска прерывания, когда настает запланированное время задачи;
-в этот момент задача удаляется из очереди таймера и помещается в очередь готовности.
-
-Давайте посмотрим, как это реализовано в коде. Рассмотрим следующую программу:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- // ..
-
- #[task(capacity = 2, schedule = [foo])]
- fn foo(c: foo::Context, x: u32) {
- // запланировать задачу на повторный запуск через 1 млн. тактов
- c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok();
- }
-
- extern "C" {
- fn UART0();
- }
-}
-```
-
-## `schedule`
-
-Давайте сначала взглянем на интерфейс `schedule`.
-
-``` rust
-mod foo {
- pub struct Schedule<'a> {
- priority: &'a Cell<u8>,
- }
-
- impl<'a> Schedule<'a> {
- // `unsafe` и спрятано, потому что мы не хотим, чтобы пользовать сюда вмешивался
- #[doc(hidden)]
- pub unsafe fn priority(&self) -> &Cell<u8> {
- self.priority
- }
- }
-}
-
-mod app {
- type Instant = <path::to::user::monotonic::timer as rtic::Monotonic>::Instant;
-
- // все задачи, которые могут быть запланированы (`schedule`)
- enum T {
- foo,
- }
-
- struct NotReady {
- index: u8,
- instant: Instant,
- task: T,
- }
-
- // Очередь таймера - двоичная куча (min-heap) задач `NotReady`
- static mut TQ: TimerQueue<U2> = ..;
- const TQ_CEILING: u8 = 1;
-
- static mut foo_FQ: Queue<u8, U2> = Queue::new();
- const foo_FQ_CEILING: u8 = 1;
-
- static mut foo_INPUTS: [MaybeUninit<u32>; 2] =
- [MaybeUninit::uninit(), MaybeUninit::uninit()];
-
- static mut foo_INSTANTS: [MaybeUninit<Instant>; 2] =
- [MaybeUninit::uninit(), MaybeUninit::uninit()];
-
- impl<'a> foo::Schedule<'a> {
- fn foo(&self, instant: Instant, input: u32) -> Result<(), u32> {
- unsafe {
- let priority = self.priority();
- if let Some(index) = lock(priority, foo_FQ_CEILING, || {
- foo_FQ.split().1.dequeue()
- }) {
- // `index` - владеющий укачатель на ячейки в этих буферах
- foo_INSTANTS[index as usize].write(instant);
- foo_INPUTS[index as usize].write(input);
-
- let nr = NotReady {
- index,
- instant,
- task: T::foo,
- };
-
- lock(priority, TQ_CEILING, || {
- TQ.enqueue_unchecked(nr);
- });
- } else {
- // Не осталось места, чтобы разместить входные данные / instant
- Err(input)
- }
- }
- }
- }
-}
-```
-
-Это очень похоже на реализацию `Spawn`. На самом деле одни и те же буфер
-`INPUTS` и список сободной памяти (`FQ`) используются совместно интерфейсами
-`spawn` и `schedule`. Главное отличие между ними в том, что `schedule` также
-размещает `Instant`, момент на который задача запланирована на запуск,
-в отдельном буфере (`foo_INSTANTS` в нашем случае).
-
-`TimerQueue::enqueue_unchecked` делает немного больше работы, чем
-просто добавление записи в min-heap: он также вызывает прерывание
-системного таймера (`SysTick`), если новая запись оказывается первой в очереди.
-
-## Системный таймер
-
-Прерывание системного таймера (`SysTick`) заботится о двух вещах:
-передаче задач, которых становятся готовыми из очереди таймера в очередь готовности
-и установке прерывания истечения времени, когда наступит запланированное
-время следующей задачи.
-
-Давайте посмотрим на соответствующий код.
-
-``` rust
-mod app {
- #[no_mangle]
- fn SysTick() {
- const PRIORITY: u8 = 1;
-
- let priority = &Cell::new(PRIORITY);
- while let Some(ready) = lock(priority, TQ_CEILING, || TQ.dequeue()) {
- match ready.task {
- T::foo => {
- // переместить эту задачу в очередь готовности `RQ1`
- lock(priority, RQ1_CEILING, || {
- RQ1.split().0.enqueue_unchecked(Ready {
- task: T1::foo,
- index: ready.index,
- })
- });
-
- // вызвать диспетчер задач
- rtic::pend(Interrupt::UART0);
- }
- }
- }
- }
-}
-```
-
-Выглядит похоже на диспетчер задач, за исключением того, что
-вместо запуска готовой задачи, она лишь переносится в очередь готовности,
-что ведет к ее запуску с нужным приоритетом.
-
-`TimerQueue::dequeue` установит новое прерывание истечения времени, если вернет
-`None`. Он сязан с `TimerQueue::enqueue_unchecked`, который вызывает это
-прерывание; на самом деле, `enqueue_unchecked` передает задачу установки
-нового прерывание истечения времени обработчику `SysTick`.
-
-## Точность и диапазон `cyccnt::Instant` и `cyccnt::Duration`
-
-RTIC предоставляет реализацию `Monotonic`, основанную на счетчике тактов `DWT` (Data Watchpoint and Trace). `Instant::now` возвращает снимок таймера; эти снимки
-DWT (`Instant`ы) используются для сортировки записей в очереди таймера.
-Счетчик тактов - 32-битный счетчик, работающий на частоте ядра.
-Этот счетчик обнуляется каждые `(1 << 32)` тактов; у нас нет прерывания,
-ассоциированног с этим счетчиком, поэтому ничего ужасного не случится,
-когда он пройдет оборот.
-
-Чтобы упорядочить `Instant`ы в очереди, нам нужно сравнить 32-битные целые.
-Чтобы учесть обороты, мы используем разницу между двумя `Instant`ами, `a - b`,
-и рассматриваем результат как 32-битное знаковое целое.
-Если результат меньше нуля, значит `b` более поздний `Instant`;
-если результат больше нуля, значит `b` более ранний `Instant`.
-Это значит, что планирование задачи на `Instant`, который на `(1 << 31) - 1` тактов
-больше, чем запланированное время (`Instant`) первой (самой ранней) записи
-в очереди приведет к тому, что задача будет помещена в неправильное
-место в очереди. У нас есть несколько debug assertions в коде, чтобы
-предотвратить эту пользовательскую ошибку, но этого нельзя избежать,
-поскольку пользователь может написать
-`(instant + duration_a) + duration_b` и переполнить `Instant`.
-
-Системный таймер, `SysTick` - 24-битный счетчик также работающий
-на частоте процессора. Когда следующая планируемая задача более, чем в
-`1 << 24` тактов в будущем, прерывание устанавливается на время в пределах
-`1 << 24` тактов. Этот процесс может происходить несколько раз, пока
-следующая запланированная задача не будет в диапазоне счетчика `SysTick`.
-
-Подведем итог, оба `Instant` и `Duration` имеют разрешение 1 такт ядра, и `Duration` эффективно имеет (полуоткрытый) диапазон `0..(1 << 31)` (не включая максимум) тактов ядра.
-
-## Вместительность очереди
-
-Вместительность очереди таймера рассчитывается как сумма вместительностей
-всех планируемых (`schedule`) задач. Как и в случае очередей готовности,
-это значит, что как только мы затребовали пустую ячейку в буфере `INPUTS`,
-мы гарантируем, что способны передать задачу в очередь таймера;
-это позволяет нам опустить проверки времени выполнения.
-
-## Приоритет системного таймера
-
-Приориет системного таймера не может быть установлен пользователем;
-он выбирается фреймворком.
-Чтобы убедиться, что низкоприоритетные задачи не препятствуют
-запуску высокоприоритетных, мы выбираем приоритет системного таймера
-максимальным из всех планируемых задач.
-
-Чтобы понять, почему это нужно, рассмотрим вариант, когда две ранее
-запланированные задачи с приоритетами `2` и `3` становятся готовыми в
-примерно одинаковое время, но низкоприоритетная задача перемещается
-в очередь готовности первой.
-Если бы приоритет системного таймера был, например, равен `1`,
-тогда после перемещения низкоприоритетной (`2`) задачи, это бы привело
-к завершению (из-за того, что приоритет выше приоритета системного таймера)
-ожидания выполнения высокоприоритетной задачи (`3`).
-Чтобы избежать такого сценария, системный таймер должен работать на
-приоритете, равном наивысшему из приоритетов планируемых задач;
-в этом примере это `3`.
-
-## Анализ приоритетов
-
-Очередь таймера - это ресурс, разделяемый всеми задачами, которые могут
-планировать (`schedule`) задачи и обработчиком `SysTick`.
-Также интерфейс `schedule` соперничает с интерфейсом `spawn`
-за списки свободной памяти. Все это должно уситываться в анализе приоритетов.
-
-Чтобы проиллюстрировать, рассмотрим следующий пример:
-
-``` rust
-#[rtic::app(device = ..)]
-mod app {
- #[task(priority = 3, spawn = [baz])]
- fn foo(c: foo::Context) {
- // ..
- }
-
- #[task(priority = 2, schedule = [foo, baz])]
- fn bar(c: bar::Context) {
- // ..
- }
-
- #[task(priority = 1)]
- fn baz(c: baz::Context) {
- // ..
- }
-}
-```
-
-Анализ приоритетов происходил бы вот так:
-
-- `foo` (prio = 3) и `baz` (prio = 1) планируемые задачи, поэтому
- `SysTick` должен работать на максимальном из этих двух приоритетов, т.е. `3`.
-
-- `foo::Spawn` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
- конечный потребитель `baz_FQ`; это приводит к максимальному приоритету `3`.
-
-- `bar::Schedule` (prio = 2) имеет экслюзивный доступ к
- конечному потребителю `foo_FQ`; поэтому максимальный приоритет `foo_FQ` фактически `2`.
-
-- `SysTick` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
- очередь таймера `TQ`; это приводит к максимальному приоритету `3`.
-
-- `SysTick` (prio = 3) и `foo::Spawn` (prio = 3) оба имеют неблокируемый
- доступ к очереди готовности `RQ3`, что хранит записи `foo`;
- поэтому максимальный приоритет `RQ3` фактически `3`.
-
-- `SysTick` имеет эксклюзивный доступ к очереди готовности `RQ1`,
- которая хранит записи `baz`; поэтому максимальный приоритет `RQ1` фактически `3`.
-
-## Изменения в реализации `spawn`
-
-Когда интерфейс `schedule` используется, реализация `spawn` немного
-изменяется, чтобы отслеживать baseline задач. Как можете видеть в
-реализации `schedule` есть буферы `INSTANTS`, используемые, чтобы
-хранить время, в которое задача была запланирована навыполнение;
-этот `Instant` читается диспетчером задач и передается в пользовательский
-код, как часть контекста задачи.
-
-``` rust
-mod app {
- // ..
-
- #[no_mangle]
- unsafe UART1() {
- const PRIORITY: u8 = 1;
-
- let snapshot = basepri::read();
-
- while let Some(ready) = RQ1.split().1.dequeue() {
- match ready.task {
- Task::baz => {
- let input = baz_INPUTS[ready.index as usize].read();
- // ADDED
- let instant = baz_INSTANTS[ready.index as usize].read();
-
- baz_FQ.split().0.enqueue_unchecked(ready.index);
-
- let priority = Cell::new(PRIORITY);
- // ИЗМЕНЕНО instant передан как часть контекста задачи
- baz(baz::Context::new(&priority, instant), input)
- }
-
- Task::bar => {
- // выглядит также как ветка для `baz`
- }
-
- }
- }
-
- // инвариант BASEPRI
- basepri::write(snapshot);
- }
-}
-```
-
-И наоборот, реализации `spawn` нужно писать значение в буфер `INSTANTS`.
-Записанное значение располагается в структуре `Spawn` и это либо
-время `start` аппаратной задачи, либо время `scheduled` программной задачи.
-
-``` rust
-mod foo {
- // ..
-
- pub struct Spawn<'a> {
- priority: &'a Cell<u8>,
- // ADDED
- instant: Instant,
- }
-
- impl<'a> Spawn<'a> {
- pub unsafe fn priority(&self) -> &Cell<u8> {
- &self.priority
- }
-
- // ADDED
- pub unsafe fn instant(&self) -> Instant {
- self.instant
- }
- }
-}
-
-mod app {
- impl<'a> foo::Spawn<'a> {
- /// Spawns the `baz` task
- pub fn baz(&self, message: u64) -> Result<(), u64> {
- unsafe {
- match lock(self.priority(), baz_FQ_CEILING, || {
- baz_FQ.split().1.dequeue()
- }) {
- Some(index) => {
- baz_INPUTS[index as usize].write(message);
- // ADDED
- baz_INSTANTS[index as usize].write(self.instant());
-
- lock(self.priority(), RQ1_CEILING, || {
- RQ1.split().1.enqueue_unchecked(Ready {
- task: Task::foo,
- index,
- });
- });
-
- rtic::pend(Interrupt::UART0);
- }
-
- None => {
- // достигнута максимальная вместительность; неудачный вызов
- Err(message)
- }
- }
- }
- }
- }
-}
-```