Марсоход Sojourner и посадочный модуль Mars Pathfinder добрались до Красной планеты в 1997 году и быстро стали одной из самых известных миссий NASA. Поначалу всё шло гладко, но спустя несколько дней после посадки аппарат начал самопроизвольно и полностью перезагружаться: бортовой сторожевой таймер (watchdog) замечал зависание и принудительно перезапускал систему. Разбор этого случая давно превратился в хрестоматийный пример для программистов встраиваемых систем.

Марсоход Sojourner и посадочный модуль Mars Pathfinder на поверхности Марса

Причиной сбоев оказалась инверсия приоритетов (priority inversion). Так называют ситуацию, когда задача с низким приоритетом удерживает ресурс, нужный задаче с высоким приоритетом, и тем самым не даёт более важной задаче выполниться. Аппаратом управляла операционная система реального времени VxWorks, но подобное способно произойти в любой системе с приоритетным планированием.

реклама кормит Уточку 🦆

Сам по себе этот эффект не был ни новым, ни экзотическим: в теории о нём знали давно, однако здесь он проявился на виду у всех — прямо во время работы одной из самых заметных космических миссий десятилетия. Именно поэтому история Pathfinder так часто разбирается в учебниках.

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

Если упростить, на борту работали несколько задач, которые делили один и тот же мьютекс (mutex), чтобы не мешать друг другу при доступе к общей шине данных. Самый высокий приоритет имела задача распределения данных, самый низкий — обработка метеорологических измерений. Была и промежуточная задача со средним приоритетом, отвечавшая за обслуживание шины.

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

реклама кормит Уточку 🦆

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

Неприятнее всего, что похожие зависания инженеры замечали ещё на наземных испытаниях, но списывали их на случайные «глюки». Это распространённая и опасная ловушка: если непонятно, почему что-то произошло, нет и гарантии, что это не повторится — тем более за десятки миллионов километров от Земли, где каждая перезагрузка дорого обходится.

Как проблему решили

К счастью, VxWorks предусматривает такой сценарий, а на борту хватило возможностей, чтобы включить для проблемного мьютекса механизм наследования приоритета (priority inheritance). При нём низкоприоритетная задача временно получает повышение приоритета, если её ресурса ждёт более важная задача, — и успевает быстро завершиться и освободить мьютекс. Нужный параметр удалось изменить прямо на летящем аппарате, после чего перезагрузки прекратились.

реклама кормит Уточку 🦆

История Pathfinder показывает, почему в системах реального времени так важны, казалось бы, скучные детали работы планировщика. Те же грабли подстерегают не только космические аппараты, но и промышленные контроллеры, автомобильную электронику и бытовую технику. А главный урок универсален: редкий, но воспроизводимый сбой нельзя списывать на случайность — его нужно понять до конца.