# Программные задачи В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события, RTIC также поддерживает *программные* задачи, которые могут порождаться приложением из любого контекста выполнения. Программным задачам можно также назначать приоритет и, под капотом, они диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные прерывания, были указаны в аргументе `dispatchers` модуля `app`, если используются программные задачи; часть из этих свободных прерываний будут использованы для управления программными задачами. Преимущество программных задач над аппаратными в том, что множество задач можно назначить на один обработчик прерывания. Программные задачи также определяются атрибутом `task`, но аргумент `binds` опускается. Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах. Три программные задачи привязаны к 2-м обработчикам прерываний. ``` rust {{#include ../../../../examples/task.rs}} ``` ``` console $ cargo run --example task {{#include ../../../../ci/expected/task.run}} ``` ## Передача сообщений Другое преимущество программной задачи в том, что задачам можно передать сообщения в момент их запуска. Тип передаваемого сообщения должен быть определен в сигнатуре задачи-обработчика. Пример ниже демонстрирует три задачи, две из которых ожидают сообщение. ``` rust {{#include ../../../../examples/message.rs}} ``` ``` console $ cargo run --example message {{#include ../../../../ci/expected/message.run}} ``` ## Вместимость RTIC *не* производит никакого рода аллокаций памяти в куче. Память, необходимая для размещения сообщения резервируется статически. По-умолчанию фреймворк минимизирует выделение памяти программой таким образом, что каждая задача имеет "вместимость" для сообщения равную 1: это значит, что не более одного сообщения можно передать задаче перед тем, как у нее появится возможность к запуску. Это значение по-умолчанию можно изменить для каждой задачи, используя аргумент `capacity`. Этот аргумент принимает положительное целое, которое определяет как много сообщений буфер сообщений задачи может хранить. Пример ниже устанавливает вместимость программной задачи `foo` равной 4. Если вместимость не установить, второй вызов `spawn.foo` в `UART0` приведет к ошибке (панике). ``` rust {{#include ../../../../examples/capacity.rs}} ``` ``` console $ cargo run --example capacity {{#include ../../../../ci/expected/capacity.run}} ``` ## Обработка ошибок Интерфейс `spawn` возвращает вариант `Err`, если для размещения сообщения нет места. В большинстве сценариев возникающие ошибки обрабатываются одним из двух способов: - Паника, с помощью `unwrap`, `expect`, и т.п. Этот метод используется, чтобы обнаружить ошибку программиста (например bug) выбора вместительности, которая оказалась недостаточна. Когда эта паника встречается во время тестирования, выбирается большая вместительность, и перекомпиляция программы может решить проблему, но иногда достаточно окунуться глубже и провести анализ времени выполнения программы, чтобы выяснить, может ли платформа обрабатывать пиковые нагрузки, или процессор необходимо заменить на более быстрый. - Игнорирование результата. В программах реального времени, как и в обычных, может быть нормальным иногда терять данные, или не получать ответ на некоторые события в пиковых ситуациях. В таких сценариях может быть допустимо игнорирование ошибки вызова `spawn`. Следует отметить, что повторная попытка вызова `spawn` обычно неверный подход, поскольку такая операция на практике вероятно никогда не завершится успешно. Так как у нас есть только переключения контекста на задачи с *более высоким* приоритетом, повторение вызова `spawn` на задаче с низким приоритом никогда не позволит планировщику вызвать задачу, что значит, что буфер никогда не будет очищен. Такая ситуация отражена в следующем наброске: ``` rust #[rtic::app(..)] mod app { #[init(spawn = [foo, bar])] fn init(cx: init::Context) { cx.spawn.foo().unwrap(); cx.spawn.bar().unwrap(); } #[task(priority = 2, spawn = [bar])] fn foo(cx: foo::Context) { // .. // программа зависнет здесь while cx.spawn.bar(payload).is_err() { // повтор попытки вызова spawn, если произошла ошибка } } #[task(priority = 1)] fn bar(cx: bar::Context, payload: i32) { // .. } } ```