diff options
| author | Henrik Tjäder <henrik@tjaders.com> | 2023-02-24 21:28:13 +0100 |
|---|---|---|
| committer | Henrik Tjäder <henrik@tjaders.com> | 2023-03-01 00:35:25 +0100 |
| commit | 0fc86d972c0305e73252dc20d702d647eb342ee5 (patch) | |
| tree | eb01e1556958912344b890c964ff3f364aaad0d2 /book/ru/src/internals/tasks.md | |
| parent | 032316855d8b55fc572ab7fcbd1de7ba394b2fd1 (diff) | |
Book: Remove RTIC v1 Russian translation
Diffstat (limited to 'book/ru/src/internals/tasks.md')
| -rw-r--r-- | book/ru/src/internals/tasks.md | 399 |
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` |
