Задача простоя (idle task)
Планировщику всегда нужна хотя бы одна задача, которую можно запустить. Когда все прикладные задачи спят, процессор получает задача простоя — и именно в ней удобно подчищать память и уводить микроконтроллер в режим пониженного потребления.
Обновлено Редакция GAW
Задача простоя (idle task) — задача, которую планировщик FreeRTOS создаёт сам при запуске, чтобы в системе всегда была хотя бы одна готовая к выполнению задача. Её приоритет — наименьший возможный, tskIDLE_PRIORITY, равный 0, поэтому она получает процессор только тогда, когда все прикладные задачи заблокированы или приостановлены. Во FreeRTOS у неё две обязанности: освобождать память, выделенную ядром удалённым задачам, и вызывать хук простоя.
Освобождение памяти
Когда задача удаляется через vTaskDelete(), её блок управления и стек возвращаются в кучу не сразу, а задачей простоя. Поэтому в приложении, которое удаляет задачи, задачу простоя нельзя лишать процессорного времени: если какая-то задача крутится без блокировок, память удалённых задач не освобождается. Во всех остальных случаях голодание задачи простоя безвредно — других активных функций у неё нет. Прикладные задачи могут делить с ней приоритет 0; тогда при configIDLE_SHOULD_YIELD = 1 (значение по умолчанию и в шаблоне конфигурации) задача простоя сразу уступает им процессор, а при 0 — использует весь свой квант времени.
Хук простоя
Хук простоя (idle hook) — функция void vApplicationIdleHook(void), которую задача простоя вызывает в каждом своём цикле, если в FreeRTOSConfig.h задано configUSE_IDLE_HOOK = 1 (в шаблоне — 0). Правила:
- хук не должен блокироваться: никаких
vTaskDelay(), ожиданий очереди или семафора с таймаутом — иначе в системе не останется готовой задачи; - если приложение использует
vTaskDelete(), хук должен периодически возвращаться, чтобы задача простоя успевала чистить память; - типичное применение — перевести процессор в режим пониженного потребления: раз хук работает, значит, делать больше нечего.
Альтернатива хуку — отдельная задача с приоритетом tskIDLE_PRIORITY: гибче, но дороже по ОЗУ. Сон в хуке ограничен периодическим тиком, который каждый раз будит процессор; снять это ограничение позволяет бестактовый режим.
В ESP-IDF: по задаче на ядро
На двухъядерных ESP32 IDF FreeRTOS создаёт отдельную закреплённую задачу простоя для каждого ядра с теми же обязанностями. Стандартный хук vApplicationIdleHook() включается опцией CONFIG_FREERTOS_USE_IDLE_HOOK, но он один на систему и одинаков для обоих ядер. Поэтому ESP-IDF добавляет свои хуки: esp_register_freertos_idle_hook() и esp_register_freertos_idle_hook_for_cpu() регистрируют их во время работы, до 8 хуков каждого типа на ядро, причём на разных ядрах наборы могут отличаться. Запрет на блокирующие вызовы сохраняется.
Задачи простоя в ESP-IDF — ещё и «сердцебиение» для сторожевого таймера задач: по умолчанию он наблюдает именно за ними, и если какая-то задача долго не уступает процессор, задача простоя не успевает отметиться, и срабатывает TWDT. Как строить защиту сторожевыми таймерами в целом, разобрано в статье «Стратегии сторожевого таймера». Задача программных таймеров, в отличие от задачи простоя, работает с настраиваемым приоритетом (в шаблоне конфигурации — максимальным) и к простою отношения не имеет.
Частые вопросы
Можно ли вызывать vTaskDelay() в хуке простоя?
Нет. В системе всегда должна быть хотя бы одна готовая к выполнению задача, а блокировка задачи простоя это правило нарушит. Документация FreeRTOS запрещает в vApplicationIdleHook() любые вызовы, которые могут заблокировать: vTaskDelay(), ожидание очереди или семафора с ненулевым таймаутом. Нужна задержка — создайте отдельную задачу с приоритетом tskIDLE_PRIORITY.
Почему задачу простоя нельзя «голодом морить»?
Потому что именно она освобождает память, которую ядро выделило задачам, удалённым через vTaskDelete(). Если высокоприоритетная задача никогда не блокируется, задача простоя не получает процессор, и память удалённых задач не возвращается в кучу. По той же причине хук простоя должен периодически возвращаться, а не крутиться в бесконечном цикле. В ESP-IDF за этим следит Task Watchdog: по умолчанию он наблюдает за задачами простоя каждого ядра.
Сколько задач простоя в ESP-IDF?
По одной на ядро, и каждая закреплена за своим ядром; обязанности те же, что у единственной задачи простоя обычного FreeRTOS. Стандартный хук vApplicationIdleHook() при этом один и вызывается на обоих ядрах, поэтому ESP-IDF добавляет свои: esp_register_freertos_idle_hook_for_cpu() регистрирует до 8 хуков на ядро во время работы, и на разных ядрах они могут быть разными.
Источники
- FreeRTOS. FreeRTOS Kernel documentation: The idle task and Hook functions (Kernel features), 2026 — стр. Idle task: создаётся автоматически при запуске планировщика, чтобы всегда была хотя бы одна готовая задача; имеет наименьший приоритет и не отнимает время у прикладных задач; освобождает память, выделенную ядром удалённым задачам, поэтому при использовании vTaskDelete() её нельзя лишать процессорного времени; прикладные задачи могут делить приоритет tskIDLE_PRIORITY, поведение задаёт configIDLE_SHOULD_YIELD; хук простоя вызывается в каждом цикле задачи простоя; два способа выполнять код на приоритете простоя — хук или отдельная задача (гибче, но больше ОЗУ); хук не должен вызывать блокирующие функции (vTaskDelay(), очередь или семафор с временем ожидания); configUSE_IDLE_HOOK = 1, прототип void vApplicationIdleHook( void ); обычно хук переводит процессор в режим энергосбережения; стр. Hook functions: хук вызывается многократно, пока работает задача простоя, при использовании vTaskDelete() хук должен периодически возвращаться
- FreeRTOS. FreeRTOS-Kernel V11.3.3: include/task.h, examples/template_configuration/FreeRTOSConfig.h, include/FreeRTOS.h (GitHub, MIT) — task.h: #define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U ); шаблон: configUSE_IDLE_HOOK 0, configUSE_TICK_HOOK 0, configIDLE_SHOULD_YIELD 1 — задача простоя уступает прикладной задаче с приоритетом 0, при 0 использует весь свой квант; FreeRTOS.h: configIDLE_SHOULD_YIELD равно 1, если не определено
- Espressif. ESP-IDF Programming Guide v6.1, ESP32: FreeRTOS (IDF); FreeRTOS (Supplemental Features); Watchdogs — разд. Idle Tasks: Vanilla FreeRTOS неявно создаёт задачу простоя с приоритетом 0 при старте планировщика, она освобождает память удалённых задач и выполняет хук простоя; в IDF FreeRTOS на каждое ядро создаётся отдельная закреплённая задача простоя с теми же обязанностями; стр. Supplemental Features, разд. Tick and Idle Hooks: хук FreeRTOS включается опцией CONFIG_FREERTOS_USE_IDLE_HOOK, регистрируется на этапе компиляции, один на систему и одинаков для всех ядер; хуки ESP-IDF регистрируются во время работы через esp_register_freertos_idle_hook() и esp_register_freertos_idle_hook_for_cpu(), до 8 хуков каждого типа на ядро, могут различаться по ядрам; callback не должен вызывать блокирующие функции; стр. Watchdogs: TWDT по умолчанию следит за задачами простоя каждого ядра и так выявляет задачи, которые долго не уступают процессор; опции CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0/CPU1
Нашли ошибку? ·
Смотрите также
- Бестактовый режим (tickless idle)Режим FreeRTOS, в котором тик останавливается на время простоя, а счётчик тиков корректируется после пробуждения
- Режимы снаSleep mode: Idle, Power-down, пробуждение и ток
- Куча (heap)ОЗУ для malloc между .bss и стеком
- Квантование времени (time slicing)Переключение планировщика между задачами одного приоритета на каждом тике; настраивается configUSE_TIME_SLICING
- Task Watchdog Timer (TWDT)Сторож задач ESP-IDF: idle-задачи по умолчанию, 5 с, подписка и сброс из задачи
- Программный таймер (software timer)Таймер FreeRTOS на системном тике: callback выполняется в задаче-демоне, однократно или периодически
- ESP32ESP32, S2, S3, C2, C3, C5, C6, H2, P4: ядра, радио, USB, GPIO — сравнение и выбор
- Сторожевой таймерWDT AVR, IWDG и WWDG STM32, таймаут, окно, контроль задач, причина сброса