Стенограмма доклада про Фантом-ОС, сделанного Дмитрием Завалишиным на ADD-2010

:

: 26

Аннотация


Дмитрий Завалишин рассказал о текущем состоянии в разработке своего любимого детища — оригинальной операционной системы PhantomOS, близкой по концепции Microsoft Singularity, но при этом open-source (опубликована большая часть исходных кодов этой операционной системы).

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

Видео


Видео в HD-качестве, смотрите в полноэкранном режиме.

Скачать видео

Подкаст


Ссылка на подкаст.

Стенограмма

Стенограмму по видеозаписи записал Стас Фомин.

Как сделать так, чтобы реально было хорошо? В том смысле, что все существующее на сегодня программное обеспечение делается по принципу «Давайте возьмем то, что было, ну и как-то это все подточим, улучшим, подвинем здесь, подправим тут», и в итоге, все что у нас есть на сегодня, это legacy-legacy-legacy, какое-то старье, которое развивалось, развивалось, развивалось, и в итоге, оно все такими наслоениями, и очень сложная и тяжелая…

Я вот пользуюсь компьютерами двадцать пять лет, вы знаете, они с тех пор, в тех с которых я начинал, в них было две дискетки по 160Кб, и 48Кб оперативной памяти. Так вот, вы знаете, оно загружалось быстрее, чем то, что у меня сейчас есть, а функционально я примерно тоже самое с ней и делаю, я программы пишу на ней, редактирую какие-то тексты, и электронную почту читаю. А при этом процессор и память изменились, господи боже… я даже подсчитать-то не могу… Пять порядков, шесть порядков — куда все это проваливается? Это проваливается ровно туда, весь софт, который сегодня сделан, он сделан по принципу исторического развития, чего-то такого старого, страшного и несчастного.

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

Есть некоторое количество мыслей, которые лежали в основе этой самой идеи, лежали такие, например, задачи. Я рассматривал современное программное обеспечение, которое все мы с вами разрабатываем.

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

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

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

Появились библиотеки. Потом эти библиотеки превратились в ядро, которое уже запускает прикладную программу. Итак все постепенно, постепенно все и стало нарастать, в конце концов появился Юникс. Юникс, на сегодня — операционка, которая явно победила, даже Винды сделаны по образу и подобию Юникса, концепт, на котором он основан, явно является доминирующим.

Интересно, что когда Юникс появлялся, а я достаточно старый, чтобы помнить эти времена, Юникс, как операционная система, был очень странный, и сильно уступал всем существующим операционкам. Была там машина, на которой мы работали, которая называлается PDP-11 (СМ-1600), на ней была родная операционка, и был Юникс. Родная операционка работала в разы быстрей Юникса. И тем не менее, она сегодня сдохла, а Юникс существует!

Почему? Потому что Юникс совершил очень правильный шаг. Люди которые его делали, они породили очень простую идею, таких вот маленьких программ, которые обрабатывают текстовые файлы фиксированным способом, и возможности собирать все эти программы на ходу, через пайпы, или через файлы, неважно, в какие-то цепочки.

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

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

В принципе, в основе Фантома лежала довольно банальная мысль, которая начала зарождаться в районе девяностых годов. Когда появился язык C++, который появился в 1987 что-ли году, все зафанатели с объектного программирования, и возникла мысль, типа «Ну а что? Язык-то объектный, почему операционка то планарная? Надо бы и ее сделать объектной». Причем, это вообще говоря, довольно такая практичная мысль, которая не из каких-то метаний или идеализма проистекает, она проистекает из того, что реально иметь объектный интерфейс это совсем удобно. Если посмотреть на сегодняшнюю ситуацию, это очевидно так. Тот же самый C#, та же самая Ява, вполне себе оборачивают операционные системы в объектные обертки, это удобно.

Хорошо. Следующий шаг. Довольно понятно, что мы хотим, чтобы программы коммуницировали. Причем вот интересно, что … вот что я забыл сказать на этом разговоре про яву и C#, про то, что в C#, ну и вообще в виндах есть OLE. Это реально… при всей, извините, ублюдочности этого инструмента, лучше пока к сожалению нет. И этот инструмент реально решает совершенно осмысленную задачу.

Задача эта — построение компонентной среды, которую можно реально клепать из той пачки объектов. То есть получать из разных сторон, собранных, скомпилированных модулей, и делать из них что-то единое, как-то собирать работающую систему. Причем надо сказать, что это тоже не абстракция, это тоже конкретная ситуация, в области, в которой мы работаем, системы промышленного управления и мониторинга, там драйвера для систем, которые продаются разными-разными-разными вендорами, они все сделаны как COM-объекты. COM — совершенно победившая технология, им это выгодно, им это нужно.

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

Здорово, да?

Я как-то вот, в один из первых разов, когда я рассказывал про Фантом, там был большой зал, там было человек двести программистов — «Кто знает, что такое OLE?». И рук тридцать поднялось. Я спрашиваю — «А сколько на нем программируют? Реально сделали OLEшный инструмент какой-нибудь?». И такие вот, неуверенные три человека вот, причем видно, что два из них компильнули «Hello World», примерно, а третий что-то реально прорубил и сделал [1].

Почему? — Очень тяжело. Очень-очень-очень тяжело.

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

Причем — в чем еще беда? Нормальное, естественное представление своих потрохов для программы, это граф объектов. То есть программа обычно работает с графом связных объектов, а единственный способ, которые реально работает на interprocess communication, это pipe. То есть дырочка, в которую можно байтик просунуть. То есть граф документов не пролезает никак, его можно сериализовать, но в этом случает, он оторвется от того, что у вас было, то есть его можно будет только скопировать в ту сторону. А дать куда-то реально, такой вот инструмент, чтобы можно было поработать вместе над общими данными, … ну OLE это как-то позволяет, но опять очень сложно. Но при этом, достаточно очевидно, что это ценная возможность. Почему это сложно? Потому, что много лет назад, какой-то умный человек, сказал, что операционная система, это ядро, которое запускает процессы. Процессы эти работают в отдельных адресных пространствах. И с тех пор, за ним, эту гениальную мысль постоянно повторяют. В то время, как на самом деле для managed-языков, которыми и Ява и C# являются, и вообще, все, что сейчас делается, это managed-языки, … вообще говоря, отдельные адресные пространства не нужны, потому что сами по себе managed языки — они хорошо управляют памятью, у них нет сишных проблем с убежавшим указателем, с порчей чужих данных.

Поэтому если перейти, сделать шаг к managed языкам, можно отказаться, от адресных пространств на уровне операционной системы. А дальше мысль пошла таким образом — ну хорошо, мы с вами взяли, написали, … (смотрит на экран) это Фантом там работает …

Грузится?
Нет, он уже загрузился, это он трудится.

Мы с вами взяли две программы, запустили их, и они — подружились. Одна другой какой-то поинтер послала, поскольку у нас общее адресное пространство, очень просто коммуницировать. Поинтер кинул, поменялся, за него можно дернуть, что-то забрать, что-то передать, чужие данные становятся как свои, буквально. Очень дешевые IPC. Только одна беда, вот вы вот эту программу остановили, и Pointer куда смотрит? Вроде как в никуда. Перезапустили, и даже если у вас pointer смотрит на старые данные, эта программа у вас снова запущена, данные в другом месте, связь прервалась. Я вот каждый раз, когда это рассказываю, привожу один пример. Вы когда запускаете Фотошоп, все запускали Фотошоп, там такое окошечко вылезает, начинает там бежать такая строчка — вот он вот это грузит, потом вот сюда потянулся, потом здесь что-то нашел такое, … и он делает это каждый раз.

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

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

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

Недавно пришел в голову пример: есть такие программы, которыми пользуются музыканты, называются секвенсоры. Вот все эти программы работают с двумя типами файлов — один тип свой, личный, в котором они хранят свое состояние полностью, это обычно некоторый proprietary формат, который обычно никому не известен. А второй — это какой-нибудь там «.midi». Файл, в который можно что-то записать, чтобы потом куда-нибудь можно было передать, в другую программу — это exchange format. Так вот, первого формата, в этом случае можно вообще не делать. Программа запустилась, построила свое состояние в памяти. Машинку выключили, при выключении машинка, как какбы это делается в Hibernate все это сохранила, вы включили — програмки все очнулись, как будто ничего не происходило. То есть они спокойно могут оставаться в этом состоянии, вы свою работу не потеряете, вы можете эту программу как угодно связывать, связность эта получается очень дешевой и эффективной.

Еще некоторые смешные вещи вылезают. Ну например, жизнь всех программ в одном адресном пространстве означает, что тут довольно эффективный ввод-вывод, потому что нет переключения контекстов, как это происходит сейчас, в ядре операционной системы. Из-за того, что контексты переключаются, и память программы может быть paged out, в нее нельзя делать напрямую нельзя делать напрямую ввод-вывод, его можно делать в буфер отдельно. Ну короче, есть некоторые заморочки. В этом случае, можно этого избежать. Почему этого нельзя было сделать давно? Потому, что для того, чтобы делать такие вещи, нужно иметь машины с очень большим виртуальным адресным пространством, в 32 битах Фантом не имеет большого смысла, то есть имеет, но понятно, что 32 бита, это пространство, в которое должен вписываться весь диск, а это не очень много, это всего-лишь четыре гигабайта, а дальше не влезает. Поэтому собственно, по большому счету, Фантом, как идея, ориентирован на машины 64 битные, а если 32 бита, то это видимо, телефон, или что-то простенькое, небольшое.

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

Первый путь, это нативные правила. У системы Фантом существует свой собственный байт-код, интерпретатор, который строит обьектную модель, аналогично явской и C#, под нее есть компилятор собственного языка, и в нее пишутся трансляторы из байт-кода Явы и C#. То есть теория гласит, что там Явский и C#-ный код, а также весь код, которыый написан на языках, которые компилируются в JVM и CLR, можно будет в Фантом затащить. Причем среда для него будет очень естественная, натуральная и весь этот код может взаимодействовать дешевыми способами через вот такую вот схему обмена поинтерами.

Вторая среда, которая изначально не планировалась, однако походу возникла мысль, что наверное нужно, это среда, такой POSIX-compatible, типа UNIX внутри Фантома, который мы планируем сделать в двух вариациях, одна вариация простая и элементарная. Просто запуск юниксовых приложений, скомпилированных под Фантом. То есть POSIX-среда, обычная-обычная POSIX-среда.

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

Ну и естественно, какой-то interchange между ними, который планируется сделать тоже очень интересным способом. Понятно, что современная операционная система не может не быть микроядерной, естественно, что втаскивать все в ядро может быть несерьезно, понятно, что микроядерность это в первую очередь средство коммуникации между компонентами, в том числе, ядром, драйверами и приложениями. Для этого существуют механизмы ядерного message passing'a, и выяснилось, я поисследовал этот вопрос… Вот если кто знает, была такая система операционная, называлась она BeOS. Довольно давно, французы, ушедшие из компании Apple, сделали платформу, такую вот довольно интересную, сделали для нее операционную систему, она не очень пошла, хотя где-то применялась, компания развалилась, а операционка перешла в open-source, и ее с тех пор пару раз реализовали, и сейчас она существуюет под именем Haiku.

Вот в ней, довольно такой приличный механизм, message passing'a существует, и мы собственно посмотрели на его и в Фантом его перетащили. То есть он рассматривается, как инструмент для расширения ядра компонентами, во-вторых, для взаимодействия между Си-шным кодом, и объектным кодом, потому что понятно, что message passing очень хорошо втыкается с любой стороны.

Вот. Я тут ничего не вижу, что там на экране, думаю, что вы тоже ничего не видите [2]

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

Но некоторые вещи делаются существенно дольше, чем должны.

Собственно, как работает ядро Фантом. Оно, в отличие от… По-простому, если вы знаете, что такое paging в Unixе, то Фантом, это операционная система, у которой консистентный пейджинг, то есть pagefile, в который сохраняется состояние виртуального адресного пространства, он при рестарте не теряется, а правильным образом, структуризуется, и при рестарте система поднимает из него все свое состояние. То есть когда она просто работает, она ничего существенно не записывает, кроме обычного пейджинга. Когда она завершается, делается снепшот, который фиксирует состояние системы, полноценное, с него же она потом и стартует, практически полностью восстанавливает свое состояние.

Тут есть два момента. Собственно очень часто спрашивают, чем это отличается от Hibernate, это отличается от hibernate тем, что hibernate нужно явно сделать, иначе вы все потеряли, а фантомовская структура памяти, она так устроена, что он время от времени сам делает снепшоты, прозрачно для вас, и достаточно безболезненно. И в принципе, если вы его просто выключите из розетки, то он после этого, при включении, снова поднимется со старым состоянием, но с каким-то отставанием, на сколько-там минут назад, секунд… зависит от многих вещей. Так вот, время snapshotа у Фантома, оно во-первых сильно отличается для первого запуска системы, и для последующих.

Если вы вообще систему, только-только, с нуля проинсталлировали, дальше ей нужно очень многое положить в snapshot, потому что у нее все новое. Если система проработала… хотя бы один snapshot сделала, то после этого у нее модификаций не очень много, и следующие snapshotы происходят очень быстро. И если убрать все наши проверки и assertы, то это наверное секунд пять происходит. Но текущее ядро, которое я вам показываю, в нем все эти проверки есть, во вторых, оно сейчас запущено так, как будто оно только что родилось, после инсталляции, соответственно снепшот занимает довольно долгое время, потому что после того, как сделан снепшот, мы его еще проверяем на консистентность, чтобы убедится в том, что система снепшотов… о умолкла, отлично … у нас будет честный эксперимент, виртуальная машина, в которой работает Фантом, почему-то решила сломаться.

Соответственно, будем считать, что его выдернули из розетки совершенно неожиданно. Сейчас она доломается, Виндовс очень задумчивая штука, она почему-то не хочет сразу это убить, вот. В чем еще одна ценность такой структуры? Дело в том, что Фантом очень быстро стартует, … что такое поднятие снепшота? Это означает, что загрузилось ядро, а ядро очень маленькое, после чего ядро нашло карту расположения памяти на диске, подняло ее в память и запустилось дальше. Все остальные обращения к диску происходят в рамках обычных pagefaults, которые вызваны обращениями к памяти, которая еще не загрузилась. Почему это важно? Потому, что современные компьютеры постоянно мигрируют мз десктопного вида ноутбуков, в какие-то встроенные приложения. В моем телевизоре есть Linux, например. Причем этот Линукс, почему-то, стартует восемь секунд, меня это жутко совершенно раздражает.

В автомобилях современных применяются компьютеры, и даже если это Линукс… если Винды, то это вообще конечно беда, очень долго поднимается. Даже если это Линукс, даже если он очень компактный, он все равно довольно долго запускается. Реально, потребность такая есть. Нужна система, которая, во-первых, быстро запускается, и которая не предъявляет претензий в плане shutdown-а. Вышел из машины, ключи так чик — зажигание выключил, положил в карман, ушел. Систему убили. Включили — система должна заработать там же, где была, потому что вы что-то с ней делали, карту там смотрели, что-то такое, какие-то операции происходили. Разумно, чтобы каждая операция была в том же состоянии, в котором вы ее и оставили. Если говорить там про какие-то серверные системы, для медицины например, вот мой любимый пример — системы искусственного дыхания — прошла уборщица, дернула за вилочку, включила обратно, а он говорит — «а я сейчас диски почекаю…, что-то там загружу, что-то еще поделаю». Человек глядишь, там уже и не дождался. Соответственно в эту область мы еще как-то стараемся попасть, я считаю, что это довольно хорошая задача для нас.

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

Какая виртуальная машина?
Это TPL. Понятно, это белое окно, это собственно Фантомовское приложение, оно очень простое, там три строки у меня, там примерно
i = i + 1
print i
Но смысл в том, что по его состоянию видно, насколько … какое состояние было поднято системой. После перезагрузок видно, что она свое состояние сохраняет, и восстанавливает, … Какая? А виртуальная машина — я ответил.

Вот. В каком состоянии проект примерно? Мы этим занимаемся около двух лет, ну я не считаю, там годы раздумий, и метаний, в течении которых писались куски кода экспериментальные, которые сегодня уже все либо переписаны, либо по другой причине выкинуты из проекта. Около двух лет. Примерно год заняла реализация proof of concept, в которой я очень активно использовал внешний код, ну грубо говоря, собрал из интернета все, что подходит из стандартного колла, типа управления процессором, managementa памяти, то, что не является специфичным для Фантома, все было использовано стандартное. И поверх этого была написана операционная система, которая реализует основные идеи.

Когда мне стало понятно, что она написана, существует, работает, снепшоты отрабатывают и восстанавливаются, была поставлена задача очистить все этот от кода, который лицензии нашей не соответствует. На сегодня задача была выйти в LGPL, и примерно полгода потребовалось на то, чтобы весь чужой, GPLный и какой-то еще код со странными лицензиями, переписать, заменить на свой. И на сегодня, ядро, кроме стека TCP/IP, который взят целиком, из другой системы под нормальной лицензией, все остальные системы написаны с нуля. Сам стек TCP/IP доведен до какого-то внятного уровня, вот есть там графические драйверы, есть базовые драйверы для стандартных, наиболее популярных десять лет назад сетевых карт, типа там NE2000, или известные RTLки.

Тесты на снепшоты, разработка ядра, вообще-говоря, это все надо постоянно тестировать, мы сделали test-suite, который практически каждый build прогоняется, на предмет регресса.

Сделан компилятор собственного языка, по совпадению, тоже называется Фантом, и сейчас в работе такие вещи. Как-то долизывается ядро, хотя в целом, оно по большей части готово, и идет работа над транслятором из байт-кода Java в Фантом. Меня часто спрашивают, почему у Фантома свой байт-код, почему не взяли явский, есть масса причин, одна из них заключается в том, что фантомовский байт-код, как бы это правильно сказать, по-научному… но в Яве существуют нативные типы типа int, float, такие вот, контентные как бы типы, а в Фантоме их нет, там все объекты, совсем-совсем-совсем.

В принципе, можно от int-а наследоваться, и сделать… развить интерфейсы inta, хотя и непонятно зачем, но можно. Это было сделано в частности, в силу того, что под виртуальной машиной Фантома хотелось абсолютной надежности. Почему? Потому что Явская виртуальная машина, она по большому счету … явская, сишарпная, непринципиально, она работает в отдельном процессе, и это личная проблема этого отдельного процесса.

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

К примеру, обычная виртуальная машина, стековая, это просто стек, на котором работают все вызываемые функции. То есть вызвали следующую функцию — она в том же самом стеке работает.

В Фантоме не так, в Фантоме каждая отдельная функция, создает под себя отдельный стек, и в нем она живет. Почему? Потому что вы можете позвать метод чужого класса, и если он ошибается, что-то напортит со стеком, то это он вам испортит. То есть это будет порча между разными юзерами. Вот этого мы не хотели допустить, поэтому там все очень-очень-очень жестко разграничено, и сам по себе этот код, в виртуальной машине, он менее эффективен, чем явский, зато он очень строгий. Кроме того, я не знаю, правильно ли было это решение или нет, может мы от этого откажемся, Явская виртуальная машина она же как сделана? У нее есть фаза верификации байт-кода и фаза исполнения. Байт код загружают, она его просматривает — «так можно, так можно, так можно… а так нельзя — не будут запускать». Или «можно-можно-можно-можно, все хорошо, запускать буду». В процессе работы это все не проверяется. В Фантоме сделано по другому, в нем нет верификации вообще, но в процессе работы байт-кода проверяется все. Попытка выйти за границы массива, попытка выйти за размеры стека, все-все-все, все это проверяется на ходу. Это очевидно влияет на эффективность, и довольно заметно. Впрочем, может не надо этим сейчас заниматься, потому что в любом случае мы будем делать shipment, он сейчас в работе, понятно, что многие верификации, которые сейчас делаются в run-time, в процессе JIT-компиляции они будут сделаны статически, наверное всем это не поможет, но вот, это одна из таких архитектурных проблем, которых надо еще продумывать.

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

Первые полгода или даже год система работала без garbage collection-а, тратилось-тратилось-тратилось, а потом все падало. А начав делать garbage collection, я осознал неприятную вещь.

Нормальный Фантом, вот нормальный, который будет на 64 битных машинах, он будет оперировать адресным пространством, объектным, размером с диск. То есть на сегодня — терабайт, два терабайта, завтра — десять терабайт примерно. И garbage collection, который нужно провести, нужно провести на всем этом самом терабайте или десяти терабайтах.

И надо понимать, что на сегодня, никакого garbage collection не существует. Весь garbage collection, который на сегодня реализован, это сборка «хороших объектов», и выкидывание остальных. Понятно, да? То есть для того, чтобы собрать мусор, нужно обойти все нужные объекты на диске. И не существует никакой возможности, каким-то образом это дело… даже все существующие, там, частичные сборщики мусора, все равно, они либо неполные — за ними кто-нибудь да подбирает, либо они останавливают к чертовой матери всю машину, в этом случае, можно убирать и все, но и в том проблема. Казалось, что это неразрешимая проблема, потому что терабайт, и мало того, что терабайт, так ведь этот терабайт еще и не весь в памяти, на самом то деле, система ведь загружает в оперативку лишь те объекты, которые реально нужны, остальные валяются там, на диске.

Если мы начинаем делать сборку мусора, по всей этой самой катавасии, то это означает, что мы должны поднять с винта все, что на нем, вообще говоря, есть. В оперативку. Ну не полностью, ведь нас волнует в основном header объекта, но нам ведь нужно не только поднимать, но и записывать… Война и немцы! Просто, конкретно, совершенно беда.

Я стал прикидывать, ну может быть, я не знаю, ночью она будет это делать… в тихую там… Полные, жуткие страдания, я реально раздумывал, не убить ли проект, потому что непонятно, что делать. Две вещи меня спасли. Первое — я почитал про Azul, которое реально, эти ребята научились делать garbage collection на терабайте, on stock.

Даже три есть выхода из ситуации.

Второй выход из ситуации, мы его пока не приняли, потому что все эти алгоритмы они, как выяснилось, кем-то довольно злобно лицензированы, в чем тонкость? Есть очень старый, добрый, простой способ garbage collection-а. Называется он reference counting. Мы считаем число ссылок на объект, упало в ноль — объект выкинули. Твердый, дубовый, работает довольно просто, очень даже можно жить, но опять же всем известно, что он не спасает, если у вас есть циклы объектов. Сделали колечко, у всех ссылочка больше чем нолик, все они будут валятся где-то в углу, внешней ссылки на это нету, вечно потерянная память, не собирается. Есть алгоритмы, оказывается, которые называются loop-breakers. Они каким-то образом анализируют, потихонечку идут вдоль объектов в системе, и как-то приглядывают, не цикл ли это? Если цикл — то они его разрезают, а дальше обычный garbage collection добивает. Хорошая штука, но с лицензиями непонятно, поэтому тоже решили откинуть в сторону.

И третий вариант, собственно, который пришел в голову, когда я пошел искать в интернете все, что есть на тему garbage collectionа, это большая такая область, тяжелая, нашел статью, в которой автор размышлял на тему,… В чем есть самая большая проблема garbage collectionа? Если мы все можем остановить, спокойно програмку выключить, и спокойно все обойти, то очень легко сделать сборку мусора. А если программа продолжает работать, то сборка мусора может оказаться неверной, причем неверной в неправильную сторону. Ладно бы она не нашла какой-нибудь мусор, так она может еще счесть мусором какие-то правильные данные, там очень простая ситуация, которая может случится, система обходит все объекты, вот некоторые объекты, представьте себе, отсюда на него есть ссылка, система ходила-ходила-ходила, не дошла до него через, слева, пошла вправо, а это время его взяли и отдали левому объекту, который уже был обойден до этого. Она и здесь, пока ходила, его не увидела, его перекинули, она его вообще не выдала, а он при этом вполне нормальный, живой.

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

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

Отсюда возникло очевидное решение. В Фантоме два garbage collectора. Сейчас пока один, второй в разработке.

Первый garbage collector сделан на ref-countах, он очень тупой, у него есть два важных свойства: он всегда коротко работает, очень маленькое, предсказуемое время нужно на проверку того, что ссылка стала нулем. Она стала нулем — убил локальный объект. Причем тоже я про это говорил, статистика говорит нам, что большинство объектов в системе создаются на короткое время и имеют одну ссылку. Поэтому refcount garbage collector очень быстро убивает коротко живущие локальные объекты. Поэтому основной оборот памяти в системе, garbage collector-ом c refcount-ом вполне себе обрабатывается. А какое-то количество долго живущих объектов с циклами, они постепенно откладываются, то есть остаются, не собираются, этим сборщиком мусора, попадают в snapshot, и после того, как случился snapshot, запускается длинный garbage collectор, который уже собственно сделанную фотографию системы медленно-медленно, спокойно ее обходит, находит в ней мусор, и его уничтожает.

Почему собственно я этого не боюсь? Когда я описывал это людям, которые занимаются garbage collectoрами, они меня спросили — вот ты не боишься, что недособранных объектов станет очень много и они всю память заполнят? Нет, не боюсь. Потому, что они заполняют виртуальную память. Ну, во-первых, диска у нас и так до хрена, во-вторых, хорошо, ну положим мы на него еще ну мегабайт, ну два, ну сто мегабайт даже, вообще говоря, современному диску это совершенно незаметно. Вот, более-менее решенная проблема.

Еще есть некоторое количество проблем, которые возникают именно в такого типа системах, в линуксах их быть не может. Например, как устроен системный вызов в обычной операционной системе? Работает программа, ей хочется сделать fileopen, она делает прерывание, попадает в ядро, … плохой пример. Скажем — read из socket-а. Попадает в ядро, начинает читать из socketа, а в socketе ничего нет! Что происходит? Она блокируется! То есть программа ушла в системный вызов, и там заснула. Когда данные появляются, она пробуждается, мы возвращаемся обратно в код, поехали дальше.

Такого в Фантоме делать нельзя. Почему? Потому что, как устроена память Фантома? Это какое-то несвоппируемое ядро, и после него, дальше, сверху, начинается эта часть памяти, которая персистентна, в ней живет вся наша обьектная…

Если она пошла в ядро, то делать snapshot нельзя. Почему? Потому что ядро может измениться, мы можем при следующем старте взять другое ядро, проапгрейдить его, да и просто даже, даже старое ядро, запустившись заново, оно находится в другом состоянии. И если мы пытаемся поднять снепшот, в котором код находится в ядре, то скорее всего он просто взорвется.

Поэтому вот правило «разработки в Фантоме» гласит, что все системные вызовы блокироваться не могут. Сходил, забрал — возвращайся.

Проблема! Ведь все-таки блокироваться как-то надо. Вот тупой совершенно системный вызов sleep, какой-нибудь, вот поспать процесс хочет, секундочку-другую, что-то надо с ним сделать. Решено следующим образом. Если процесс хочет сделать что-то такое, что может его заблокировать, то это делится на два вызова — в первом вызове он уходит и сразу возвращается, но вернувшись, тут же засыпает, там, у себя наверху, в состоянии, нормальном, полноценном, в том, которое можно зафотографировать. А ядро, если оно сделало что-то важное для него, и хочет ему об этом сообщить, оно его пробуждает, и в следующем системном вызове оно его забирает. Такая вот непростая модель.

Что еще такого, нетривиального? Ну во общем, на самом-то деле, по большому счету, нетривиальности все находятся на той стороне, на стороне ядра. Для программиста, Фантом — это очень простая вещь, система, которая такая же, как все остальные, только программы в ней живут вечно. Тоже в общем, неправильно говорю, потому что вызывает неверные ассоциации. Данные живут вечно! Программу можно убить, перезапустить, это обычный thread, который так же себя ведет. Потому что меня постоянно спрашивают — если программа вечно живет, а она сломалась, как же ее убить? Ну просто убить. Берешь и убиваешь. Тем же самым killом и убивешь, или нажатием на какой-нибудь крестик, там, в уголку. В этом плане никаких отличий нет. Просто данные, которыми она пользуется, они остались. При остановке процесса, все это остается.

В принципе, из некоторых таких более или менее нетривиальных мыслей, которые в процессе разработки возникали, это было желание, как-то более или менее прозрачно поддержать в плане взаимодействия через сеть, потому что то, что я писал, это все равно, в рамках одной машины. Теперь уже хочется, хорошо, раз мы сделали такую объектную среду, давайте с ней что-нибудь сделаем в рамках сети — дистанционную инвокацию объектов, а желательно бы еще и миграцию. Потому что там тоже вылезают очень нетривиальные ситуации. Если мы сделали ссылку на объект на другой машине, то может случится превеселейшая вещь. Может получится ??? объектов через разные машины. С точки зрения каждой из машин, оно как бы осмысленно, что с кем-то вне, оно связано, а в сумме, эта вся куча объектов только друг на друга ссылается, и никому не нужна. В силу этого, для такого вида среды нужна распределенная сборка мусора, которая умеет, собирать мусор больше чем на одной машине. И тоже, это меня довольно сильно пугало — ну, предположим, Фантом победил так весь мир, везде сплошные Фантомы, и все они связались друг с другом ссылками, ссылки с машины на машины по всему миру, и бешенное количество сетевого мусора, который валяется и не может быть убит, и вся планета завалена этим, и я в этом виноват.

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

Вопросы


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

Коллеги, обращаю ваше внимание, что Дмитрий Завалишин будет выступать в ближайшую пятницу (29 апреля) на Application Developer Days в Санкт-Петербурге. У вас есть отличная возможность поговорить с ним лично. Присоединяйтесь к нам!

Примечания

  1. Стенографист был один из тех, что поднял тогда руку (было это на РИТ-2010), а делал я и OLE-объекты, и OLE-объект, который позволял запускать VB-скрипты через AXHost, а те скрипты использовали другие OLE-объекты… впрочем, было это реально давно, в 1997 году где-то.
  2. Cм. нашу видео-скринкасто-запись, там все видно.
  3. Cтраничка доклада на сайте конференции.