За пределами границ.

Портал «МИР КОМПЬЮТЕРНОЙ АВТОМАТИЗАЦИИ»

МКА №1/2016
Герард Хольцман (Gerard I. Holzman), NASA/JPL


В любом компьютере все объекты, которые мы храним или модифицируем, с неизбежностью имеют пределы. Все ресурсы ограниченны. Стеки ограниченны, очереди ограниченны, ёмкость файловой системы ограниченна и даже числа ограниченны. Однако при написании кода об этом часто забывают.

На курсах по сертификации программного обеспечения, которые проводятся в Лаборатории реактивного движения (JPL), мы делаем акцент на том, что для создания надёжного кода необходимо тонко чувствовать границы системы. В любом компьютере для выполнения вычислений в вашем распоряжении есть лишь ограниченный кусочек памяти и конечный отрезок времени, поэтому все объекты, которые мы храним или модифицируем, с неизбежностью имеют пределы. Все ресурсы ограниченны. Стеки ограниченны, очереди ограниченны, ёмкость файловой системы ограниченна и даже числа, представьте себе, ограниченны. Поэтому мир компьютеров сильно отличается от мира математики, что при написании кода слишком многие не берут в расчёт. Дело, по-видимому, в том, что в большинстве случаев не так уж и важно, знаете вы об ограничениях вашего компьютера или нет, поскольку бóльшую часть времени всё будет работать правильно.

Есть замечательный фрагмент в серии комиксов Peanuts, где персонаж Пепперминт Пэтти настойчиво внушает своей однокласснице, что алгебра на самом деле не так уж и трудна: «x почти всегда равен одиннадцати, а y – почти всегда девять». В математике, если оба слагаемых x и y положительны, то ясно, что их сумма должна быть больше каждого из них. В компьютере не так. Если мы используем целые 32-битные числа со знаком, это свойство сохраняется лишь для сумм не выше где-то двух миллиардов. Поскольку это бóльшую часть времени так, можно с уверенностью сказать, что когда мы увеличиваем x на положительную величину, результат «почти всегда» будет больше первоначального значения.

Почти всегда

Предположим, что мы управляем 32-битным знаковым счётчиком (signed counter). Мы запускаем его с нулевого значения и инкрементируем раз в секунду. Тогда до того момента, когда он переполнится, пройдёт примерно 68 лет. Именно так хранилось количество секунд в оригинальной операционной системе Unix, где за нулевую отметку была принята дата 1 января 1970 года. Невооружённым глазом видно, что этот метод записи времени имеет ограничения. Такие 32-рязрядные часы в системе Unix могут работать бесперебойно лишь на отрезке времени порядка 136 лет: с декабря 1901 года (отрицательная шкала счёта секунд ранее января 1970 года) до января 2038 года. Если продолжать работу в Unix достаточно долго, то во вторник 19 января 2038 года произойдёт аналог проблемы 2000-го года (Y2K), когда окажется, что часы перескочили обратно на 13 декабря 1901 года.

Починить часы в Unix нетрудно. Если просто перевести счётчик времени на 64-битный формат, то для его переполнения потребуется порядка 293-х миллиардов лет. И хотя этот временной интервал небесконечен, он более чем достаточен, поскольку, по прикидкам учёных, ещё до того момента, как такой счётчик досчитает до отметки в районе пяти миллиардов лет, нашу солнечную систему ждёт огненный конец.

Но если мы начнём отсчитывать время с большей точностью, нежели посекундно, картина изменится. Если, к примеру, мы будем инкрементировать наш 32-битный счётчик каждые 100 мс, то знаковый счётчик (signed counter) переполнится примерно через 6,8 лет, а беззнаковый (unsigned counter) – через 13, 6 лет. Если увеличить точность до 10 мс, то знаковый счётчик переполнится через 248,6 дней, а беззнаковый – через 497,1 дней. Запомните эти цифры (см. таблицу), мы к ним ещё вернёмся.

Как точность отсчёта времени влияет на переполнение счётчика

Если инкрементировать
32-битный счётчик каждые

Знаковый счётчик переполнится через

Беззнаковый счётчик переполнится через

1 с

68, 1 лет

132,2 лет

100 мс

6,8 лет

13,6 лет

10 мс

248, 6 дней = 0,68 лет

497,1 дней = 1, 36 лет


Время на борту космических аппаратов

Космический аппарат НАСА «Дип импакт» (Deep Impact) был запущен в январе 2005 года. Его встроенный контроллер вычислял время в виде числовой последовательности 100-миллисекундых интервалов, которые отсчитывались с 1 января 2000 года. Если прибавить 13,6 лет, то получим некую дату августа 2013 года, которая находится далеко за пределами запланированного срока работы миссии. Назначение этого космического аппарата – сбросить зонд-импактор на комету Темпеля 1 и исследовать состав образовавшегося пылевого облака. Миссия завершилась 4 июля 2004 года.

Часто бывает так, что космические аппараты после успешного выполнения основной задачи выполняют дополнительные задания, при условии, что у них ещё остались в достаточном количестве необходимые ресурсы. В ноябре 2010 года во время первой дополнительной миссии «Дип импакт» пролетел на близком расстоянии от кометы Хартли-2 (Hartley 2). После этого у него оставалось ещё достаточно топлива для другой дополнительной миссии – сближения с астероидом 2002 GT. Если бы всё пошло хорошо, то данный космический аппарат под новым именем EPOXI достиг бы астероид в 2020 году.

Но этому не суждено было сбыться. Бортовой хронометражный счётчик переполнился 13 августа 2013 года, что инициировало обработку исключительной ситуации. Это исключение привело к сбросу бортовой аппаратуры в исходное состояние, в результате чего она должна была перейти в режим безопасной работы. В реальности этот сброс обнулил всю память за исключением бортовых часов, что привело к зацикливанию аппарата на сбросе. Даже в этом отчаянном режиме наземные контроллеры могут «реанимировать» космическую миссию, подавая «аппаратные команды», минуя главный процессор. Однако такое вмешательство нужно делать быстро, до того, как космический аппарат «заблудится» и более не сможет получать команды с Земли. Такая помощь не поспела вовремя, и 20 сентября 2013 космический аппарат был объявлен потерянным. Его убил 32-разрядный счётчик.

Это, увы, не единственный пример числового переполнения, приводящего к проблемам даже в системах с особыми требованиями к обеспечению безопасности (safety-critical systems).

5e9ca63468dd0e9b31e715fc8249fb50.jpg

Самолёты

Пассажирский самолёт Боинг 787 (Дримлайнер) состоит из множества подсистем, где есть встроенные контроллеры. В них, как правило, предусмотрены внутренние часы и отсчёт времени. Контроллеры, встроенные в блоки управления генераторами (generator control units – GCUs) отсчитывают время каждые 10 мс. Соответствующие счётчики содержат знаковые 32-битные числа. Теперь перед тем, как продолжить чтение, гляньте ещё раз в таблицу.

9 июля 2015 года Федеральное управление гражданской авиации США выпустило директиву лётной годности (AD), предписывающую всем авиакомпаниям перезагружать блоки управления генераторами на всех Боингах 787 как минимум каждые 248 дней [1]. Директива гласит: «Цель AD – предотвратить полную потерю подачи переменного напряжения, в результате чего возможна потеря управляемости самолёта». Далее объясняется, что «Эта AD появилась после того, как было обнаружено, что самолёт модели 787, в системы которого электроэнергия подаётся непрерывно в течение 248 дней, может оказаться обесточенным из-за одновременного перехода блоков управления генераторами в отказоустойчивый режим работы. Причиной этого состояния стали программные счётчики в составе ПО генераторов…, которые каждые 248 дней непрерывной генерации электроэнергии переходят в режим переполнения».

Откуда берутся эти 248 дней, мы уже знаем, однако, по-прежнему, важно видеть, как часто программная ошибка такого типа случается на практике, причём даже в системах, которые прошли весьма жёсткий процесс сертификации. Программное обеспечение для гражданских самолётов всегда разрабатывается и тестируется с большой тщательностью. Процесс разработки должен соответствовать процедурам, оговоренным в стандарте DO-178B (и его последователю DO-178C). Нет никакого сомнения, что программное обеспечение для Боинга 787 не писалось в спешке. Первый Боинг 748 был показан публике 8 июля 2007 года, а первый полёт состоялся 15 декабря 2009 года. Первый коммерческий полёт был осуществлён два года спустя. Можно предположить, что к тому моменту самолёт и управляющее им ПО были под пристальным вниманием разработчиков в течение весомого количества лет. Когда в начале 2015 года была обнаружена проблема переполнения, в эксплуатации было почти 300 самолётов.

Ресурсные ограничения

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

LightSail – «Солнечный парус»

20 мая 2015 года «Планетарное общество» (Planetary Society) запустило в тестовый полёт небольшой космический аппарат. Программа эксперимента, названного LightSail («Солнечный парус»), заключалась в испытаниях большого солнечного паруса. Всего через два дня испытательный полёт столкнулся с неожиданной проблемой. В новостной ленте «Mission Update» от 20 мая проблема описывалась так: «В настоящее время LightSail, вероятно, замер, наподобие того, как перестаёт реагировать настольный компьютер»[2].

Причина была относительно проста. Бортовой файл регистрации хранил записи всех телеметрических данных, передаваемых аппаратом; когда файл превысил объём 32 Мбайт, произошёл фатальный сбой системы. Почему 32 Мбайт? В пресс-релизах это не объяснялось, но эта цифра соответствует пределу старой файловой системы FAT 12, которая ещё используется в старых встроенных системах. В идеологии FAT12 для хранения в файловой системе последовательности 512-байтных блоков используется 16-битная адресация, и да, 216× 512 байт выходит за пределы 32 Мбайт.

Curiosity – марсоход «Любопытство»

Другой пример мне гораздо ближе. Космическая программа НАСА «Марсианская научная лаборатория» (Mars Science Laboratory) была разработана и воплощена в той подразделении, где я работал. В рамках этой программы 6 августа 2012 года был успешно доставлен на поверхность Марса большой марсоход Curiosity («Кьюриосити» – «Любопытство»). С тех пор марсоход надёжно функционирует, однако за это время в его программном обеспечении случилось несколько сбоев.

Один из них произошёл примерно через полгода после начала работы на поверхности Марса, 27 февраля 2013 года. Неожиданно сбойнул банк флэш-памяти. Программное обеспечение марсохода использует флэш-память для поддержания файловой системы, где хранятся временные файлы с телеметрическими данными и фотоснимками поверхности Марса перед их передачей на Землю. Файловая система во флэш-памяти запрограммирована на режим «только для чтения», на случай, если произойдёт что-то неожиданное. Итак, когда произошёл сбой банка памяти, это и была та самая неожиданность.

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

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

Можно также спросить, что происходит, когда заполняется флэш-память? Это событие случалось бы ещё реже, поскольку от менеджеров миссии требуется постоянно поддерживать достаточный запас свободной флэш-памяти на протяжении всей миссии. Однако, если она всё же заполняется, то программное обеспечение пишется таким образом, чтобы стирать низкоприоритеные файлы, чтобы освободить необходимое количество флэш-памяти. Высокопроиоритетные файлы, которые хранятся во флэш-памяти, в конце концов будут переданы на Землю, и пространство, которое они занимали, вновь будет свободным.

Все эти соображения выглядели достаточно продуманными и основательными, чтобы успешно пройти все экспертизы проекта, анализ кода и тестирование блоков программы, работающих с учётом известных ресурсных лимитов. Однако один тестовый кейс (контрольный пример) не был испытан - именно то, который случился на Марсе в феврале 2013 года. Когда флэш-память вследствие аппаратной ошибки находится в режиме «только для чтения», никаких дальнейших изменений сделать нельзя: невозможно что-то добавить или стереть. Теперь ОЗУ медленно заполняется новыми данными, ожидая, когда они перепишутся во флэш-память. В какой-то момент ОЗУ больше не может вмещать новую информацию. Соответствующие задачи одна за другой приостанавливают свою работу, и в итоге марсоход становится неактивен.

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

Следует ли вам беспокоиться? Программисты, конечно же, знают, что все ресурсы, с которыми они работают, ограниченны. Но трудно всё время помнить об этом отрезвляющем факте. Учитывая частоту, с которой эти проблемы возникают, следует их прорабатывать. Проводите тщательно спланированное тестирование системных блоков и всей системы, при котором достигаются известные системные пределы и делается попытка их превзойти. При использовании 32-битных часов, протестируйте их на перспективу за пределы 248 дней, 1,3 и 13,6 лет, и посмотрите, работает ли ваша система по-прежнему корректно. Если вы забыли об этих тестах, то у вас, конечно, много времени, чтобы об этом подумать, но спать вы будете наверняка лучше, если всё же не забудете.

Ссылки

  1. «Airworthiness Directives; the Boing Company Airplanes», Federal Register, vol. 80, no. 84,2015; http://rgl.faa.gov/Regulatory_and_Guidance_Library/rgad.nsf/0/584c7ee3b270fa3086257e38004d0f3e/$FILE/2015-09-07.pdf

  2. M.Wall, «LightSail Solar Sail Test Flight Stalled by Software Glitch», Space.com, 27 May 2015; www.space.com/29502-lightsail-solar-sail-software-glitch.html

___________________________________________________________________________________

Источник: IEEE Software, November/December 2015, p. 24, Out of Bounds