POWERMAN
"In each of us sleeps a genius...
and his sleep gets deeper everyday."

Сразу предупреждаю, что делать полный обзор архитектуры я не собираюсь. Во-первых есть оригинальная документация где всё детально описано. А во-вторых я ещё не настолько хорошо знаю Inferno, чтобы браться за написание детального описания архитектуры. Но вот про "фишки" архитектуры я обязательно расскажу, это самое интересное.

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

Программы и библиотеки.

В Inferno нет отличия между программой (application, script) и библиотекой (.so - shared object, .dll). Все программы на Limbo являются модулями, и могут быть использованы в этом качестве любой другой программой/модулем. Т.е. одна "программа" может в любой момент подгрузить ЛЮБУЮ другую "программу" в память как обычную библиотеку и начать вызывать её функции. А закончив вызывать нужные функции она может выгрузить ставший не нужным модуль из памяти (загрузка/выгрузка происходит во время выполнения, а не при запуске программы).

Под "программами" в Inferno подразумеваются такие модули, которые совместимы с интерфейсом "Command", что означает всего лишь что в модуле есть функция с названием init которая принимает два параметра (графический контекст и список аргументов) и ничего не возвращает.

А знаете, что происходит, когда вы в командной строке sh набираете имя программы для запуска - например, ls или pwd? Происходит смешная вещь - sh просто находит на диске файл с байт-кодом модуля, имя которого вы набрали, подгружает его и вызывает его функцию init. А когда init завершается sh выгружает этот модуль из памяти и ждёт набора имени следующей команды. :) (Если честно, на практике там всё капельку сложнее, но суть именно такая.) Таким образом аналога всего семейства сисколов POSIX exec*() в Inferno вообще нет - они не нужны. :)

Интерфейсы.

Это не самый ключевой момент, но раз уж о нём зашла речь… Limbo язык сильнотипизированный, проверка типов осуществляется и на этапе компиляции, и на этапе выполнения. Когда вы подгружаете какой-то модуль, вы получаете его handler в переменной, и тип этой переменной должен определять какие функции/константы есть в этом модуле, какие и какого типа у них параметры, etc. Вот описание всего этого хозяйства и называется интерфейсом модуля. (Похожая схема используется в паскале - там тоже отдельно пишется описание интерфейса и отдельно реализация модуля.)

При этом один модуль может реализовывать несколько разных интерфейсов (наборов функций/констант). И один интерфейс может иметь много разных реализаций в разных модулях (что используется в случае интерфейса "Command").

А когда вы подгружаете модуль Limbo нужно указать и путь к файлу с реализацией (байт-кодом модуля) и название интерфейса (описания интерфейсов подгружаются на манер include .h-файлов в C). После чего Limbo может проверить совместимость указанных вами реализации и интерфейса, и если всё в порядке вернёт вам handler этого модуля имеющий тип указанного вами интерфейса.

Что касается использования памяти, то всё сделано правильно - в памяти отдельно хранится одна копия кода подгруженного модуля на все процессы Inferno, а к каждому handler-у этого модуля прилагается отдельная память для хранения стека, глобальных переменных, etc. этого модуля. Когда вы выгружаете свой handler освобождается часть памяти занятая его копией глобальных переменных/стека, а когда освобождается последний handler из памяти выгружается и сам код модуля.

Процессы и нити (threads).

В Inferno нет отличия между процессом и нитью. По сути все процессы Inferno это очень легкие нити.

Как нити Inferno отображаются на нити host OS (если Inferno запущен в hosted режиме под виндой/линухом/etc.) зависит от реализации Inferno для этой host OS. Насколько я понимаю, Inferno использует смешанную модель - часть нитей его внутренние, т.е. один процесс/нить host OS может содержать несколько нитей Inferno, а часть нитей Inferno отображаются на нити host OS один-к-одному (в частности это касается нитей выполняющих блокирующие syscall host OS - напр. чтение из сокета).

По сути работающая OS Inferno изнутри выглядит как кучка выполняющихся, равных между собой нитей, шарящих между собой всю память. И это не смотря на то, что часть нитей "как бы" относится к одному работающему приложению, часть к другому - всё это только у вас в голове, а Inferno до этой группировки нитей по приложениям дела нет.

Если вас ужаснула идея отсутствия защиты памяти одних приложений от других и вы вспомнили про DOS… то я вас успокою - эта проблема, как и многие другие в Inferno, решена на архитектурном уровне и никаких специальных усилий "для защиты" не требует. А именно - в Dis (виртуальной машине) просто нет способа для прямого доступа в память по адресу, все указатели являются по сути высокоуровневыми ссылками на фиксированные участки памяти. Таким образом, любая нить имеет доступ только к тем участкам памяти, на которые у неё есть указатели. А указатель на существующий (т.е. выделенный какой-то другой нитью) участок памяти нить может получить только параметром или через IPC между нитями. Таким образом сильная типизация Dis обеспечивает абсолютную защиту памяти в качестве "побочного эффекта". :)