Pull to refresh

Вся правда об ОСРВ. Статья #31. Диагностика и проверка ошибок ОСРВ

Reading time11 min
Views1.6K
Original author: Colin Walls


Обработка ошибок – не самая распространенная вещь для операционных систем, предназначенных для приложений встраиваемых систем. Это является неизбежным результатом ограниченных ресурсов, поскольку все встраиваемые системы имеют те или иные ограничения. И лишь небольшое количество таких систем имеет возможность вести себя как настольные системы, то есть предлагать пользователю возможность выбора действий в случае возникновения исключительных событий.

В Nucleus SE, в целом, существуют три типа проверки ошибок:
  • средства для проверки работоспособности выбранной конфигурации, чтобы убедиться, что выбранные параметры не приводят к возникновению ошибок;
  • опционально включаемый код для проверки поведения времени выполнения;
  • определенные функции API, способствующие разработке более надежного кода.

Все это будет рассмотрено в данной статье, вместе с некоторыми идеями, касающимися диагностики силами пользователя.

Предыдущие статьи серии:
Статья #30. Инициализация и процедуры запуска Nucleus SE
Статья #29. Прерывания в Nucleus SE
Статья #28. Программные таймеры
Статья #27. Системное время
Статья #26. Каналы: вспомогательные службы и структуры данных
Статья #25. Каналы передачи данных: введение и основные службы
Статья #24. Очереди: вспомогательные службы и структуры данных
Статья #23. Очереди: введение и базовые службы
Статья #22. Почтовые ящики: вспомогательные службы и структуры данных
Статья #21. Почтовые ящики: введение и базовые службы
Статья #20. Семафоры: вспомогательные службы и структуры данных
Статья #19. Семафоры: введение и базовые службы
Статья #18. Группы флагов событий: вспомогательные службы и структуры данных
Статья #17. Группы флагов событий: введение и базовые службы
Статья #16. Сигналы
Статья #15. Разделы памяти: службы и структуры данных
Статья #14. Разделы памяти: введение и базовые службы
Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Статья #12. Службы для работы с задачами
Статья #11. Задачи: конфигурация и введение в API
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.



Проверка настроек


Nucleus SE разрабатывался с упором на высокую степень конфигурируемости пользователем, что должно обеспечить наилучшее использование доступных ресурсов. Такая конфигурируемость является сложной задачей, так как число возможных параметров и взаимозависимостей между ними огромно. Как было сказано во многих предыдущих статьях, большинство действий пользователя по настройке Nucleus SE выполняется при помощи директив #define в файле nuse_config.h.

Чтобы помочь выявить ошибки конфигураций, в файл nuse_config.c через #include включен файл nuse_config_check.h, который выполняет проверки целостности директив #define. Ниже приведен фрагмент этого файла:
/*** Tasks and task control ***/
#if NUSE_TASK_NUMBER < 1 || NUSE_TASK_NUMBER > 16
    #error NUSE: invalid number of tasks - must be 1-16
#endif
#if NUSE_TASK_RELINQUISH && (NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER)
    #error NUSE: NUSE_Task_Relinquish() selected - not valid with
                                           priority scheduler
#endif
#if NUSE_TASK_RESUME && !NUSE_SUSPEND_ENABLE
    #error NUSE: NUSE_Task_Resume() selected  - task suspend not
                                           enabled
#endif
#if NUSE_TASK_SUSPEND && !NUSE_SUSPEND_ENABLE
    #error NUSE: NUSE_Task_Suspend() selected  - task suspend not
                                           enabled
#endif
#if NUSE_INITIAL_TASK_STATE_SUPPORT && !NUSE_SUSPEND_ENABLE
    #error NUSE: Initial task state enabled - task suspend not
                                           enabled
#endif
/*** Partition pools ***/
#if NUSE_PARTITION_POOL_NUMBER > 16
    #error NUSE: invalid number of partition pools - must be 0-16
#endif
#if NUSE_PARTITION_POOL_NUMBER == 0
    #if NUSE_PARTITION_ALLOCATE
        #error NUSE: NUSE_Partition_Allocate() enabled – no
                                partition pools configured
    #endif
    #if NUSE_PARTITION_DEALLOCATE
        #error NUSE: NUSE_Partition_Deallocate() enabled – no
                                partition pools configured
    #endif
    #if NUSE_PARTITION_POOL_INFORMATION
        #error NUSE: NUSE_Partition_Pool_Information() enabled –
                                no partition pools configured
    #endif
#endif

Приведенный код выполняет следующие проверки:
  • проверка того, что сконфигурирована хотя бы одна, но не более шестнадцати задач;
  • подтверждение того, что выбранные функции API совместимы с выбранным планировщиком и другими заданными параметрами;
  • проверка того, что было создано не более шестнадцати экземпляров прочих объектов ядра;
  • подтверждение, что не были выбраны функции API, относящиеся к необъявленным объектам;
  • обеспечение того, чтобы функции API для сигналов и системного времени не используются при деактивированной поддержке этих служб;
  • верификация выбранного типа планировщика и связанных с этим параметров.

Во всех случаях обнаружение ошибок приводит к исполнению директивы #error при компиляции. Обычно это приводит к остановке компиляции и отображению соответствующего сообщения.

Этот файл не гарантирует невозможность создания некорректной конфигурации/настройки, но делает ее очень маловероятной.

Проверка параметров API


Как и Nucleus RTOS, Nucleus SE имеет возможность включения кода для проверки параметров вызова функций API во время выполнения. Обычно это используется только во время начальной отладки и тестирования, так как в окончательном коде программы избыточное расходование памяти нежелательно.

Проверка параметров активируется при помощи присвоения параметру NUSE_API_PARAMETER_CHECKING в файле nuse_config.h значения TRUE. Это приводит к компиляции требуемого дополнительного кода. Ниже приведен пример проверки параметров функции API:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message,
                           U8 suspend)
{
      STATUS return_value;
      #if NUSE_API_PARAMETER_CHECKING
         if (mailbox >= NUSE_MAILBOX_NUMBER)
         {
               return NUSE_INVALID_MAILBOX;
         }
         if (message == NULL)
         {
               return NUSE_INVALID_POINTER;
         }
         #if NUSE_BLOCKING_ENABLE
            if ((suspend != NUSE_NO_SUSPEND) &&
                  (suspend != NUSE_SUSPEND))
            {
                  return NUSE_INVALID_SUSPEND;
            }
            #else
               if (suspend != NUSE_NO_SUSPEND)
               {
                     return NUSE_INVALID_SUSPEND;
               }
            #endif
      #endif

Такая проверка параметров может привести к тому, что вызов API выдаст на выходе код ошибки. Он представляет из себя отрицательное значение вида NUSE_INVALID_xxx (например, NUSE_INVALID_POINTER) – полный набор определений содержится в файле nuse_codes.h.

Для обработки значений ошибок может быть добавлен дополнительный код приложения (возможно, созданный при помощи условной компиляции), однако для их обнаружения лучше использовать средства мониторинга данных современных отладчиков встраиваемого ПО.

Проверка параметров приводит к дополнительному расходованию памяти (дополнительный код) и влияет на производительность кода, следовательно, ее использование повлияет на всю систему. Так как весь исходный код Nucleus SE доступен разработчику, проверку и отладку можно выполнять вручную на окончательном коде приложения, если требуется абсолютная точность.

Проверка стека задач


Пока не используется планировщик Run to Completion, Nucleus SE предоставляет возможность проверки стека задач, которая похожа на подобную функцию в Nucleus RTOS и показывает оставшееся пространство в стеке. Этот служебный вызов API (NUSE_Task_Check_Stack()) был подробно описан в одной из предыдущих статей (#12). Некоторые идеи, касающиеся проверки ошибок стека приведены далее в этой статье в разделе «Пользовательская диагностика».

Информация о версии


Nucleus RTOS и Nucleus SE имеют функцию API, которая просто возвращает информацию о версии/сборке ядра.

Служебный вызов Nucleus RTOS API


Прототип служебного вызова:
CHAR *NU_Release_Information(VOID);

Параметры: отсутствуют.

Возвращаемое значение:
Указатель на строку, содержащую информацию о версии и завершающаяся нулевым байтом.

Служебный вызов Nucleus SE API


Этот вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:
char * NUSE_Release_Information(void);

Параметры: отсутствуют.

Возвращаемое значение:
Указатель на строку, содержащую информацию о версии и завершающаяся нулевым байтом.

Реализация вызова для получения информации о сборке Nucleus SE


Реализация этого вызова API довольно проста. Возвращается указатель на константную строку NUSE_Release_Info, которая объявляется и инициализируется в файле nuse_globals.c.

Эта строка имеет вид Nucleus SE – Xyymmmdd, где:
X – статус сборки: A = альфа; B = бета; R = релиз
yy – год релиза
mm – месяц релиза
dd – день релиза

Совместимость с Nucleus RTOS


Nucleus RTOS содержит опциональную поддержку журнала истории. Ядро записывает детали различных системных действий. Имеются функции API, которые позволяют программам:
  • активировать/деактивировать запись в журнал;
  • создавать запись в журнале;
  • получать запись в журнале.

Эта возможность не поддерживается в Nucleus SE.

Nucleus RTOS также имеет несколько макросов для управления ошибками, позволяющие выполнять подтверждения отсутствия ошибок (ASSERT) и предоставляющие возможность вызывать заданные пользователем функции критических ошибок. Они опционально включаются в сборку ОС. Nucleus SE не поддерживает данный функционал.

Пользовательская диагностика


До сих пор в этой статье мы рассматривали средства диагностики и проверки ошибок, предоставляемые самой Nucleus SE. Теперь стоит рассказать, как могут быть реализованы заданные пользователем или ориентированные на конкретное приложение средства диагностики при помощи инструментов, предоставляемых ядром, и/или наших знаний о его внутренней структуре и реализации

Диагностика конкретного приложения


Практически в каждое приложение можно добавить дополнительный код для проверки его целостности во время выполнения. Многозадачное ядро позволяет легко и просто создать специальную задачу для выполнения данной работы. Очевидно, в рамках данной статьи мы не будем рассматривать слишком необычные случаи диагностики, а рассмотрим некоторые общие идеи.

Проверки памяти


Очевидно, что правильная работа памяти является критически важной для целостности любой процессорной системы. Не менее очевидно и то, что критическая ошибка не позволит запустить, не то что диагностику, а весь программный продукт в целом (примечание переводчика: Кстати, именно такой случай мы разбирали в статье «Поддельная голубая пилюля»). Однако существуют ситуации, когда появляется определенная ошибка, являющаяся серьезным поводом для беспокойства, однако не мешающая выполнению кода. Тестирование памяти – довольно сложная тема, которая не входит в рамки этой статьи, поэтому я приведу лишь некоторые общие идеи.

Две наиболее распространенные ошибки, возникающие в ОЗУ:
  • «залипающие биты», когда бит имеет значение 0 или 1, которое не может быть изменено;
  • «перекрестные помехи», когда соседние биты вызывают помехи друг у друга.

Обе ошибки можно проверить при помощи записи и чтения определенных тестовых паттернов в каждую область ОЗУ по очереди. Некоторые проверки могут быть выполнены только при запуске, даже до того, как будет сформирован стек (примечание переводчика: в упомянутой выше статье в итоге выяснилось, что именно первое использование стека все сразу и рушило). Например, проверка «бегущая единица», в которой каждому биту памяти присваивается значение единицы, а все остальные биты проверяются, чтобы убедиться, что они равны нулю. Другие побитовые паттерны тестирования могут выполняться во время работы, при условии что пока область ОЗУ повреждена, контекстного переключения не произойдет. Использование макросов ограничения критической секции Nucleus SE NUSE_CS_Enter() и NUSE_CS_Exit(), довольно просто и масштабируемо.

Различные виды ПЗУ также подвержены периодическим ошибкам, однако инструментов для их проверки не так много. Здесь могла бы пригодиться контрольная сумма, вычисляемая после того, как код собран. Эта проверка может выполняться при загрузке и, возможно, во время выполнения.

Ошибка в логике адресации памяти может повлиять как на ПЗУ, так и на ОЗУ. Можно разработать специальную проверку этой ошибки, однако она, скорее всего, будет обнаружена в рамках проверок, описанных выше.

Проверка периферийных устройств


Помимо CPU, периферийные схемы также могут быть подвержены ошибкам. Разумеется, это сильно варьируется от системы к системе, однако в большинстве устройств имеются те или иные способы проверки их целостности при помощи диагностического программного обеспечения. Например, канал связи может иметь закольцованный режим проверки, в котором любые данные, поступающие на канал, немедленно возвращаются.

Обслуживание сторожевого таймера


Разработчики встраиваемых систем часто используют «сторожевой» таймер. Это периферийное устройство, которое либо прерывает CPU и ожидает ответа, либо (что более предпочтительно) требует периодического обращения от программного обеспечения. В обоих случаях распространенным результатом срабатывания сторожевого таймера является сброс системы.

Эффективное использование сторожевого таймера в многозадачной среде представляет из себя сложную проблему. Если сделать задачу, которая периодически обращается к нему (сторожевому таймеру), это подтвердит, что эта конкретная задача работает. Возможным решением может быть реализация «задачи-диспетчера». Пример такой задачи будет приведен ниже.

Проверка переполнения стека


Если вы не используете планировщик Run to Completion, в приложении Nucleus SE для каждой задачи будет создан стек. Целостность этих стеков очень важна, однако объем ОЗУ, скорее всего, будет ограничен, следовательно, важно сделать размер приложения оптимальным. Статически предугадать требования к стеку каждой задачи возможно, но очень сложно. Стек должен иметь размер, достаточный даже для самых вложенных функций, вместе с наиболее требовательным обработчиком прерывания. Более простым подходом к решению этой проблемы будет использование исчерпывающего тестирования во время выполнения.

Говоря в общем, существует два подхода к верификации стека. Если используется сложный встраиваемый программный отладчик, границы стека могут отслеживаться, а все нарушения будут обнаружены. Размещение и размер стеков Nucleus SE доступны в глобальных структурах данных ПЗУ: NUSE_Task_Stack_Base[] и NUSE_Task_Stack_Size[].

Альтернативой является тестирование во время выполнения. Распространенным подходом является использование «защитных слов» (guard words) в конце каждого стека, обычно это первый элемент каждой области данных стека. Эти слова инициализируются распознаваемым ненулевым значением. Затем служба/задача диагностики проверяет, изменялись ли эти слова, и выполняет соответствующие действия. Затирание защитного слова не означает, что стек переполнился, а указывает на то, что это вот-вот произойдет. Следовательно, программное обеспечение может продолжить работу, возможно, потребуется произвести корректирующие действия или сообщить об ошибке пользователю.

Задача-супервизор


Несмотря на то, что Nucleus SE не резервирует ни одну из возможных шестнадцати задач для собственных нужд, пользователь может выделить одну задачу под диагностику. Это может быть задача с низким приоритетом, которая просто использует любое «свободное» процессорное время, либо это может быть задача с высоким приоритетом, которая выполняется периодически, занимая короткий промежуток времени, что обеспечивает выполнение диагностики на регулярной основе.

Ниже приведен пример того, как подобная задача может работать.

Сигнальные флаги задачи-диспетчера используются для отслеживания работы шести критических задач системы. Каждая из этих задач использует определенный флаг (от бита 0 до бита 5) и должна регулярно устанавливать его. Задача-диспетчер сбрасывает все флаги, а затем приостанавливает свою работу на определенный период времени. Когда она возобновит работу, она ожидает, что все шесть задач «отметились» путем установки соответствующего флага, затем она ищет точное совпадение со значением b00111111 (из файла nuse_binary.h). Если все соответствует требованиям, она сбрасывает флаги и снова приостанавливается. Если нет, она вызывает процедуру обработки критических ошибок, которая в свою очередь может, например, перезагрузить систему.

В альтернативной реализации могли использоваться группы флагов событий. Это имеет смысл, если сигналы не используются в других местах в приложении (в противном случае это приведет к чрезмерному использованию ОЗУ всеми задачами) и особенно, если флаги событий используются для других целей.

Трассировка и профилирование


Несмотря на то, что многие современные отладчики встраиваемого программного обеспечения имеют высокую степень настраиваемости и могут использоваться для работы с ОСРВ, отладка многопоточного приложения все еще может представлять сложность. Широко используемым подходом является профилирование после выполнения, при котором код (ОСРВ) реализуется таким образом, чтобы подробный аудит его работы можно было проанализировать в ретроспективе. Обычно реализация такой службы включает два компонента:
  1. В ОСРВ добавляется дополнительный код для журналирования действий. Обычно он будет заключен в директивы препроцессора для использования условной компиляции. Этот код записывает несколько байт информации, когда случается важное событие (например, вызов функции API или переключение контекста). К такой информации могут относиться:
    • текущий адрес (PC);
    • ID текущей задачи (индекс);
    • индексы других используемых объектов;
    • код, соответствующий выполненной операции.

  2. Задача, выделенная под выгрузку буфера информации профиля во внешнее хранилище, обычно, в хост-компьютер.

Анализ полученных таким образом данных также потребует определенной работы, однако это не сложнее, чем использование обычной таблицы Excel.

В следующей статье мы подробно рассмотрим совместимость Nucleus SE и Nucleus RTOS.

Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.
Tags:
Hubs:
Total votes 9: ↑8 and ↓1+7
Comments0

Articles