aboutsummaryrefslogtreecommitdiff
path: root/book/ru/src/internals/tasks.md
diff options
context:
space:
mode:
authorHenrik Tjäder <henrik@tjaders.com>2023-02-24 21:28:13 +0100
committerHenrik Tjäder <henrik@tjaders.com>2023-03-01 00:35:25 +0100
commit0fc86d972c0305e73252dc20d702d647eb342ee5 (patch)
treeeb01e1556958912344b890c964ff3f364aaad0d2 /book/ru/src/internals/tasks.md
parent032316855d8b55fc572ab7fcbd1de7ba394b2fd1 (diff)
Book: Remove RTIC v1 Russian translation
Diffstat (limited to 'book/ru/src/internals/tasks.md')
-rw-r--r--book/ru/src/internals/tasks.md399
1 files changed, 0 insertions, 399 deletions
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`