Очередь работ Zephyr (workqueue)
Обработчик прерывания должен быть коротким, а разобрать принятый пакет или записать лог — долго. В Zephyr для этого не заводят отдельный поток под каждую мелочь: элемент работы отправляют в очередь, и её поток выполнит его, когда дойдёт очередь.
Обновлено Редакция GAW
Очередь работ (workqueue) — объект ядра Zephyr, который состоит из очереди элементов работы и выделенного потока, выполняющего их по одному в порядке поступления (FIFO). Каждый элемент — структура k_work с функцией-обработчиком; поток очереди вызывает её, передавая адрес самого элемента. Назначение — та же отложенная обработка прерываний, что и у «нижних половин» в других системах: обработчик прерывания или высокоприоритетный поток отправляет элемент в очередь и возвращается, а несрочную работу выполняет поток с более низким приоритетом. Так сокращается задержка прерываний без отдельного потока под каждую задачу.
Элементы работы и их состояния
Элемент инициализируют k_work_init() или статически через K_WORK_DEFINE, отправляют k_work_submit() (в системную очередь) или k_work_submit_to_queue(). Элемент может быть в нескольких состояниях сразу: K_WORK_QUEUED (в очереди), K_WORK_DELAYED (ждёт срока), K_WORK_RUNNING (выполняется), K_WORK_CANCELING (отменяется). Повторная отправка элемента, который уже стоит в очереди, ничего не меняет — работа выполнится один раз; зато обработчик может переотправить свой элемент, чтобы выполнять длинную работу порциями. Дополнительные данные хранят в структуре, которая содержит k_work, и достают через CONTAINER_OF. Пока элемент ожидает обработки, менять его и его данные нельзя.
| Вид | Тип | Как попадает в очередь |
|---|---|---|
| обычный | k_work | сразу, k_work_submit() |
| отложенный | k_work_delayable | по истечении задержки: k_work_schedule() не меняет назначенный срок, k_work_reschedule() заменяет его; K_NO_WAIT — немедленно |
| по событию опроса | k_work_poll_submit() | когда наблюдаемый объект ядра изменит состояние или истечёт таймаут |
k_work_cancel() безопасно вызывать из ISR; k_work_cancel_sync() и k_work_flush() ждут завершения и потому доступны только потокам. Отложенные элементы заменяют для многих задач программный таймер: ядро само отправит элемент в очередь по сроку, а обработчик выполнится в потоке, а не в прерывании.
Системная очередь
Ядро определяет системную очередь (system workqueue), доступную любому коду; она создаётся, только если приложением используется. По умолчанию её поток имеет наименьший кооперативный приоритет, поэтому опустошение системной очереди имеет приоритет над всеми вытесняемыми потоками: кооперативный поток в Zephyr, став текущим, остаётся им, пока сам не выполнит действие, делающее его неготовым, — вот почему поток очереди уступает между элементами. Делать системную очередь вытесняемой документация называет рискованным, потому что подсистемы и драйверы могут рассчитывать на кооперативный контекст; обратная сторона — длинный обработчик в кооперативном потоке задержит остальные потоки, в том числе более приоритетные. Параметры: CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE, CONFIG_SYSTEM_WORKQUEUE_PRIORITY, CONFIG_SYSTEM_WORKQUEUE_NO_YIELD (см. Kconfig). Свою очередь заводят через k_work_q, стек K_THREAD_STACK_DEFINE, k_work_queue_init() и k_work_queue_start() — но только когда элементы несовместимы с системной очередью, например надолго блокируются: каждая очередь заметно стоит по памяти.
Ограничения и практика
Обработчику доступны все API потоков, но любая блокировка задерживает все следующие элементы. Поэтому мьютекс берут по пути без ожидания (K_NO_WAIT), а при неудаче переотправляют элемент — лучше отложенный, с ненулевой задержкой, чтобы владелец блокировки успел её отпустить. Сами элементы самоблокирующиеся, внешний замок для отправки не нужен, но данные, которые ISR заполняет, а обработчик читает, нужно защищать от гонок атомарными операциями или спин-блокировками. Общие принципы разделения работы между прерыванием и потоком — в статье «Прерывания и параллельность»; какую версию Zephyr брать для проекта — в ответе о LTS-версиях Zephyr.
Частые вопросы
Чем k_work_schedule() отличается от k_work_reschedule()?
Обе отправляют отложенный элемент k_work_delayable в очередь после задержки. k_work_schedule() не трогает уже назначенный срок: повторный вызов до его истечения ничего не меняет — так удобно собирать данные в течение заданного времени после первого события. k_work_reschedule() безусловно заменяет срок и при необходимости очередь — так собирают данные до тех пор, пока после последнего события не пройдёт заданная пауза. Если элемент ещё не запланирован, обе ведут себя одинаково.
Можно ли блокироваться внутри обработчика элемента работы?
Формально обработчику доступны все API потоков, включая семафоры и мьютексы. Но пока он ждёт, поток очереди не обрабатывает следующие элементы, поэтому документация советует брать блокировку по пути без ожидания (K_NO_WAIT), а при неудаче переотправить элемент в очередь, лучше отложенный с ненулевой задержкой. Отдельную очередь заводят только если её элементы блокируются настолько, что мешать системной очереди недопустимо.
Что будет, если отправить элемент, который уже стоит в очереди?
Ничего: элемент остаётся на своём месте, и работа выполнится один раз. Это защищает от лавины при частых прерываниях — пример в документации отправляет из ISR печать сообщения об ошибке, и если ISR сработает снова до выполнения, второе сообщение просто не напечатается. Обработчик, напротив, может переотправить собственный элемент, потому что в момент выполнения тот уже не в очереди.
Источники
- Zephyr Project. Zephyr Project Documentation (latest): Kernel Services — Workqueue Threads, 2026 — введение: очередь работ — объект ядра с выделенным потоком, который обрабатывает элементы работы в порядке FIFO, вызывая функцию элемента; обычно используется ISR или высокоприоритетным потоком, чтобы передать несрочную обработку потоку ниже приоритетом; число очередей ограничено только ОЗУ; приоритет потока настраивается (кооперативный или вытесняемый); поток уступает между элементами; очередь нужно инициализировать; разд. Work Item Lifecycle: элемент имеет функцию-обработчик с одним аргументом — адресом элемента; состояния K_WORK_QUEUED, K_WORK_DELAYED, K_WORK_RUNNING, K_WORK_CANCELING; повторная отправка уже стоящего в очереди элемента ничего не меняет — работа выполнится один раз; обработчик может переотправить свой элемент; CONTAINER_OF для доступа к объемлющей структуре; обработчику доступны все API потоков, но блокирующие операции задерживают остальные элементы; ожидающий элемент нельзя менять; разд. Delayable Work: k_work_delayable, после задержки ядро само отправляет элемент в очередь; разд. Triggered Work: k_work_poll_submit; разд. System Workqueue: системная очередь существует, только если используется; дополнительные очереди заводят лишь при необходимости — каждая дорого стоит по памяти; по умолчанию системная очередь имеет наименьший кооперативный приоритет, её опустошение имеет приоритет над всеми вытесняемыми потоками, делать её вытесняемой рискованно; разд. How to Use: k_work_q, K_THREAD_STACK_DEFINE, k_work_queue_init(), k_work_queue_start(); k_work_init() или K_WORK_DEFINE; k_work_submit() — в системную очередь, k_work_submit_to_queue() — в заданную; пример отправки из ISR печати сообщения об ошибке; k_work_cancel() безопасен из ISR, k_work_cancel_sync() и k_work_flush() — из потоков; k_work_init_delayable(), k_work_schedule() не меняет уже назначенный срок, k_work_reschedule() заменяет его; K_NO_WAIT — немедленная отправка; разд. Best Practices: брать блокировки по пути без ожидания (K_NO_WAIT) и переотправлять элемент; разд. Suggested Uses: откладывать сложную обработку прерывания из ISR в общий поток без отдельного потока; разд. Configuration Options: CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE, CONFIG_SYSTEM_WORKQUEUE_PRIORITY, CONFIG_SYSTEM_WORKQUEUE_NO_YIELD
- Zephyr Project. Zephyr Project Documentation (latest): Kernel Services — Scheduling, 2026 — разд. Cooperative Time Slicing: кооперативный поток, став текущим, остаётся им, пока сам не выполнит действие, делающее его неготовым, поэтому длинные вычисления в нём задерживают остальные потоки, включая более приоритетные; разд. Preemptive Time Slicing: вытесняемый поток остаётся текущим, пока не станет готов поток выше приоритетом или он сам не станет неготовым; квантование времени планировщика даёт потокам равного приоритета шанс выполниться; параметры CONFIG_TIMESLICING, CONFIG_TIMESLICE_SIZE, CONFIG_TIMESLICE_PRIORITY
Нашли ошибку? ·
Смотрите также
- Отложенная обработка прерыванийКороткий обработчик прерывания и основная работа в задаче, которую он будит
- Задержка прерыванияВремя от запроса до обработчика: Cortex-M3 и AVR
- Программный таймер (software timer)Таймер FreeRTOS на системном тике: callback выполняется в задаче-демоне, однократно или периодически
- Мьютекс (mutex)Объект RTOS для взаимного исключения задач при доступе к общему ресурсу
- Состояние гонкиОшибка, зависящая от момента срабатывания прерывания
- KconfigСистема настройки сборки из ядра Linux: символы CONFIG_ с типами и зависимостями, меню, файлы prj.conf и sdkconfig
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- Какую версию Zephyr выбрать: LTS или последнюю стабильную?Изделие на годы — LTS 3.7 (до 27.07.2029); новые платы и функции — 4.5 (до 28.10.2027)