Мы компилируем встроенное приложение C / C ++, которое развертывается на экранированном устройстве в среде, засыпанной ионизирующее излучение. Мы используем GCC и кросс-компиляцию для ARM. При развертывании наше приложение генерирует некоторые ошибочные данные и вылетает чаще, чем хотелось бы. Аппаратное обеспечение разработано для этой среды, и наше приложение работает на этой платформе в течение нескольких лет.
Есть ли какие-либо изменения, которые мы можем внести в наш код, или улучшения во время компиляции, которые можно внести, чтобы идентифицировать / исправить мягкие ошибки и повреждение памяти, вызванное расстраивает одно событие? Удалось ли другим разработчикам снизить вредное воздействие программных ошибок на долго работающее приложение?
Работаю около 4-5 лет с разработкой программного обеспечения / прошивки и тестированием среды миниатюрные спутники* Я хотел бы поделиться своим опытом здесь.
* (Миниатюрные спутники намного более склонны к одиночным событиям, чем большие спутники из-за его относительно небольших, ограниченных размеров для своих электронных компонентов)
Быть очень кратким и прямым: нет механизма, чтобы оправиться от обнаружимый, ошибочный
ситуация самой программой / прошивкой без, хотя бы один
копия из минимальная рабочая версия программного обеспечения / прошивки где-то за восстановление цель — и с аппаратная поддержка восстановления (Функциональные).
Теперь эта ситуация обычно обрабатывается как на аппаратном, так и на программном уровне. Здесь, по вашей просьбе, я поделюсь тем, что мы можем сделать на уровне программного обеспечения.
…цель восстановления …. Предоставьте возможность обновлять / перекомпилировать / перепрошивать ваше программное обеспечение / прошивку в реальных условиях. Это почти обязательный функция для любого программного обеспечения / прошивки в сильно ионизированной среде. Без этого ты мог иметь избыточное программное / аппаратное обеспечение столько, сколько вы хотите, но в один прекрасный момент они все взорвутся. Итак, подготовьте эту функцию!
…минимальная рабочая версия … Имейте отзывчивые, множественные копии, минимальную версию программного обеспечения / прошивки в вашем коде. Это похоже на безопасный режим в Windows. Вместо того, чтобы иметь только одну полнофункциональную версию вашего программного обеспечения, имейте несколько копий минимальной версии вашего программного обеспечения / прошивки. Минимальная копия обычно имеет намного меньший размер, чем полная копия, и почти всегда имеет только следующие две или три функции:
…скопировать … куда-нибудь … Есть где-нибудь избыточное программное обеспечение / прошивка.
Вы могли бы, с или же без избыточного оборудования, попробуйте установить избыточное программное обеспечение / прошивку в вашем ARM uC. Обычно это делается с помощью двух или более идентичных программ / прошивок в отдельных адресах который посылает сердцебиение друг другу — но только один будет активен одновременно. Если известно, что одно или несколько программ / микропрограмм не отвечают, переключитесь на другое программное обеспечение / микропрограммное обеспечение. Преимущество использования этого подхода заключается в том, что мы можем произвести функциональную замену сразу после возникновения ошибки — без какого-либо контакта с какой-либо внешней системой / стороной, ответственной за обнаружение и устранение ошибки (в случае спутника, как правило, это Центр управления полетом ( MCC)).
Строго говоря, недостатком аппаратного обеспечения является недостаток этого не могу Устранить все единая точка неудач. По крайней мере, у вас все еще будет один единая точка отказа, которая сам переключатель (или часто начало кода). Тем не менее, для устройства, ограниченного по размеру в сильно ионизованной среде (например, пико / фемтосателлиты), сокращение одной точки отказов до одной точки без дополнительное оборудование все еще стоит рассмотреть. Более того, кусок кода для переключения, безусловно, будет намного меньше, чем код для всей программы, что значительно снизит риск попадания в него одного события.
Но если вы этого не делаете, у вас должна быть хотя бы одна копия во внешней системе, которая может войти в контакт с устройством и обновить программное обеспечение / прошивку (в случае спутника это снова центр управления полетами).
…обнаруживаемая ошибочная ситуация .. Ошибка должна быть обнаруживаемый, обычно аппаратно схема исправления ошибок / обнаружения или небольшим фрагментом кода для исправления ошибок / обнаружения. Лучше всего ставить такой код маленьким, множественным и независимый из основного программного обеспечения / прошивки. Его основная задача только для проверки / исправления. Если аппаратная схема / прошивка надежный (например, он более защищен от радиации, чем остальные — или имеет несколько схем / логических схем), тогда вы можете рассмотреть возможность исправления ошибок с его помощью. Но если это не так, лучше сделать это как обнаружение ошибок. Коррекция может осуществляться внешней системой / устройством. Для исправления ошибок вы могли бы рассмотреть возможность использования базового алгоритма исправления ошибок, такого как Hamming / Golay23, потому что они могут быть легче реализованы как в схеме / программном обеспечении. Но в конечном итоге это зависит от возможностей вашей команды. Для обнаружения ошибок обычно используется CRC.
…аппаратная поддержка восстановления Теперь перейдем к самому сложному аспекту в этом вопросе. В конечном счете, для восстановления требуется аппаратное обеспечение, отвечающее за восстановление. по крайней мере функциональны. Если оборудование постоянно сломано (обычно происходит после его Общая ионизирующая доза достигает определенного уровня), то для программного обеспечения (к сожалению) нет никакого способа помочь в восстановлении. Таким образом, аппаратное обеспечение по праву является первостепенной задачей для устройства, подверженного высокому уровню излучения (такого как спутник).
В дополнение к предложению для вышеупомянутой ошибки прошивки из-за одного события, я также хотел бы предложить вам:
Алгоритм обнаружения и / или исправления ошибок в межсистемном протоколе связи. Это еще одно, почти необходимо иметь, чтобы избежать неполных / неправильных сигналов, полученных от другой системы
Фильтр в вашем чтении АЦП. Делать не используйте чтение АЦП напрямую. Фильтруйте его по медианному фильтру, среднему фильтру или любым другим фильтрам — никогда доверяйте единственному значению чтения. Образец больше, а не меньше — разумно.
НАСА имеет статья о радиационной стойкости программного обеспечения. Он описывает три основные задачи:
Обратите внимание, что частота сканирования памяти должна быть достаточно частой, чтобы много-битные ошибки возникали редко, так как большинство ECC память может восстанавливаться после однобитовых ошибок, а не многобитовых ошибок.
Надежное восстановление после ошибок включает передачу потока управления (как правило, перезапуск процесса в точке, предшествующей ошибке), освобождение ресурса и восстановление данных.
Их основная рекомендация по восстановлению данных состоит в том, чтобы избежать необходимости в этом, поскольку промежуточные данные рассматриваются как временные, так что перезапуск до того, как ошибка также откатит данные до надежного состояния. Это звучит аналогично понятию «транзакции» в базах данных.
Они обсуждают методы, особенно подходящие для объектно-ориентированных языков, таких как C ++. Например
И так уж вышло, НАСА использовало C ++ для крупных проектов, таких как Марсоход.
Абстракция и инкапсуляция класса C ++ обеспечили быструю разработку и тестирование среди множества проектов и разработчиков.
Они избегали определенных функций C ++, которые могут создавать проблемы:
new
а также delete
)new
чтобы избежать возможности повреждения кучи системы).Вот некоторые мысли и идеи:
Используйте ROM более творчески.
Храните все, что вы можете в ROM. Вместо того, чтобы вычислять вещи, храните справочные таблицы в ПЗУ. (Убедитесь, что ваш компилятор выводит таблицы поиска в раздел только для чтения! Распечатайте адреса памяти во время выполнения, чтобы проверить!) Сохраните таблицу векторов прерываний в ПЗУ. Конечно, запустите несколько тестов, чтобы увидеть, насколько надежна ваша ПЗУ по сравнению с вашей ОЗУ.
Используйте вашу лучшую оперативную память для стека.
SEU в стеке, вероятно, являются наиболее вероятным источником сбоев, потому что именно там обычно живут такие вещи, как индексные переменные, переменные состояния, адреса возврата и указатели различных типов.
Реализуйте процедуры таймера-тика и сторожевого таймера.
Вы можете запускать процедуру проверки работоспособности каждый тик таймера, а также подпрограмму сторожевого таймера для обработки блокировки системы. Ваш основной код также может периодически увеличивать счетчик, чтобы указывать прогресс, и процедура проверки работоспособности может гарантировать, что это произошло.
Воплощать в жизнь исправляющих ошибки-коды в программном обеспечении.
Вы можете добавить избыточность к своим данным, чтобы иметь возможность обнаруживать и / или исправлять ошибки. Это добавит время обработки, потенциально оставляя процессор подверженным облучению в течение более длительного времени, увеличивая тем самым вероятность ошибок, поэтому вы должны учитывать компромисс.
Помните тайники.
Проверьте размеры кэшей вашего процессора. Данные, к которым вы недавно обращались или изменяли, вероятно, будут в кеше. Я считаю, что вы можете отключить по крайней мере некоторые из кэшей (с большой производительностью); Вы должны попробовать это, чтобы увидеть, насколько кеши восприимчивы к SEU. Если кеши более устойчивы, чем ОЗУ, то вы можете регулярно читать и перезаписывать критически важные данные, чтобы убедиться, что они остаются в кеше и вернуть ОЗУ в рабочее состояние.
Используйте ловко обработчики ошибок страницы.
Если вы отметите страницу памяти как отсутствующую, ЦП выдаст ошибку страницы, когда вы попытаетесь получить к ней доступ. Вы можете создать обработчик ошибок страницы, который выполняет некоторую проверку перед обслуживанием запроса на чтение. (Операционные системы ПК используют это для прозрачной загрузки страниц, которые были выгружены на диск.)
Используйте язык ассемблера для критических вещей (которые могут быть всем).
С ассемблером вы знать что находится в регистрах и что находится в оперативной памяти; вы знать какие специальные таблицы ОЗУ использует ЦП, и вы можете создать обходные пути, чтобы снизить риск.
использование objdump
на самом деле посмотреть на сгенерированный ассемблер и выяснить, сколько кода занимает каждая ваша подпрограмма.
Если вы используете большую ОС, такую как Linux, вы напрашиваетесь на неприятности; просто так много сложностей и так много вещей, которые могут пойти не так.
Помните, что это игра вероятностей.
Комментатор сказал
Каждая подпрограмма, которую вы пишете для обнаружения ошибок, может не справиться с одной и той же причиной.
Хотя это действительно так, вероятность ошибок в (скажем) 100 байтах кода и данных, необходимых для правильной работы процедуры проверки, намного меньше, чем вероятность ошибок в других местах. Если ваш ПЗУ достаточно надежен и почти весь код / данные фактически находятся в ПЗУ, ваши шансы еще выше.
Используйте избыточное оборудование.
Используйте 2 или более идентичных аппаратных настроек с идентичным кодом. Если результаты отличаются, необходимо выполнить сброс. С 3 или более устройствами вы можете использовать систему «голосования», чтобы попытаться определить, какое из них было взломано.
Вас также может заинтересовать обширная литература по теме алгоритмической отказоустойчивости. Это включает в себя старое назначение: написать сортировку, которая правильно сортирует свои входные данные, когда постоянное число сравнений не удастся (или, что более опасная версия, когда асимптотическое число неудачных сравнений масштабируется как log(n)
за n
сравнения).
Место, где можно начать читать, — статья Хуанга и Авраама 1984 года «.Основанная на алгоритме отказоустойчивость для матричных операций«Их идея в некоторой степени похожа на гомоморфные зашифрованные вычисления (но на самом деле это не то же самое, поскольку они пытаются обнаружить / исправить ошибки на уровне операций).
Более поздний потомок этого документа — Босилька, Дельма, Донгарра и Лангу ».Отказоустойчивость на основе алгоритма, применяемая для высокопроизводительных вычислений».
Написание кода для радиоактивных сред на самом деле ничем не отличается от написания кода для любого критически важного приложения.
В дополнение к тому, что уже упоминалось, вот несколько разных советов:
ВАЖНО: Вы должны обеспечить целостность внутренних регистров MCU. Весь контроль & Регистры состояния аппаратных периферийных устройств, которые могут быть записаны, могут быть расположены в оперативной памяти и поэтому уязвимы.
Чтобы защитить себя от повреждения реестра, желательно выбрать микроконтроллер со встроенными функциями «однократной записи» регистров. Кроме того, вам нужно хранить значения по умолчанию всех аппаратных регистров в NVM и регулярно копировать эти значения в свои регистры. Вы можете обеспечить целостность важных переменных таким же образом.
Примечание: всегда используйте защитное программирование. Это означает, что вы должны настроить все регистры в MCU, а не только те, которые используются приложением. Вам не нужно, чтобы какое-то случайное аппаратное периферийное устройство внезапно просыпалось.
Существуют всевозможные методы для проверки ошибок в ОЗУ или NVM: контрольные суммы, «шаблоны ходьбы», программный ECC и т. Д. И т. Д. На сегодняшний день лучшее решение — это не использовать ни один из них, а использовать MCU со встроенным ECC и аналогичные проверки. Потому что делать это в программном обеспечении сложно, и проверка ошибок сама по себе может привести к ошибкам и неожиданным проблемам.
Понять и принять концепцию защитного программирования. Это означает, что ваша программа должна обрабатывать все возможные случаи, даже те, которые не могут возникнуть в теории. Примеры.
Высококачественная критически важная прошивка обнаруживает как можно больше ошибок, а затем безопасно их игнорирует.
ВАЖНО: не используйте значения по умолчанию для статических переменных продолжительности хранения. То есть не доверяйте содержимому по умолчанию .data
или же .bss
, Между моментом инициализации и моментом, когда переменная фактически используется, может пройти любое количество времени, а ОЗУ могло иметь достаточно времени для повреждения. Вместо этого напишите программу так, чтобы все такие переменные устанавливались из NVM во время выполнения, незадолго до того времени, когда такая переменная используется впервые.
На практике это означает, что если переменная объявлена в области видимости файла или как static
, вы никогда не должны использовать =
инициализировать его (или вы могли бы, но это бессмысленно, потому что вы все равно не можете полагаться на значение). Всегда устанавливайте его во время выполнения, перед использованием. Если есть возможность многократно обновлять такие переменные из NVM, то сделайте это.
Аналогично в C ++, не полагайтесь на конструкторы для статических переменных продолжительности хранения. Пусть конструктор (ы) вызовет общедоступную подпрограмму «set-up», которую вы также можете вызвать позже во время выполнения, прямо из приложения вызывающей стороны.
Если возможно, удалите код запуска «copy-down», который инициализирует .data
а также .bss
(и вызывает конструкторы C ++) полностью, так что вы получите ошибки компоновщика, если вы пишете код, полагаясь на него. Многие компиляторы имеют возможность пропустить это, обычно называемое «минимальный / быстрый запуск» или подобное.
Это означает, что любые внешние библиотеки должны быть проверены, чтобы они не содержали такой зависимости.
Реализуйте и определите безопасное состояние для программы, куда вы будете возвращаться в случае критических ошибок.
Может быть возможно использовать C для написания программ, которые ведут себя надежно в таких средах, но только если большинство форм оптимизации компилятора отключены. Оптимизирующие компиляторы предназначены для замены многих, казалось бы, избыточных шаблонов кодирования на «более эффективные», и могут не иметь ни малейшего представления о причине, по которой программист тестирует x==42
когда компилятор знает, что нет пути x
может содержать что-то еще, потому что программист хочет предотвратить выполнение определенного кода с помощью x
сохраняя какое-то другое значение — даже в тех случаях, когда единственным способом сохранить это значение было бы, если бы система получила какой-то электрический сбой.
Объявление переменных как volatile
часто полезно, но не может быть панацеей.
Особо важно отметить, что безопасное кодирование часто требует
операции имеют аппаратные блокировки, для активации которых требуется несколько шагов,
и этот код будет написан с использованием шаблона:
... code that checks system state
if (system_state_favors_activation)
{
prepare_for_activation();
... code that checks system state again
if (system_state_is_valid)
{
if (system_state_favors_activation)
trigger_activation();
}
else
perform_safety_shutdown_and_restart();
}
cancel_preparations();
Если компилятор переводит код относительно буквально, и если все
проверки состояния системы повторяются после prepare_for_activation()
,
система может быть устойчивой практически к любому правдоподобному событию одиночного сбоя,
даже те, которые произвольно повредят счетчик программ и стек. Если
сбой происходит сразу после звонка prepare_for_activation()
, это будет означать,
эта активация была бы уместна (так как нет другой причины
prepare_for_activation()
был бы вызван до глюка). Если
сбой заставляет код достигать prepare_for_activation()
неуместно, но есть
нет никаких последующих событий сбоя, не было бы никакого способа для кода в последующем
достичь trigger_activation()
не пройдя проверку проверки или сначала не вызвав cancel_preparations [если стек выпадает, выполнение может продолжаться до определенного момента trigger_activation()
после контекста, который называется prepare_for_activation()
возвращается, но звонок cancel_preparations()
произошло бы между вызовами prepare_for_activation()
а также trigger_activation()
тем самым делая последний вызов безвредным.
Такой код может быть безопасным в традиционном Си, но не с современными компиляторами Си. Такие компиляторы могут быть очень опасными в такой среде, потому что агрессивно они стремятся включать только код, который будет уместен в ситуациях, которые могут возникнуть с помощью какого-то четко определенного механизма и чьи последствия будут также хорошо определены. Код, целью которого будет обнаружение и очистка после сбоев, в некоторых случаях может ухудшить ситуацию. Если компилятор определит, что попытка восстановления в некоторых случаях вызовет неопределенное поведение, это может означать, что условия, которые могут потребовать такого восстановления в таких случаях, не могут возникнуть, таким образом устраняя код, который бы проверял их.
Это чрезвычайно широкий предмет. По сути, вы не можете действительно восстановиться после повреждения памяти, но вы можете, по крайней мере, попытаться быстро провалиться. Вот несколько методов, которые вы могли бы использовать:
постоянные данные контрольной суммы. Если у вас есть какие-либо данные конфигурации, которые остаются постоянными в течение длительного времени (включая настроенные вами аппаратные регистры), вычислите его контрольную сумму при инициализации и проверяйте ее периодически. Когда вы видите несоответствие, пришло время для повторной инициализации или сброса.
хранить переменные с избыточностью. Если у вас есть важная переменная x
, напишите его значение в x1
, x2
а также x3
и читать это как (x1 == x2) ? x2 : x3
,
воплощать в жизнь мониторинг выполнения программы. XOR — глобальный флаг с уникальным значением в важных функциях / ветвях, вызываемых из основного цикла. Запуск программы в среде без излучения с почти 100% тестовым покрытием должен дать вам список допустимых значений флага в конце цикла. Сброс, если вы видите отклонения.
контролировать указатель стека. В начале основного цикла сравните указатель стека с его ожидаемым значением. Сброс на отклонение.
Что может помочь вам сторожевая собака. В 1980-х годах сторожевые устройства широко использовались в промышленных вычислениях. Аппаратные сбои были гораздо более распространенными, чем другой — другой ответ также относится к этому периоду.
Сторожевой таймер — это комбинированная аппаратная / программная функция. Аппаратное обеспечение представляет собой простой счетчик, который отсчитывает от числа (скажем, 1023) до нуля. TTL или другая логика может быть использована.
Программное обеспечение было разработано таким образом, чтобы одна программа контролировала правильную работу всех основных систем. Если эта процедура завершается правильно = обнаруживает, что компьютер работает нормально, для счетчика возвращается значение 1023.
Общая конструкция такова, что в нормальных условиях программное обеспечение предотвращает достижение аппаратным счетчиком нуля. Если счетчик достигает нуля, аппаратная часть счетчика выполняет свою единственную задачу и сбрасывает всю систему. С точки зрения счетчика ноль равен 1024, и счетчик продолжает обратный отсчет.
Этот сторожевой таймер обеспечивает перезагрузку подключенного компьютера во многих случаях сбоя. Я должен признать, что я не знаком с оборудованием, которое способно выполнять такую функцию на современных компьютерах. Интерфейсы к внешнему оборудованию теперь намного сложнее, чем раньше.
Недостатком сторожевого таймера является то, что система недоступна с момента сбоя до тех пор, пока счетчик сторожевого таймера не достигнет нуля + время перезагрузки. Хотя это время обычно намного короче, чем любое внешнее или человеческое вмешательство, поддерживаемое оборудование должно быть в состоянии работать без компьютерного контроля в течение этого периода времени.