Pull to refresh

Персистентная оперативная память

Reading time 5 min
Views 27K
Сегодня — рассказ про одну из ключевых концепций ОС Фантом. Впрочем, сама концепция, конечно, существовала и до Фантома — фактически, у Танненбаума в конце книги, там, где он позволяет себе фантазировать, просматриваются очертания почти всех особенностей Фантома, так что, в целом, подход довольно очевиден для тех, кто хотя бы задумывается о будущем систем вообще.

Персистентная оперативка — очень простая и очень непростая вещь.

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

Ну, действительно — неважно: если мы смогли спасти состояние компьютера перед его отключением, то можно восстановить это состояние в другом. Таком же. Вообще таком же? Прямо до микросхемы? А если в нём видеокарта другая — уже нельзя?

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

А задача — именно такова. Обеспечить программе среду, в которой остановка ОС и остановка компьютера для программы выглядели исключительно как нажатие на кнопку «пауза» при просмотре фильма. Во время паузы «под программой» можно даже компьютер поменять, но надо как-то обеспечить ситуацию, в которой продолжение работы для программы будет совершенно прозрачным.

Это недостижимо, если требование доводить до абсолюта. Состояние хардвера сохранить и полностью восстановить нельзя. Но и не надо. Программе не нужна видеокарта, ей нужен тот же API и сохранённая картинка на экране, а это — можно.

Что вырисовывается: сохранить состояние только оперативки — мало, а всего компьютера — нереально.

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

Легко видеть, что такой средой является спецификация операционной системы. Ну — не любой. Хорошей операционной системы. Системы, которая достаточно качественно скрывает от приложения детали аппаратуры (и вообще бренности окружающего мира).

Следовательно, вообще-то, говорит надо не о персистентной (виртуальной) памяти, а о персистентной среде исполнения = персистентной ОС. Собственно, это и есть проект ОС Фантом, но сегодня я принципиально ограничусь, всё же, вопросом только оперативки, иначе статью не получится дописать к утру.

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

Ясна так же и цель войны. Ясна? Нет?

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

Ну — вот простой пример: habrahabr.ru/company/2gis/blog/198564

Вместо того, чтобы воевать с ФС, можно просто забыть про неё как про страшный сон. Очевидно, что это вызывает массу вопросов про обмен данными, но, вообще-то, признаемся себе, что обмен тоже давно уже происходит не через файлы, а через REST/SOAP/whatever, и тоже не в формате сериализации дерева в байт-стрим, а в виде запросов объектов или «ветвей» дерева объектов. Что, очевидно, соответствует вовсе не «файловой» семантике, а тому представлению данных, которое сподручно для самой программы.

Хорошо, цель ясна. Теперь средство. Как же записать память на диск?

Ну, write( fd, (void *)0, get_mem_size() ), верно?

Конечно, нет. По многим причинам. И потому, что в памяти могут быть «дырки» — не отображённые никуда области, доступ к которым запрещён. И потому, что совсем незачем записывать то, что не менялось. Да тот же код — зачем? Да и, главное, потому, что это дико дорого.

И уж совсем важно, что программу во время записи придётся остановить. А вот это уже никуда не гоже вообще.

Дело тут вот в чём. Если мы хотим (а мы хотим), чтобы прикладная программа нам доверяла и правда не пыталась для спокойствия всё-таки записаться в файл (а это убивает всю идею вообще), надо ей гарантировать, нет ГАРАНТИРОВАТЬ, что мы её обязательно сохраним. Может быть, не совсем в последнем состоянии (это не так важно — она повторит часть вычислений), но в целостном и не позавчерашнем.

То есть — состояние программы (чуть позже мы выясним, что это состояние всех программ вообще) нельзя сохранять по старинке — перед выключением компьютера или по ctrl-s, это надо делать постоянно. Или, как минимум, достаточно регулярно. И нередко.

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

Надо научиться сохранять состояние в фоновом режиме, потихоньку, прямо «под» работающей программой.

Но тогда оно будет неконсистентным! Мы сохранили левую половину состояния программы, а когда начали сохранять правую, программа уже наработала что-то новенькое — половины «не срастутся» при рестарте. Программа добавит в левую половину объект, поставит на него ссылку из правой половины, а в «фотографии» состояния окажется, что ссылка есть, а объекта — нет.

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

Можно?

Можно.

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

Если утрировать до предела, то фотографирование состояния сводится к мгновенному переключению всей памяти в read only — это единственная (и крайне недорогая) операция, которую нужно сделать атомарно. (Заранее оговоримся, что перед ней нужно загнать программы в состояние, которое полностью представлено в памяти. А не в регистрах процессора. Что тоже несложно и недорого).

Это, собственно, «точка фотографирования».

Дальше процесс обретает известную медитативность и может быть выполнен без вырывания волос из естественных впадин организма, спокойно и с расстановкой. Страницы памяти можно спокойно записывать на диск одна за одной. А что ж программа? Она, конечно, не будет ждать и попробует в какую-то страницу памяти таки записать что-то. Такая страница разделяется на пару — старая, неизменная копия встаёт в очередь на запись на диск (поближе к началу), а новая используется и модифицируется программой. Когда запись на диск закончится, старые копии будут уничтожены.

Как я уже говорил, это только черновик. Есть масса деталей. (Желающие взорвать мозг могут заглянуть в код прямо сейчас.)

Очевидная оптимизация — записывать только diff-ы. С прошлого раза поменялось очень не всё.

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

Понятно так же, что рабочее множество программы (точнее, его не read only часть) потенциально удвоится за время записи снапшота: если нам не повезёт, то пока мы будем записывать состояние, программа «перетрогает», перезапишет все свои странички и всем им придётся выделить вторую физическую страницу оперативной памяти. То есть при невезении затраты оперативки удвоятся во время снапшота. Реально ситуация всегда мягче, но «спрос» на оперативку во время снапшота неизбежно возрастает. Чтобы его удовлетворить, не останавливая программы по причине нехватки оперативки, хорошо бы перед снапшотом немного подготовиться, позаписывать на диск странички, которые изменились — тогда у них можно будет «отобрать» физическую страницу памяти, если она срочно потребуется в другом месте. Опять же, внимательный читатель увидит, что выше уже сказано про позаписывать на диск превентивно, но в других целях.

Превентивная запись улучшает и другой показатель. Латентность снапшота — время между «фотографированием» и окончанием записи фотографии на диск.

Отмечу, что всё это на сегодня вполне реализовано и работает в ядре ОС.

В этом месте поставим точку с запятой. Через некоторое время я постараюсь написать статью, которая связывает предыдущую тему — примитивы синхронизации — и эту. То есть написать о проблемах реализации примитивов синхронизации в персистентной памяти.

И это уже будет статья о том, что до сих пор как следует не сделано. То есть, в существенной степени она будет состоять из вопросов.
Tags:
Hubs:
+33
Comments 68
Comments Comments 68

Articles