it-swarm.com.ru

Составление приложения для использования в высокорадиоактивных средах

Мы компилируем встроенное приложение C/C++, которое развертывается в экранированном устройстве в среде, подвергшейся воздействию ионизирующего излучения . Мы используем GCC и кросс-компиляцию для ARM. При развертывании наше приложение генерирует ошибочные данные и вылетает чаще, чем хотелось бы. Аппаратное обеспечение разработано для этой среды, и наше приложение работает на этой платформе в течение нескольких лет.

Есть ли какие-то изменения, которые мы можем внести в наш код, или улучшения во время компиляции, которые могут быть сделаны для выявления/исправления программных ошибок и повреждения памяти, вызванного сбоями одного события ? Удалось ли другим разработчикам снизить вредное воздействие программных ошибок на долго работающее приложение?

1345
rook

НАСА имеет документ о радиационно-упрочненных программное обеспечение. Он описывает три основные задачи:

  1. Регулярный мониторинг памяти на наличие ошибок, а затем их устранение,
  2. надежные механизмы восстановления после ошибок и
  3. возможность перенастроить, если что-то больше не работает.

Обратите внимание, что частота сканирования памяти должна быть достаточно частой, чтобы редко возникали многобитовые ошибки, так как большая часть ECC memory может восстанавливаться после однобитовых ошибок, а не многобитовых ошибок.

Надежное восстановление после ошибок включает передачу потока управления (как правило, перезапуск процесса в точке, предшествующей ошибке), освобождение ресурса и восстановление данных.

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

Они обсуждают методы, особенно подходящие для объектно-ориентированных языков, таких как C++. Например

  1. Программные ECC для смежных объектов памяти
  2. Программирование по контракту : проверка предварительных условий и постусловий, затем проверка объекта, чтобы убедиться, что он все еще находится в действительном состоянии.

И так уж вышло, НАСА использовало C++ для крупных проектов, таких как Mars Rover .

Абстракция и инкапсуляция класса C++ обеспечили быструю разработку и тестирование среди множества проектов и разработчиков.

Они избегали определенных функций C++, которые могут создавать проблемы:

  1. Исключения
  2. Шаблоны
  3. Iostream (без консоли)
  4. Множественное наследование
  5. Перегрузка оператора (кроме new и delete)
  6. Динамическое распределение (используется выделенный пул памяти и размещение new, чтобы избежать возможности повреждения кучи системы).
381
rsjaffe

Вот некоторые мысли и идеи:

Используйте ROM более творчески.

Храните все, что вы можете в ROM. Вместо того, чтобы вычислять вещи, храните таблицы поиска в ПЗУ. (Убедитесь, что ваш компилятор выводит таблицы поиска в раздел только для чтения! Распечатайте адреса памяти во время выполнения, чтобы проверить!) Сохраните таблицу векторов прерываний в ПЗУ. Конечно, запустите несколько тестов, чтобы увидеть, насколько надежна ваша ROM по сравнению с вашей оперативной памятью.

Используйте ваши лучшие RAM для стека.

SEU в стеке, вероятно, являются наиболее вероятным источником сбоев, потому что именно там обычно живут такие вещи, как индексные переменные, переменные состояния, адреса возврата и указатели различных типов.

Реализовать функции таймера и сторожевого таймера.

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

Внедрить коды с исправлением ошибок в программном обеспечении.

Вы можете добавить избыточность к своим данным, чтобы иметь возможность обнаруживать и/или исправлять ошибки. Это добавит время обработки, потенциально оставляя процессор подверженным облучению в течение более длительного времени, увеличивая тем самым вероятность ошибок, поэтому вы должны учитывать компромисс.

Помните тайники.

Проверьте размеры кэшей вашего процессора. Данные, к которым вы недавно обращались или изменяли, вероятно, будут в кеше. Я полагаю, что вы можете отключить хотя бы некоторые кэши (с большими затратами на производительность); Вы должны попробовать это, чтобы увидеть, насколько кеши восприимчивы к SEU. Если кеши более жесткие, чем RAM, вы можете регулярно читать и перезаписывать критически важные данные, чтобы убедиться, что они остаются в кэше, и вернуть RAM обратно в строку.

Умно используйте обработчики ошибок страницы.

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

Используйте язык ассемблера для критических вещей (которые могут быть всем).

С языком ассемблера вы знаете что находится в регистрах, а что в оперативной памяти; вы знаете какие специальные RAM таблицы используют процессор, и вы можете создавать обходные пути, чтобы снизить риск.

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

Если вы используете большую ОС, такую ​​как Linux, вы напрашиваетесь на неприятности; просто так много сложностей и так много вещей, которые могут пойти не так.

Помните, что это игра вероятностей.

Комментатор сказал

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

Хотя это действительно так, вероятность ошибок в (скажем) 100 байтах кода и данных, необходимых для правильной работы процедуры проверки, намного меньше, чем вероятность ошибок в других местах. Если ваш ROM довольно надежный и почти весь код/​​данные фактически находятся в ROM, то ваши шансы еще выше.

Используйте избыточное оборудование.

Используйте 2 или более идентичных аппаратных настроек с идентичным кодом. Если результаты отличаются, необходимо выполнить сброс. С 3 или более устройствами вы можете использовать систему «голосования», чтобы попытаться определить, какое из них было взломано.

111
Artelius

Вас также может заинтересовать обширная литература по теме алгоритмической отказоустойчивости. Это включает в себя старое назначение: написать сортировку, которая правильно сортирует свои входные данные, когда постоянное число сравнений не удастся (или немного более злая версия, когда асимптотическое число неудачных сравнений масштабируется как log(n) для n сравнений).

Место для начала чтения - статья Хуанга и Авраама 1984 года " Отказоустойчивость на основе алгоритма для матричных операций ". Их идея в некоторой степени похожа на гомоморфные зашифрованные вычисления (но на самом деле это не то же самое, поскольку они пытаются обнаружить/исправить ошибки на уровне операций).

Более поздний потомок этого документа - « Отказоустойчивость на основе алгоритма, примененная Bosilca, Delmas, Dongarra и Langou для высокопроизводительных вычислений ».

96
Eric Towers

Написание кода для радиоактивных сред на самом деле ничем не отличается от написания кода для любого критически важного приложения. 

В дополнение к тому, что уже упоминалось, вот несколько разных советов:

  • Используйте каждодневные меры безопасности "хлеб с маслом", которые должны присутствовать в любой полупрофессиональной встроенной системе: внутренний сторожевой таймер, внутренний детектор низкого напряжения, внутренний монитор часов. Эти вещи даже не нужно упоминать в 2016 году, и они стандартны практически для каждого современного микроконтроллера.
  • Если у вас есть безопасный и/или ориентированный на автомобили MCU, он будет иметь определенные функции сторожевого таймера, такие как заданное временное окно, внутри которого вам необходимо обновить сторожевой таймер. Это предпочтительнее, если у вас есть критически важная система реального времени.
  • В общем, используйте MCU, подходящий для систем такого типа, а не какой-нибудь общий основной поток, который вы получили в пакете кукурузных хлопьев. В настоящее время почти каждый производитель MCU имеет специализированные MCU, разработанные для приложений безопасности (TI, Freescale, Renesas, ST, Infineon и т.д.). Они имеют множество встроенных функций безопасности, в том числе ядра блокировки: это означает, что 2 ядра процессора выполняют один и тот же код, и они должны соглашаться друг с другом.
  • ВАЖНО: Вы должны обеспечить целостность внутренних регистров MCU. Все регистры управления и состояния аппаратных периферийных устройств, которые доступны для записи, могут находиться в RAM памяти и поэтому уязвимы. 

    Чтобы защитить себя от повреждений реестра, желательно выбрать микроконтроллер со встроенными функциями «однократной записи» регистров. Кроме того, вам необходимо хранить значения по умолчанию всех аппаратных регистров в NVM и регулярно копировать эти значения в свои регистры. Вы можете обеспечить целостность важных переменных таким же образом.

    Примечание: всегда используйте защитное программирование. Это означает, что вы должны настроить all регистрируется в MCU, а не только те, которые используются приложением. Вам не нужно, чтобы какое-то случайное аппаратное периферийное устройство внезапно просыпалось.

  • Существуют всевозможные методы проверки ошибок в RAM или NVM: контрольные суммы, «шаблоны ходьбы», программный ECC и т.д. И т.д. На сегодняшний день лучшее решение - это не использовать ни один из них, а использовать MCU со встроенным ECC и аналогичными проверками. Поскольку делать это в программном обеспечении сложно, и проверка ошибок сама по себе может привести к ошибкам и неожиданным проблемам.

  • Используйте избыточность. Вы можете хранить как энергозависимую, так и энергонезависимую память в двух одинаковых «зеркальных» сегментах, которые всегда должны быть эквивалентными. К каждому сегменту может быть прикреплена контрольная сумма CRC.
  • Избегайте использования внешней памяти вне MCU.
  • Реализуйте стандартную процедуру обработки прерываний/обработчик исключений по умолчанию для всех возможных прерываний/исключений. Даже те, которые вы не используете. Процедура по умолчанию не должна делать ничего, кроме отключения собственного источника прерывания.
  • Понять и принять концепцию защитного программирования. Это означает, что ваша программа должна обрабатывать все возможные случаи, даже те, которые не могут возникнуть в теории. Примеры

    Высококачественная критически важная прошивка выявляет как можно больше ошибок, а затем безопасно их игнорирует.

  • Никогда не пишите программы, которые полагаются на плохо определенное поведение. Вполне вероятно, что такое поведение может резко измениться с неожиданными аппаратными изменениями, вызванными излучением или электромагнитными помехами. Лучший способ гарантировать, что ваша программа свободна от такого дерьма, - это использовать стандарт кодирования, такой как MISRA, вместе с инструментом статического анализатора. Это также поможет с защитным программированием и устранением ошибок (почему вы не хотите обнаруживать ошибки в любом приложении?).
  • ВАЖНО: не используйте значения по умолчанию для статических переменных продолжительности хранения. То есть не доверяйте содержимому по умолчанию .data или .bss. Между моментом инициализации и моментом, когда переменная фактически используется, может пройти любое количество времени; RAM могло бы быть достаточно много времени для повреждения. Вместо этого напишите программу так, чтобы все такие переменные устанавливались из NVM во время выполнения, незадолго до того времени, когда такая переменная используется впервые. 

    На практике это означает, что если переменная объявлена ​​в области видимости файла или как static, вы никогда не должны использовать = для ее инициализации (или вы можете, но это бессмысленно, потому что вы все равно не можете полагаться на значение). Всегда устанавливайте его во время выполнения, перед использованием. Если есть возможность многократно обновлять такие переменные из NVM, сделайте это.

    Точно так же в C++ не полагайтесь на конструкторы для статических переменных продолжительности хранения. Пусть конструктор (ы) вызовет общедоступную подпрограмму «set-up», которую вы также можете вызвать позже во время выполнения, прямо из приложения вызывающей стороны.

    Если возможно, удалите код запуска «copy-down», который полностью инициализирует .data и .bss (и вызывает конструкторы C++), чтобы вы могли получить ошибки компоновщика, если вы пишете код, полагаясь на такое. Многие компиляторы имеют возможность пропустить это, обычно называемое «минимальный/быстрый запуск» или подобное.

    Это означает, что любые внешние библиотеки должны быть проверены, чтобы они не содержали такой зависимости.

  • Реализуйте и определите безопасное состояние для программы, куда вы будете возвращаться в случае критических ошибок. 

  • Внедрение системы отчетов об ошибках/журнала ошибок всегда полезно.
38
Lundin

Может быть возможно использовать 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(), таким образом делая последний вызов безвредным ,.

Такой код может быть безопасным в традиционном Си, но не с современными компиляторами Си. Такие компиляторы могут быть очень опасными в такой среде, потому что агрессивно они стремятся включать только код, который будет уместен в ситуациях, которые могут возникнуть через какой-то четко определенный механизм и чьи последствия будут также хорошо определены. Код, целью которого будет обнаружение и устранение неисправностей, в некоторых случаях может ухудшить ситуацию. Если компилятор определит, что попытка восстановления в некоторых случаях вызовет неопределенное поведение, это может означать, что условия, которые могут потребовать такого восстановления в таких случаях, не могут возникнуть, таким образом устраняя код, который бы проверял их.

31
supercat

Что может вам помочь, это сторожевой таймер . В 1980-х годах сторожевые устройства широко использовались в промышленных вычислениях. Аппаратные сбои были гораздо более распространенными, чем другой - другой ответ также относится к этому периоду.

Сторожевой таймер - это комбинированная аппаратная/программная функция. Аппаратное обеспечение представляет собой простой счетчик, который отсчитывает от числа (скажем, 1023) до нуля. TTL или можно использовать другую логику.

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

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

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

Недостатком сторожевого таймера является то, что система недоступна с момента сбоя до тех пор, пока счетчик сторожевого таймера не достигнет нуля + время перезагрузки. Хотя это время, как правило, намного короче, чем любое внешнее или человеческое вмешательство, поддерживаемое оборудование должно иметь возможность работать без компьютерного контроля в течение этого периода времени.

27
OldFrank

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

  • данные константы контрольной суммы . Если у вас есть какие-либо данные конфигурации, которые остаются постоянными в течение длительного времени (включая настроенные вами аппаратные регистры), вычислите его контрольную сумму при инициализации и проверяйте ее периодически. Когда вы видите несоответствие, пришло время для повторной инициализации или сброса.

  • хранить переменные с избыточностью . Если у вас есть важная переменная x, запишите ее значение в x1, x2 и x3 и прочитайте ее как (x1 == x2) ? x2 : x3.

  • реализовать мониторинг потока программ . XOR глобальный флаг с уникальным значением в важных функциях/ветвях, вызываемых из основного цикла. Запуск программы в среде без излучения с почти 100% тестовым покрытием должен дать вам список допустимых значений флага в конце цикла. Сброс, если вы видите отклонения.

  • контролировать указатель стека . В начале основного цикла сравните указатель стека с его ожидаемым значением. Сброс на отклонение.

27
Dmitry Grigoryev

В этом ответе предполагается, что вы заинтересованы в том, чтобы система работала корректно, помимо системы с минимальной или быстрой стоимостью; большинство людей, играющих с радиоактивными вещами, ценят правильность/безопасность, а не скорость/стоимость

Несколько человек предложили внести изменения в аппаратные средства, которые вы можете внести (хорошо - здесь уже есть много полезного в ответах, и я не собираюсь повторять все это), а другие предложили избыточность (в принципе здорово), но я не думаю, что кто-нибудь предположил, как эта избыточность может работать на практике. Как вы терпите неудачу? Как вы знаете, когда что-то «пошло не так»? Многие технологии работают на основе того, что все будет работать, и поэтому неудача - это сложная задача. Однако некоторые технологии распределенных вычислений предназначены для масштабирования ожидаем сбой (в конце концов, при достаточном масштабе сбой одного узла из множества неизбежен при любом MTBF для одного узла); Вы можете использовать это для своего окружения.

Вот несколько идей:

  • Убедитесь, что все ваше оборудование реплицировано n раз (где n больше 2 и предпочтительно нечетно), и что каждый аппаратный элемент может связываться друг с другом аппаратным элементом. Ethernet является одним из очевидных способов сделать это, но есть много других гораздо более простых маршрутов, которые обеспечат лучшую защиту (например, CAN). Минимизируйте общие компоненты (даже источники питания). Это может означать, например, выборку входов АЦП в нескольких местах.

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

  • Принять протокол кворума для изменений состояния. Смотрите RAFT например. Поскольку вы работаете в C++, для этого есть хорошо известные библиотеки. Изменения в FSM будут сделаны только тогда, когда большинство узлов согласится. Используйте известную хорошую библиотеку для стека протоколов и протокола кворума вместо того, чтобы самим ее свернуть, иначе вся ваша хорошая работа по резервированию будет потрачена впустую, когда протокол кворума зависнет.

  • Убедитесь, что вы проверяли контрольную сумму (например, CRC/SHA) своего FSM и сохраняли CRC/SHA в самом FSM (а также передавали в сообщении и проверяли контрольную сумму самих сообщений). Получите, чтобы узлы регулярно проверяли свой FSM на соответствие этой контрольной сумме, проверяли контрольную сумму входящих сообщений и проверяли, чтобы их контрольная сумма соответствовала контрольной сумме кворума.

  • Встраивайте в свою систему как можно больше других внутренних проверок, заставляя узлы, которые обнаруживают свои собственные ошибки, перезагружаться (это лучше, чем выполнять половину работы, если у вас достаточно узлов). Попытайтесь позволить им аккуратно удалить себя из кворума во время перезагрузки на случай, если они не появятся снова. При перезагрузке попросите их проверить контрольную сумму образа программного обеспечения (и всего остального, что они загружают) и выполнить полный тест RAM, прежде чем снова ввести себя в кворум.

  • Используйте оборудование, чтобы поддержать вас, но делайте это осторожно. Например, вы можете получить оперативную память ECC и регулярно читать и записывать ее для исправления ошибок ECC (и паники, если ошибка не исправима). Однако (из памяти) static RAM гораздо более терпим к ионизирующему излучению, чем DRAM, во-первых, поэтому лучше использовать статический DRAM может . Смотрите также первый пункт в разделе «что бы я не делал».

Допустим, у вас есть 1% шанс отказа любого узла в течение одного дня, и давайте представим, что вы можете сделать отказы полностью независимыми. С 5 узлами вам понадобится три для отказа в течение одного дня, что составляет вероятность 0,0000%. С большим, ну, вы поняли.

То, что я хотел бы не сделать:

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

  • Бросайте свои собственные алгоритмы . Люди делали это раньше. Используйте их работу. Отказоустойчивость и распределенные алгоритмы сложны. Используйте работу других людей, где это возможно.

  • Используйте сложные настройки компилятора в наивной надежде, что вы обнаружите больше сбоев. Если вам повезет, вы можете обнаружить больше неудач. Скорее всего, вы будете использовать путь кода в компиляторе, который был менее протестирован, особенно если вы сами его свернули.

  • Используйте методы, которые не были протестированы в вашей среде. Большинству людей, пишущих программное обеспечение высокой доступности, приходится имитировать режимы сбоев, чтобы проверить правильность работы HA, и в результате пропустить множество режимов сбоев. Вы находитесь в «счастливом» положении с частыми неудачами по требованию. Поэтому протестируйте каждую технику и убедитесь, что ее приложение действительно улучшает MTBF на величину, превышающую сложность ее внедрения (со сложностью возникают ошибки). Особенно примените это к моим советам по алгоритмам кворума и т.д.

23
abligh

Поскольку вы специально запрашиваете программные решения и используете C++, почему бы не использовать перегрузку операторов для создания собственных, безопасных типов данных? Например:

Вместо использования uint32_tdouble, int64_t и т.д.) Создайте свой собственный SAFE_uint32_t, который содержит кратное (минимум 3) значение uint32_t. Перегрузите все операции, которые вы хотите (* + -/<< >> = ==! = И т.д.), И заставьте перегруженные операции выполнять независимо для каждого внутреннего значения, то есть не делайте этого один раз и копируйте результат. Как до, так и после убедитесь, что все внутренние значения совпадают. Если значения не совпадают, вы можете обновить неправильное значение до наиболее распространенного. Если нет наиболее распространенного значения, вы можете смело уведомлять об ошибке.

Таким образом, не имеет значения, произойдет ли повреждение в ALU, регистрах, ОЗУ или на шине, у вас все равно будет несколько попыток и очень хороший шанс на обнаружение ошибок. Однако обратите внимание, что это работает только для переменных, которые вы можете заменить - например, ваш указатель стека все еще будет восприимчивым.

Дополнительная история: я столкнулся с аналогичной проблемой, также на старой микросхеме ARM. Это оказался набор инструментов, который использовал старую версию GCC, которая вместе с конкретным чипом, который мы использовали, вызывала ошибку в некоторых случаях Edge, которая (иногда) приводила к повреждению значений, передаваемых в функции. Убедитесь, что у вашего устройства нет проблем, прежде чем обвинять его в радиоактивности, и да, иногда это ошибка компилятора =)

22
jkflying

Отказ от ответственности: я не профессионал радиоактивности, и не работал на такого рода приложения. Но я работал над мягкими ошибками и избыточностью для долгосрочного архивирования критических данных, которые несколько связаны (одна и та же проблема, разные цели).

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

Тогда возникает вопрос: как надежно вычислить, когда ваша память ненадежна?

Чтобы значительно снизить частоту мягких ошибок (за счет вычислительных затрат, поскольку это будут в основном программные решения), вы можете:

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

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

Если вы заинтересованы в устойчивых структурах данных (что является недавним, но интересным, новым направлением в алгоритмике и разработке избыточности), я советую вам прочитать следующие документы:

  • Интеллектуальные алгоритмы структуры данных intro от Джузеппе Ф.Итальяно, Университет Рома "Tor Vergata"

  • Christiano P., Demaine E.D. & Kishore S. (2011). Безотказные отказоустойчивые структуры данных с аддитивными издержками. В алгоритмах и структурах данных (стр. 243-254). Springer Berlin Heidelberg.

  • Ferraro-Petrillo, U., Grandoni, F. & Italiano, G.F. (2013). Структуры данных, устойчивые к сбоям памяти: экспериментальное изучение словарей. Журнал экспериментальной алгоритмики (JEA), 18, 1-6.

  • Итальяно, Г. Ф. (2010). Эластичные алгоритмы и структуры данных. В алгоритмах и сложности (стр. 13-24). Springer Berlin Heidelberg.

Если вам интересно узнать больше о области упругих структур данных, вы можете ознакомиться с работами Джузеппе Ф. Итальяно (и пройтись по ссылкам) и модель Faulty-RAM (введено в Finocchi et al. 2005; Finocchi и Italiano 2008).

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

Идея состоит в том, что вы просто делаете x раз одно и то же вычисление для каждого вычисления, которое вам нужно сделать, и сохраняете результат в x различных переменных (с x> = 3). Затем вы можете сравнить ваши переменные x:

  • если они все согласны, то нет никакой ошибки в вычислениях.
  • если они не согласны, то вы можете использовать большинство голосов, чтобы получить правильное значение, и, поскольку это означает, что вычисления были частично повреждены, вы также можете запустить сканирование состояния системы/программы, чтобы проверить, все ли в порядке.
  • если большинство голосов не может определить победителя (все значения x различны), то это идеальный сигнал для вас, чтобы запустить безотказную процедуру (перезагрузить компьютер, выдать предупреждение пользователю и т. д.).

Эта схема резервирования очень быстрая по сравнению с ECC (практически O(1)) и ​​предоставляет вам сигнал сброса, когда вам необходимо отказоустойчиво). Большинство голосов также (почти) гарантированно никогда не произведет искаженный вывод, а также восстановление после незначительных ошибок вычислений, потому что вероятность того, что вычисления x дадут тот же результат, бесконечно мала (потому что огромное количество возможных выходных данных, почти невозможно случайным образом получить 3 раза одинаковые, еще меньше шансов, если x> 3).

Таким образом, большинством голосов вы защищены от искаженного вывода, а с избыточностью x == 3 вы можете восстановить 1 ошибку (при x == 4 это будет 2 ошибки, которые можно исправить, и т.д. - точное уравнение nb_error_recoverable == (x-2), где x - это количество повторений расчетов, потому что для восстановления с использованием большинства голосов необходимо как минимум 2 согласованных вычисления).

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

Кроме того, если вы хотите убедиться, что вычисления выполняются правильно, если вы можете сделать свое собственное оборудование, вы можете создать свое устройство с x процессорами и подключить систему так, чтобы вычисления автоматически дублировались на x процессорах с большинством голосов. механически в конце (например, с использованием вентилей AND/OR). Это часто реализуется в самолетах и ​​критически важных устройствах (см. Тройная модульная избыточность ). Таким образом, у вас не будет никаких вычислительных накладных расходов (поскольку дополнительные вычисления будут выполняться параллельно), и у вас будет еще один уровень защиты от мягких ошибок (поскольку дублирование вычислений и большинство голосов будут управляться непосредственно аппаратным обеспечением, а не программное обеспечение, которое может быть легко повреждено, поскольку программа просто хранит биты в памяти ...).

16
gaborous

Вы хотите 3+ подчиненных машин с хозяином вне радиационной среды. Все операции ввода/вывода проходят через мастер, который содержит механизм голосования и/или повторных попыток. У ведомых устройств должен быть аппаратный сторожевой таймер, и вызов для их повышения должен быть окружен CRC или чем-то подобным, чтобы уменьшить вероятность непроизвольного столкновения. Удар должен контролироваться ведущим, поэтому потерянное соединение с главным равняется перезагрузке в течение нескольких секунд.

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

Редактировать: Из комментариев я чувствую необходимость прояснить «идею CRC». Вероятность того, что ведомое устройство ударит свой собственный сторожевой таймер, близка к нулю, если вы окружите удар с помощью CRC или дайджест-проверок случайных данных от мастера. Эти случайные данные отправляются только от ведущего, когда подчиненное устройство находится под контролем других. Случайные данные и CRC/дайджест немедленно очищаются после каждого удара. Частота удара ведущий-ведомый должна быть больше двойной тайм-аут сторожевого таймера. Данные, отправленные мастером, генерируются уникальным образом каждый раз.

9
Jonas Byström

Один момент никто, кажется, не упомянул. Вы говорите, что разрабатываете в GCC и кросс-компилируете в ARM. Откуда вы знаете, что у вас нет кода, который делает предположения о свободной оперативной памяти, целочисленном размере, размере указателя, сколько времени требуется для выполнения определенной операции, сколько времени система будет работать непрерывно или что-то в этом роде? Это очень распространенная проблема.

Ответом обычно является автоматическое юнит-тестирование. Напишите тестовые наборы, которые осуществляют код в системе разработки, а затем запустите те же тестовые наборы в целевой системе. Ищите различия!

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

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

8
Graham

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

7
ren

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

 Enter image description here

Там будет поверхность материала, которая не будет подвергаться воздействию радиации. Несколько передач будет там. Механический считыватель будет работать на всех передачах и будет гибким, чтобы двигаться вверх и вниз. Вниз означает, что это 0, а вверх означает, что это 1. Из 0 и 1 вы можете сгенерировать свою кодовую базу.

7
Hitul

Возможно, было бы полезно узнать, означает ли это, что аппаратное обеспечение «предназначено для этой среды». Как это исправить и/или указывает на наличие ошибок SEU?

В одном проекте, связанном с исследованием космоса, у нас был собственный MCU, который вызывал бы исключение/прерывание при ошибках SEU, но с некоторой задержкой, то есть некоторые циклы могут проходить/выполнять инструкции после одного insn, который вызвал исключение SEU.

Особенно уязвим был кэш данных, поэтому обработчик лишил законной силы строку кэша и перезапустил программу. Только то, что из-за неточного характера исключения последовательность insns во главе с повышением исключения может не быть перезапускаемой.

Мы определили опасные (не перезапускаемые) последовательности (например, lw $3, 0x0($2), за которым следует insn, который изменяет $2 и не зависит от данных от $3), и я внес изменения в GCC, поэтому такие последовательности не встречаются (например, в крайнем случае , разделяя два insns nop).

Просто кое-что рассмотреть ...

7
chill

Кто-то упомянул об использовании более медленных чипов, чтобы ионы не могли легко перевернуть биты. Аналогичным образом возможно использовать специализированный процессор/оперативную память, который на самом деле использует несколько битов для хранения одного бита. Таким образом, обеспечивая аппаратную отказоустойчивость, потому что очень маловероятно, что все биты будут перевернуты. Так что 1 = 1111, но нужно было бы попасть 4 раза, чтобы на самом деле перевернуться. (4 может быть плохим числом, так как если 2 бита перевернуты, это уже неоднозначно). Таким образом, если вы выберете 8, вы получите в 8 раз меньше оперативной памяти и на некоторое время меньше времени доступа, но гораздо более надежное представление данных. Возможно, вы могли бы сделать это как на уровне программного обеспечения с помощью специализированного компилятора (выделите на x больше места для всего), так и на языке реализации (напишите обертки для структур данных, которые размещают объекты таким образом). Или специализированное оборудование, которое имеет ту же логическую структуру, но делает это в прошивке.

7
Alex C

То, что вы спрашиваете, является довольно сложной темой - не так легко ответить. Другие ответы в порядке, но они охватывают лишь небольшую часть всего, что вам нужно сделать.

Как видно в комментариях , невозможно решить проблемы с оборудованием на 100%, однако возможно с высокой вероятностью уменьшить или поймать их, используя различные методы.

Если бы я был вами, я бы создал программное обеспечение самого высокого уровня Уровень целостности безопасности уровень (SIL-4). Получите документ IEC 61513 (для атомной промышленности) и следуйте ему.

7
BЈовић

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

Похожий на Watchdog концепт является таймерами крайних сроков. Запустите аппаратный таймер перед вызовом функции. Если функция не вернется до того, как таймер крайнего срока прервется, перезагрузите стек и повторите попытку. Если после 3/5 попыток это не помогло, необходимо перезагрузить ПЗУ.

Разделите ваше программное обеспечение на части и изолируйте эти части, чтобы использовать отдельные области памяти и время выполнения (особенно в среде управления). Пример: получение сигнала, предварительная обработка данных, основной алгоритм и реализация/передача результата. Это означает, что сбой в одной части не приведет к сбоям в остальной части программы. Поэтому, пока мы восстанавливаем получение сигнала, остальные задачи продолжаются на устаревших данных. 

Все нуждается в CRC. Если вы выполняете из RAM, даже ваш .text нуждается в CRC. Регулярно проверяйте CRC, если вы используете циклический планировщик. Некоторые компиляторы (не GCC) могут генерировать CRC для каждого раздела, а некоторые процессоры имеют выделенное оборудование для выполнения вычислений CRC, но я думаю, что это выходит за рамки вашего вопроса. Проверка CRC также предлагает контроллеру ECC в памяти исправить однобитовые ошибки, прежде чем это станет проблемой.

6
Gerhard

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

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

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

4
MrBigglesworth

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

3
Joshua

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

Что-то выходит из строя или работает неправильно, может быть результатом ваших собственных ошибок - тогда это должно быть легко исправить, когда вы обнаружите проблему. Но существует также возможность аппаратных сбоев - и это сложно, если не невозможно, исправить в целом.

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

Восстановление из такой ситуации ошибки - это либо перезагрузка (если программное обеспечение все еще живо и работает), либо аппаратный сброс (например, hw watchdogs). Проще начать с первого.

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

Также, если код относительно сложен - имеет смысл «разделить и завоевать» его - то есть вы удаляете/отключаете некоторые вызовы функций там, где вы подозреваете проблему - обычно отключая половину кода и включая другую половину - вы можете получить «работает»/решение типа «не работает», после которого вы можете сосредоточиться на другой половине кода. (Где проблема)

Если через какое-то время возникает проблема - тогда можно ожидать переполнения стека - тогда лучше отслеживать регистры точек стека - если они постоянно растут.

И если вам удастся полностью свести к минимуму свой код до тех пор, пока приложение типа «здравствуй, мир» - и оно все равно будет случайным образом терпеть неудачу - тогда возникнут проблемы с оборудованием - и потребуется «обновление оборудования» - что означает создание такого процессора/ram/... - комбинация аппаратных средств, которая лучше переносит излучение.

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

Если в вашей среде возможно также передавать сигнал и получать ответ - вы можете попытаться создать какую-то среду удаленной отладки в режиме онлайн, но тогда у вас должен быть хотя бы работающий коммуникационный носитель и какой-нибудь процессор/некоторое количество оперативной памяти в рабочем состоянии. И под удаленной отладкой я подразумеваю либо подход типа GDB/gdb-заглушки, либо вашу собственную реализацию того, что вам нужно вернуть из вашего приложения (например, загрузить файлы журналов, загрузить стек вызовов, загрузить оперативную память, перезапустить)

1
TarmoPikaro

Я действительно прочитал много хороших ответов!

Вот мои 2 цента: создайте статистическую модель аномалий памяти/регистра, написав программное обеспечение для проверки памяти или для частого сравнения регистров. Далее создайте эмулятор в стиле виртуальной машины, где вы сможете поэкспериментировать с этой проблемой. Я предполагаю, что если вы измените размер перехода, тактовую частоту, производителя, корпус и т.д., То наблюдаете другое поведение.

Даже память нашего настольного ПК имеет определенную частоту отказов, которая, однако, не мешает повседневной работе.

0
user9016329