Вот уже почти тридцать лет Plan 9 будоражит умы юниксоидов. Операционная система, на десятилетия опередившая свое время, оказалась просто не нужна и была выброшена на обочину айтишного мира. Inferno, ее преемница, оказалась еще менее востребованной и в конце концов нашла свою смерть в руках никому не известной компании Vita Nuova. Однако обе системы и по сей день продолжают допиливаться отдельными разработчиками и даже применяются для решения узкого круга задач. Появилось множество форков, большая часть из них умерли, а некоторые живут и по сей день. Едва ли не самый интересный из них — Node9, симбиоз платформы Inferno и языка Lua, способный потягаться с великим и ужасным Erlang.

 

Вместо введения

Я уже давно слежу за развитием Inferno и Plan 9, читаю листы рассылки, оцениваю форки, время от времени всплывающие на GitHub, а нередко и сам пишу что-нибудь незамысловатое на Limbo, стандартном языке Inferno. Поэтому проект Node9, базирующийся на идеях Inferno и использующий один из моих любимых и, как мне кажется, сильно недооцененный язык Lua, сразу привлек мое внимание. И как оказалось, не зря. Проект действительно оказался более чем серьезным и гораздо более развитым, чем все остальные «варианты» системы, виденные мной до этого. Что такое Node9? В терминологии самих разработчиков это hosted operating system, а в более привычной нам формулировке — среда или даже, лучше сказать, платформа для запуска распределенных приложений. Node9 базируется на идеях и технологиях Inferno и Plan 9, которые должны быть известны многим читателям. Тем не менее без некоторых пояснений не обойтись.

В свое время в основу Plan 9 легла идея единого IPC-механизма, который бы позволял обмениваться данными как локальным, так и удаленным процессам. Предполагалось, что вся система будет построена вокруг этого механизма, поэтому уже на ранних этапах разработки ОС был спроектирован протокол 9P (в Inferno его переименовали в Styx), отвечающий за его реализацию. А все остальные компоненты системы, включая драйверы, графический стек и обычные приложения, использовали его для взаимодействия друг с другом.

Ключевая особенность 9P в том, что, по сути, это протокол доступа к удаленным (и локальным) файлам. Поэтому в Plan 9 буквально все было представлено (синтетическими) файлами, начиная от указателя мыши и заканчивая сетевым стеком. Многие вполне обычные приложения также предоставляли доступ к «своим файлам» с целью обмена данными с другими приложениями. Даже настройка системы осуществлялась не через конфиги, а с помощью скриптов, записывающих те или иные значения в файлы. А самое главное — одна Plan 9 машина могла смонтировать всю или часть файловой системы другой машины по сети и использовать ее файлы для взаимодействия с удаленными приложениями, то есть, по сути, выполнять удаленный вызов процедур (RPC). Такой подход позволял из коробки получить многие интересные вещи, включая удаленный рабочий стол (дисплей, клавиатура и мышь — это ведь тоже файлы), удаленный доступ к периферии, простое и удобное масштабирование вычислительных систем. Мой излюбленный пример — реализация NAT. В Plan 9 она вообще не нужна, достаточно смонтировать каталог /net (содержит файлы сетевого стека) удаленной машины поверх локального, и все запущенные после этого приложения будут использовать сетевой стек этой машины.

По идее, такой трюк не должен работать, так как для доступа к сетевому стеку удаленной машины нужен сетевой стек локальной. Но в Plan 9 это работает, благодаря так называемым пространствам имен. Это еще один ключевой механизм Plan 9, который гарантирует, что любые изменения файлового дерева, видимого одному процессу, не отразятся на файловых деревьях, видимых другим процессам. Возвращаясь к примеру с NAT, это значит, что смонтированный сетевой стек удаленной машины будет виден только смонтировавшему его процессу и его потомкам, тогда как все остальные процессы будут продолжать работать с локальным сетевым стеком. Это очень мощный механизм, позволяющий создавать различные окружения для процессов, а также контролировать их доступ к ресурсам (кстати, песочницы OpenVZ, LXC и Docker в Linux используют ту же идею пространств имен).

Plan 9 собственной персоной
Plan 9 собственной персоной

Последнее, что важно для нас в контексте введения в Node9, — это каналы и потоки. Язык Limbo, используемый в Inferno по умолчанию, оснащен очень простой и эффективной системой многопоточности, которая, кстати говоря, впоследствии перекочевала в язык Go. Суть ее проста до безобразия: в любой момент любую функцию можно отправить в отдельный поток, вызвав ее с помощью ключевого слова spawn. Нечто подобное есть в чистом Lua и некоторых других языках, но важно не это. Важно то, что при вызове такой функции можно указать в ее аргументах канал передачи данных (есть такой тип данных в Limbo), через который она сможет общаться с дочерним потоком. Это называется модель многопоточности CSP, и она применяется в Limbo и Go для синхронизации потоков и обмена данными между ними. Но что самое интересное, канал можно превратить в файл! И конечно же, этот файл (точнее, каталог, его содержащий) можно смонтировать с удаленной машины. Брюки превращаются… в распределенное приложение.

Соберем все это вместе и получим потрясающую платформу для создания распределенных приложений. Проблема только в том, что Plan 9 сама по себе довольно маргинальная система, и внедрять ее в продакшен никто не будет. Inferno, с другой стороны, способна работать поверх других систем, включая Linux, OS X и Windows, но использует язык Limbo, который хоть и имеет низкий порог вхождения, но почти никому не известен и не располагает хорошей базой готовых библиотек. Именно по этой причине появился Node9.

Кто и как использует Inferno и Plan 9

  • Компания Coarid применяет Plan 9 и ее компоненты в своих решениях NAS.
  • IBM использовала Plan 9 и некоторые компоненты Inferno на одной из своих суперкомпьютерных систем Blue Gene/L, построенной на базе 64 тысяч процессоров.
  • ОС Plan 9 использовалась для управления системой освещения стадиона на Олимпийских играх в Сиднее в 2000 году.
  • Долгое время операционная система Inferno применялась в некоторых аппаратных решениях Lucent (позднее Alcatel-Lucent), таких как файрволы и шлюзы. Достоверно известно, что внутри Lucent Managed Firewall, Lucent VPN Firewall Portfolio и Lucent Pathstar скрыта именно ОС Inferno.
  • На основе операционной системы Inferno работали некоторые модели аппаратов для SIP-телефонии, в частности Philips IS2630 и Alcatel Webtouch.
  • Некоторые исследовательские центры и университеты выбирают Inferno в качестве платформы для grid-вычислений: Cambridge Crystallography Data Centre (CCDC), The University of York Department of Biology, Rutgers University Libraries, Evotec OAI AG (Deutsche Boerse EVT; TecDAX 30), Montclair State University.
 

Inferno, LuaJIT, libuv и все-все-все

Итак, Node9 — это симбиоз низкоуровневой части hosted-версии (работающей поверх другой ОС) Inferno, быстрого JIT-компилятора LuaJIT и библиотеки асинхронной обработки событий libuv, изначально разработанной для Node.js, а теперь используемой и другими проектами. Что все это дает стороннему разработчику? Ну, во-первых, это, конечно же, весь тот комплекс фирменных технологий Inferno / Plan 9 с возможностью писать софт на достаточно распространенном и очень простом в освоении языке Lua, снабженном эффективным JIT-компилятором LuaJIT.

В-вторых, это простая многопоточная модель, реализованная внутри самой платформы. В Node9 все процессы и потоки работают внутри одного процесса нижележащей ОС. Это значит, что для переключения между потоками не потребуется переключение контекста, а это может дать серьезный выигрыш в производительности. Ну и плюс ко всему уже обсуждавшаяся ранее модель CSP на основе функций, каналов и файлов, привязанных к каналам. CSP намного эффективнее традиционных реализаций многопоточности и, по сути, решает почти все проблемы распараллеленных приложений. Потоки здесь не делят одно адресное пространство (область видимости потока = области видимости функции), они не получают доступ к одним и тем же данным, а вместо этого используют синхронные сообщения (нет проблемы блокировок), их очень просто синхронизировать.

В-третьих, эффективная система асинхронной обработки событий (I/O, TCP, UDP, TTY), не погружающая программиста в callback hell, известный любому разработчику на Node.js. Node9 опирается на libuv во всем, что касается обработки ввода-вывода. Поэтому даже системные вызовы (если их можно так назвать в контексте «ОС, работающей поверх ОС») здесь реализованы через «событийный цикл». Работает это так. Планировщик Node9 последовательно передает управление каждому работающему потоку до тех пор, пока один из них не сделает системный вызов. В этот момент запускается примерно такая функция (в данном случае функция read):

function S.read(vp, s_read, fd, buf, nbytes)
    s_read.fd = fd
    s_read.buf = buf
    s_read.nbytes = nbytes
    n9.sysreq(vp, n9.Sys_read)
    coroutine.yield()
    return s_read.ret
end

Она помещает системный вызов в очередь libuv (функция n9.sysreq()), затем приостанавливает исполнение текущего потока и возвращает управление обратно. После этого планировщик передает управление следующему потоку, а по исчерпании очереди потоков запускает один проход событийного цикла libuv (проверка, получены ли запрошенные данные, в данном случае с диска или по сети, Node9 не делает различий). Затем все начинается сначала. Если же в ходе цикла libuv обнаруживает получение данных, управление возвращается сделавшему системный вызов потоку. Вдумчивый читатель может сказать, что все это сильно напоминает идею так называемых зеленых потоков, реализованную в Smalltalk, Tcl, Erlang, и будет абсолютно прав. Как и ее родоначальница Inferno, Node9 полностью основана на этой модели, но с привлечением возможностей libuv и языком Lua вместо Limbo. Реализация программной платформы на основе «зеленых потоков» уже неоднократно доказывала свою эффективность, и она почти не проигрывает событийной модели, применяемой в Node.js и nginx. Однако в отличие от последних она позволяет «просто писать код» и не переиначивать свое мышление в пользу callback-ориентированного программирования, что чревато ошибками и сильным запутыванием кода.

Благодаря тому что Node9 построена вокруг протокола 9P, ее приложения очень просто связать с приложениями на любых других языках, для которых есть библиотека с реализацией 9P. А таких языков много, это чистый си, Go, Java и множество других. И да, все они будут доступны удаленно.

 

Пробуем

В данный момент Node9 находится в стадии пре-бета. Работать она работает, софт запускать позволяет, планировщик планирует, libuv обрабатывает, однако многие функции Inferno до сих пор остаются нереализованными. Например, пока что нет возможности создать файловый сервер (в терминологии Plan 9 / Inferno это приложение, экспортирующее собственные интерфейсы через файлы), невозможно смонтировать каталоговую структуру удаленной машины, но можно открыть для монтирования для Plan 9 машин. Тем не менее с платформой уже есть смысл познакомится хотя бы для оценки ее возможностей в текущем состоянии (веб-приложение для нее можно написать уже сейчас).

Итак, Node9 доступна на GitHub и в данный момент умеет работать только в OS X и Linux, а после полного перевода системы на libuv заявлена поддержка множества других систем. Устанавливаем git и клонируем репозиторий:

$ git clone https://github.com/jvburnes/node9.git
$ cd node9

Сборка системы осуществляется с помощью приложения premake, а для сборки LuaJIT понадобится еще и automake. Все остальные инструменты сборки стандартные, поэтому установки пакета build-essential в Ubuntu и его аналогов в других дистрибутивах будет достаточно. После этого просто запускаем сборочный скрипт и ждем:

$ ./rebuild.sh

После успешного завершения компиляции запускаем систему (любители белых яблочек заменяют LD_LIBRARY_PATH на DYLD_LIBRARY_PATH):

$ export LD_LIBRARY_PATH=./lib
$ bin/node9

На экран должно вывалиться приглашение командной строки. Это местный шелл, возможностей у него кот наплакал: стандартные cd, ls, cat и несколько команд, которые ты найдешь в каталоге fs/appl. Сама система будет заперта в песочнице, корнем которой будет каталог fs внутри каталога с установленным Node9. Предварив команду пробелом, ее можно превратить в Lua-выражение. Никаких стандартных для UNIX пайпов и перенаправления ввода-вывода пока нет, только три команды, только хардкор.

Шелл Node9
Шелл Node9

Для запуска своего Lua-скрипта просто кладем его в fs/appl, а затем запускаем как обычную команду, опустив расширение (.lua). API здесь отличается от стандартного Lua. Базовые «системные вызовы» включены в модуль sys. Чтобы ознакомиться с ними, можно почитать исходник appl/syscall.lua. Модуля для сетевого взаимодействия нет вообще, вместо него, как и положено, файлы, в данном случае каталог /net. Чтобы узнать, как с ним работать, можно почитать исходники в каталоге fs/appl. Также в состав включена Lua-библиотека Penlight с набором функций для манипуляции с разными типами данных. В остальном ничего экстраординарного. Обычный проект в своей постначальной стадии развития. Готовый каркас, который осталось только допилить и завернуть в красивую упаковку.

Часть исходника fs/mount.lua
Часть исходника fs/mount.lua
LuaJIT vs Lua
LuaJIT vs Lua
 

Выводы

Node9 — более чем интересный проект. Его разработчики решили исправить то, чем Inferno всегда отпугивала программистов. Я сильно сомневаюсь, что эта система когда-нибудь приобретет большую известность, хотя бы потому, что Lua у многих программистов не ассоциируется с чем-то серьезным (даже если таковым является). Но свою нишу она вполне способна занять, благо большая часть работ по разработке платформы уже выполнена.

Оставить мнение

Check Also

Кавычкой по «Джумле». Учимся проводить SQL-инъекцию второго порядка на примере уязвимости в Joomla

Joomla — вторая по популярности система управления сайтами после WordPress. Уязвимость, о …