[6.1. Введение: о чем говорится в части 6]
Эта часть рассматривает общие проблемы, с которыми сталкиваются пользователи, которые только начали осваивать FreeRTOS.
[Предыдущая часть FreeRTOS: практическое применение, часть 5 (управление памятью)]
Наиболее подробно рассматриваются проблемы переполнения стека и детектирования переполнения стека, потому что практика показала, что проблемы стека наиболее часто встречаются в запросах поддержки у пользователей из года в год. Также кратко, в стиле FAQ будут затрагиваются другие распространенные ошибки, их возможные причины и методы решения.
printf-stdarg.c
При использовании стандартных библиотечных функций C стек может использоваться очень интенсивно, особенно при вводе/выводе и поддержке строковых функций, таких как sprintf(). Загружаемый пакет FreeRTOS включает файл, который называется printf-stdarg.c - он содержит эффективную версию sprintf(), которая по минимуму использует стек, и может заменить во многих случаях стандартную библиотечную версию sprintf(). Часто это позволит выделить меньше памяти под стек для каждой задачи, которая вызывает sprintf() и похожие функции.
Модуль printf-stdarg.c имеет открытый исходный код (open source), однако права на его использование принадлежат другому производителю, так что этот модуль лицензируется отдельно от FreeRTOS. Условия лицензии содержатся в начале файла исходного кода модуля.
[6.2. Переполнение стека]
FreeRTOS предоставляет несколько возможностей для облегчения диагностики и отладки проблем, связанных со стеком.
Примечание: к сожалению, эти возможности не могут быть использованы в симулируемой среде DOS, потому что DOS использует сегментированную память. Поэтому невозможно предоставить пример, демонстрирующий их использование в среде Open Watcom.
API функция uxTaskGetStackHighWaterMark()
Каждая задача содержит собственный стек, общий размер которого указывается при создании задачи. Функция uxTaskGetStackHighWaterMark() используется для запроса величины, насколько было переполнено пространство памяти, выделенное под стек. Эта величина называется 'high water mark' (верхняя ватерлиния, сокращенно HWM).
unsigned portBASE_TYPE uxTaskGetStackHighWaterMark( xTaskHandle xTask );
Листинг 75. Прототип API функции uxTaskGetStackHighWaterMark()
Таблица 20. Параметры и значение возврата функции uxTaskGetStackHighWaterMark()
Имя параметра / возвращаемое значение |
Описание |
xTask |
Хендл задачи (субъект задачи), у которой запрашивается HWM - см. параметр pxCreatedTask API функции xTaskCreate() для информации по получению хендлов задач.
Задача может запросить HWM собственного стека, передав NULL вместо действительного значения хендла.
|
возвращаемое значение |
Эта величина становится больше и меньше в зависимости от выполнения задачи и обработки прерываний. Функция uxTaskGetStackHighWaterMark() вернет минимальное значение оставшегося места в стеке, которое было доступно после того, как задача начала выполнение. Эта величина остается неиспользованной, когда использование стека было самым большим. Возвращаемое значение, приближающееся к 0 означает, что имеется угроза переполнения стека задачи. |
Проверка стека во время выполнения (run time) - обзор
FreeRTOS предоставляет два опциональных механизма проверки переполнения стека во время выполнения. Поведение механизма определяется константой времени компиляции configCHECK_FOR_STACK_OVERFLOW, размещенной в файле FreeRTOSConfig.h. Оба метода увеличивают время, которое тратится на переключение контекста.
Хук переполнения стека (или callback) является функцией, которая будет вызвана ядром, когда было детектировано переполнение стека. Чтобы можно было использовать хук переполнения стека, нужно:
· Установить константу configCHECK_FOR_STACK_OVERFLOW либо в значение 1, либо в значение 2 (как обычно, в файле FreeRTOSConfig.h). · Предоставить реализацию функции хука, используя точно такое имя и прототип, как показано в листинге 76.
void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName );
Листинг 76. Прототип функции хука переполнения стека
Хук переполнения стека позволяет проще отлавливать и отлаживать ошибки стека, однако не предоставляет возможности восстановления из переполнения стека, если оно вдруг произошло. Параметры хука передают хендл и имя задачи, у которой будет отслеживаться переполнение в функции хука, так как возможно, что переполнение стека испортит имя задачи, которое было указано при её создании.
Хук переполнения стека будет вызван из контекста прерывания.
Некоторые микроконтроллеры будут генерировать исключение отказа, когда они детектировали некорректный доступ к памяти, и возможно, что данное исключение сработает еще раньше, чем ядро получит шанс вызвать функцию хука переполнения стека.
Проверка стека во время выполнения - метод 1
Метод 1 используется, когда configCHECK_FOR_STACK_OVERFLOW установлена в 1.
Задачи сохраняют в стеке свой контекст выполнения каждый раз, когда они отдают ресурс процессора другой задаче (или прерыванию). Возможна ситуация, когда использование стека в какой-то момент дойдет до предела. Когда configCHECK_FOR_STACK_OVERFLOW установлена в 1, ядро будет проверять, что указатель стека остался в допустимых пределах пространства стека после того, как контекст был сохранен в стеке. Функция хука переполнения стека будет вызвана тогда, когда указатель стека выйдет за границу допустимого диапазона.
Метод 1 работает быстрее, однако он может не отследить переполнения стека, которые произойдут между сохранениями контекста.
Проверка стека во время выполнения - метод 2
Метод 2 выполняет другие проверки кроме тех, что делает метод 1. Метод 2 используется, когда configCHECK_FOR_STACK_OVERFLOW установлена в 2.
Когда задача создается, то её стек заполняется известным значением. Метод 2 просматривает последние 20 байт пространства стека чтобы проверить, не были ли они перезаписаны. Хук переполнения стека запускается тогда, если любой из этих 20 байт окажется измененным.
Метод 2 не такой быстрый, как метод 1, однако он все же относительно быстрый, так как проверяются только 20 байт. Он скорее всего сможет отловить все случаи переполнения стека, хотя иногда возможно (но маловероятно), что некоторое переполнение может быть пропущено.
[6.3. Другие общие источники ошибок]
Симптом: добавление простой задачи в демо приводит его к сбою
Создание задачи требует выделения памяти, полученной из кучи. Многие проекты демо-приложений имеют размер кучи достаточно большим только для создания задач демо, так что после создания других дополнительных задач (а также очередей или семафоров) может не оказаться достаточно свободной памяти в куче.
Задача ожидания Idle Task создается автоматически, как только вызвана функция vTaskStartScheduler(). Выход из функции vTaskStartScheduler() произойдет только тогда, когда нет достаточного места в куче для создания задачи. Добавление пустого цикла после вызова vTaskStartScheduler() делает такую ошибку доступной для отладки.
Чтобы можно было добавить в демо другие задачи либо увеличьте размер кучи, либо удалите в демо некоторые из имеющихся задач.
Симптом: использование API функции внутри прерывания приводит к сбою приложения
Используйте только те API функции внутри ISR, имя которых оканчивается на "...FromISR()".
Симптом: иногда приложение разрушается внутри обработчика прерывания
Первое, что нужно проверить - не происходит ли при прерывании переполнения стека.
Методы задания и использования прерываний отличаются между разными портами FreeRTOS и разными компиляторами - так что второе, что нужно проверить - синтаксис, макросы и соглашения вызова, используемые для ISR, полностью соответствуют страничке документации демо, и полностью демонстрируются в ISR самого демо.
Если приложение работает на Cortex M3, то убедитесь, что при назначении приоритетов каждому прерыванию было принята во внимание обратная нумерация приоритетов этого микроконтроллера, так что малым числовым уровням приоритета соответствуют высокоуровневые приоритеты прерываний (что может выглядеть не интуитивно понятным). Общей ошибкой бывает случайное назначение прерыванию приоритета, которое используется FreeRTOS API (выше назначенного константой configMAX_SYSCALL_INTERRUPT_PRIORITY).
Симптом: крах шедулера при попытке запустить первую задачу
Если используется микроконтроллер ARM7, то проверьте, что микроконтроллер находится в режиме супервизора (Supervisor) перед вызовом vTaskStartScheduler(). Самый простой способ достичь этого - поместить процессор в режим супервизора внутри стартового кода C, который вызывается до функции main() (так называемый C startup code). Это делается так же, как сконфигурировано в демо-примерах для ARM7.
Шедулер не сможет запуститься, если процессор не будет находится в режиме Supervisor.
Симптом: критические секции не могут корректно быть вложены друг в друга
Не изменяйте биты разрешения прерываний микроконтроллера или флаги приоритетов прерываний с использованием каких-либо методов, отличающихся от вызовов taskENTER_CRITICAL() и taskEXIT_CRITICAL(). Эти макросы сохраняют подсчет глубины вкладывания критических секций, чтобы гарантировать, что прерывания будут разрешены снова, когда полностью развернется вложение секций друг в друга (счетчик вложений не станет равным 0).
Симптом: крах приложения уже перед запуском шедулера
ISR, которое могло потенциально вызвать переключение контекста, не должно быть разрешено к выполнению до старта шедулера. То же самое верно для любого ISR, которое делает попытку отправить данные в очередь или поменять семафор. Переключения контекста могут происходить только после старта шедулера.
Многие функции API не могут быть вызваны до старта шедулера. Лучше всего ограничить использование API созданием задач, очередей и семафоров, пока не будет вызвана функция vTaskStartScheduler().
Симптом: вызов API функции по время приостановки шедулера вызывает крах приложения
Шедулер приостанавливается вызовом vTaskSuspendAll() и возобновляет работу вызовом xTaskResumeAll().
Не делайте вызовы функций API, когда шедулер приостановлен.
Симптом: прототип для функции pxPortInitialiseStack() вызывает ошибку компиляции
Каждый порт требует определения макроса, который обеспечивает включение в сборку корректных заголовочных файлов. Ошибка при компиляции прототипа pxPortInitialiseStack() указывает на то, что установленный макрос не соответствует используемому порту FreeRTOS. Для дополнительной информации см. Дополнение 4.
Создавайте новые приложения на основе предоставленных демо-проектов с требуемым используемым портом FreeRTOS. Таким способом будет обеспечено корректное включение всех заголовков и правильная установка опций компилятора.
[Следующая часть FreeRTOS: практическое применение, дополнения, словарик]
[Ссылки]
1. Обзор FreeRTOS на русском языке - Андрей Курниц, статья в журнале «Компоненты и технологии» (2..10 номера 2011 года). 2. 150422FreeRTOS-API.pdf - документация по API FreeRTOS 8.2.х на английском языке. 3. FreeRTOS: использование стека и проверка стека на переполнение. 4. Проектирование стека и кучи в IAR. 5. FreeRTOS, STM32: отладка ошибок и исключений. 6. IAR C-SPY: предупреждение о переполнении стека. |