Содержание


Inferno и Plan 9

Часть 4. Разработка распределенных приложений

Comments

Серия контента:

Этот контент является частью # из серии # статей: Inferno и Plan 9

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Inferno и Plan 9

Следите за выходом новых статей этой серии.

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

Все это возможно благодаря файловым серверам, работающим на удаленной стороне и принимающим запросы на доступ к своим ресурсам посредством протокола 9P. Любой драйвер ОС Inferno представляет собой файловый сервер, многие более высокоуровневые компоненты ОС реализованы в виде файловых серверов, благодаря чему они легко поддаются управлению удаленно без применения громоздких механизмов RPC и написания сотен строк кода.

Однако что стандартные файловые серверы, предлагаемые ОС, могут покрыть потребности программиста только на удаленное управление самой ОС. Если вы решите создать более высокоуровневое приложение, выполняющее сложные вычисления на стороне сервера и предлагающее простой клиент, с помощью которого им можно управлять удаленно, вам не обойтись без создания собственного файлового сервера.

9P и Файловые серверы как основа распределенных приложений

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

  • Серверная сторона. Ее роль будет выполнять фоновой процесс (демон в терминологии UNIX), опрашивающий операционную систему на предмет сведений о загруженности процессора, занятой памяти, свободного дискового пространства и т.п.
  • Клиентская сторона. Простая графическая оболочка, которая получает данные от сервера и строит на их основе красивые графики (или же просто выводит на экран в понятном для чтения человеком виде).

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

Вот как может выглядеть набор файлов, экспортируемый нашим файловым сервером:

  • ctl - интерфейс управления, на случай если клиент захочет изменить поведение сервера. Чтение этого файла должно привести к отдаче текущих настроек.
  • memory - информация о количестве занятой/свободной памяти.
  • disk - занятость дисков.
  • uptime - длительность непрерывной работы сервера.

Файл ctl доступен как для чтения, так и для записи, тогда как все остальные файлы доступны только для чтения. В зависимости от настроек, записанных в файл ctl, некоторые из других файлов (или даже все) могут и не существовать (например, вы можете отключить доступ к информации о занятости дисков).

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

Файловый сервер

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

Основной строительный компонент, который предлагает Inferno создателям файловых серверов это модули Styx и Styxservers. Первый предоставляет программисту ряд вспомогательных функций и структур данных, которые облегчают процесс приема новых сообщений, извлечения из них данных и формирования ответных Styx-сообщений.

Два наиболее важных компонента модуля Styx это абстрактные типы данных Tmsg и Rmsg, инкапсулирующих Styx-запросы и Styx-ответы. Модуль Styxservers более сложен. Он помогает обрабатывать Styx-сообщения, производить навигацию по созданному файловым сервером пространству имен и обеспечивает программиста набором стандартных действий, которые обычно выполняются в ответ на Styx-сообщения (например, Styxservers может самостоятельно отвечать на запрос версии 9P/Styx-протокола). Все это позволяет программисту сосредоточиться на обработке запросов на чтение и запись в файлы, а все остальное возложить на плечи Styxservers.

Следующие разделы посвящены реализации упрощенной версии сервера мониторинга с помощью этих модулей.

Реализация файлового сервера. Шаг 1

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

implement MonitorServer;

# Подключаем интерфейсы всех необходимых нам модулей
include "sys.m";
	sys: Sys;
include "draw.m";
include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import styx;
include "styxservers.m";
	styxservers: Styxservers;
	Styxserver, Navigator: import styxservers;
	nametree: Nametree;
	Tree: import nametree;

# Наш модуль представляет собой программу, поэтому реализует
# только одну внешнюю функцию, необходимую для запуска
MonitorServer: module
{
	init: fn(nil: ref Draw->Context, argv: list of string);
};

Этот фрагмент исходного кода не должен вызвать у вас вопросов, за исключением ключевого слова import, о котором мы не говорили в прошлой статье. Import используется для повышения удобства использования структур данных и функций другого модуля путем их импортирования в пространство имен нашего файла. Если бы мы не поместили строку "Styxserver, Navigator: import styxservers;" в файл, нам бы пришлось обращаться к функции new абстрактного типа Navigator используя полную запись следующего вида:

styxservers->Navigator.new();

После импорта Navigator, мы можем писать просто "Navigator.new()".

Если у вас возник вопрос откуда взялся модуль Nametree, с типом которого создается переменная nametree, то имейте ввиду, что один файл реализации может содержать объявления интерфейсов сразу нескольких модулей. В нашем случае styxservers.m содержит два объявления интерфейса: Styxservers и Nametree.

Теперь создадим константы, которые будут использоваться для ссылки на наши файлы:

Qroot, Qctl, Qmemory, Qcpu, Qdisk, Quptime: con big iota;

Они будут содержать значения от 0 до 4 и использоваться для однозначной идентификации файлов/каталогов в коде. Далее мы должны определить функцию init, с которой начнется исполнение нашего модуля:

init(nil: ref Draw->Context, args: list of string)
{
	# Загружаем и инициализируем модули
	sys = load Sys Sys->PATH;
	styx := load Styx Styx->PATH;
	styx->init();
        styxservers = load Styxservers Styxservers->PATH;
        styxservers->init(styx);
        nametree = load Nametree Nametree->PATH;
        nametree->init();

        # Запускаем поток Nametree, отвечающий за хранение
        # пространства имен нашего файлового сервера
        (tree, treeop) := nametree->start();

        # Добавляем в пространство имен файлы
        # Корневой каталог должен быть добавлен первым
        tree.create(Qroot, dir(".", 8r555|Sys->DMDIR, Qroot));
        # Теперь можно присоединить к нему все остальные файлы
        tree.create(Qroot, dir("ctl", 8r666, Qctl));
        tree.create(Qroot, dir("memory", 8r444, Qmemory));
        tree.create(Qroot, dir("disk", 8r444, Qdisk));
        tree.create(Qroot, dir("uptime", 8r444, Quptime));

        # Запускаем поток Navigator, который будет использован
        # Styxserver для доступа к пространству имен
        nav := Navigator.new(treeop);

	# Запускаем поток Styxserver, который будет читать Styx-сообщения
        # и после обработки отправлять их в канал tchan
        (tchan, srv) := Styxserver.new(sys->fildes(0), nav, Qroot);

	# Порождаем поток обработки сообщений
	spawn server(tchan, srv, tree);
}

Функция start модуля Nametree возвращает кортеж, состоящий из ссылки на экземпляр абстрактного типа данных Tree, который можно использовать для модификации пространства имен, созданного потоком Nametree, и канала для передачи ссылок на данные типа Styxservers->Navop. Он нужен для навигации по пространству имен с помощью экземпляра абстрактного типа данных Styxservers->Navigator.

Метод create абстрактного типа Tree принимает два аргумента: ссылка (на самом деле это константа, но Tree использует ее для идентификации файла) на родительский каталог и экземпляр абстрактного типа Sys->Dir, описывающий атрибуты файла. Для создания экземпляра типа Sys->Dir мы используем описанную ниже функцию dir, которая принимает три аргумента: имя файла, права доступа и ссылка. Обратите внимание, что при создании родительского каталога Qroot мы используем ссылку на него самого в качестве его родительского каталога. Это значит, что Qroot должен быть корневым каталогом всего пространства имен нашего файлового сервера.

Следующий шаг - создание экземпляра абстрактного типа Navigator с помощью метода new, принимающего в качестве аргумента созданный ранее канал treeop. Он нужен только для того, чтобы создать экземпляр абстрактного типа Styxserver в следующем шаге.

Выполнив все подготовительные действия мы можем запустить поток Styxserver с помощью метода new одноименного абстрактного типа. В качестве аргументов new принимает ссылку на файловый дескриптор (тип Sys->FD), из которого он будет читать Styx-сообщения, ссылку на экземпляр абстрактного типа Navigator и ссылку на корневой каталог пространства имен. Обратно он возвращает кортеж, состоящий из канала, по которому будет передавать Styx-сообщения, предварительно упакованные в экземпляры абстрактного типа Rmsg, и ссылку на экземпляр абстрактного типа Styxserver, с помощью которой мы сможем отправлять ответные сообщения.

Обратите внимание, что в качестве файлового дескриптора мы передали методу new ссылку на стандартный ввод нашего сервера (его можно получить с помощью функции sys->fildes(0)). Это нормально потому как мы будем подключать пространство имен файлового сервера к пространству имен Inferno с помощью стандартной команды mount, в результате все запросы на доступ к файлам внутри каталога (точки монтирования) будут перенаправлены на стандартный ввод смонтированного к ней файлового сервера.

Последний шаг - запуск функции server в качестве нового потока исполнения. Основное действо будет происходить в нем.

Функция dir, используемая для формирования экземпляра типа Sys->Dir:

dir(name: string, perm: int, qid: big): Sys->Dir
{
        d := sys->zerodir;
        d.name = name;
        d.uid = "monitor";
        d.gid = "monitor";
        d.qid.path = qid;
        if (perm & Sys->DMDIR)
                d.qid.qtype = Sys->QTDIR;
        else
                d.qid.qtype = Sys->QTFILE;
        d.mode = perm;
        return d;
}

Создание файлового сервера. Шаг 2

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

server(tchan: chan of ref Tmsg, srv: ref Styxserver, tree: ref Tree)
{
	# Создаем новую группу процессов
	sys->pctl(Sys->NEWPGRP, nil);

	# Читаем сообщения Styxserver
	while((gm := <-tchan) != nil) {
		# Проверяем тип сообщения
		pick m := gm {
		# Сообщение Write. Обрабатываем запрос на запись в файл/каталог
		Write =>
			# Проверяем, все ли условия для чтения файла были выполнены
			# и не содержит ли сообщение ошибок
			(c, err) := srv.canwrite(m);
			# Если нет, то отправляем обратно сообщение об ошибке
			if (c == nil){
				srv.reply(ref Rmsg.Error(m.tag, err));
			# Проверяем, является ли файл, к которому происходит обращение,
			# файлом ctl
			} else if (c.path == Qctl) {
				# Если происходит запись в файл ctl ничего
				# не делаем и отправляем ответ о получении сообщения
				srv.reply(ref Rmsg.Write(m.tag, len m.data));
			}

		# Сообщение Read. Обрабатываем запрос на чтение файла/каталога
		Read =>
			(c, err) := srv.canread(m);
			if (c == nil)
				srv.reply(ref Rmsg.Error(m.tag, err));
			# Определяем файл, к которому происходит обращение
			# В зависимости от этого отправляем в ответ результаты
			# выполнения разных команд низлежащей ОС
			case c.path {
			Qmemory =>
				data := run_cmd("free");
				srv.reply(styxservers->readbytes(m, data));
			Qdisk =>
				data := run_cmd("df -h");
				srv.reply(styxservers->readbytes(m, data));
			Quptime =>
				data := run_cmd("uptime");
				srv.reply(styxservers->readbytes(m, data));
			* =>
				# Пусть запросы на чтение несуществующих файлов
				# обрабатывает Styxserver
				srv.default(gm);
			}

		# Обработку всех остальных запросов возложим на плечи Styxserver
		* =>
			srv.default(gm);
		}
	}
	# Завершаем поток Nametree
	tree.quit();
}

В самом начале функция делает то, что UNIX-программисты назвали бы форком процесса: она помещает поток в новую группу процессов (потоков). Это нужно чтобы наш файловый сервер не был "убит" после закрытия вызвавшего его командного интерпретатора. В Inferno нет системного вызова fork (его роль исполняет ключевое слово spawn языка Limbo), а для управления "весом" потоков (набором разделяемых с родителем ресурсов) используется функция pctl модуля sys. Передав ей в качестве аргумента константу Sys->NEWPGRP мы "отделили" поток server от всех остальных потоков приложения и сделали его обособленным.

Далее функция запускает цикл для чтения сообщений потока Styxserver, которые поступают по каналу tchan. Сообщения приходят в виде экземпляров абстрактного типа Tmsg, определенного в модуле Styx. Для определения типа сообщения (чтение, запись, получение информации о файле или что-то другое) используется ключевое слово pick.

В Limbo ключевое слово pick предназначено для создания и работы с так называемыми "Pick ADT", абстрактными типами данных, способными инкапсулировать различные типы данных в зависимости от своего "подтипа". Это альтернатива наследованию объектно-ориентированных языков. Например, тип Tmsg модуля Styx предназначен для хранения сообщений, передаваемых по протоколу Styx. Но протокол Styx поддерживает передачу различных типов сообщений, поэтому в зависимости от типа сообщения Styx, содержимое Tmsg должно быть разным, за исключением переменной tag, в которую записывается уникальное число для связывания Styx-запроса со Styx-ответом.

Программируя Styx-сервер на объектно-ориентированном языке мы могли бы создать класс Tmsg с единственным полем tag и универсальными методами для обработки сообщения, а затем создать классы ReadTmsg, WriteTmsg и т.д. Каждый из них наследовал бы Tmsg, добавляя к нему несколько новых полей, специфичных для определенного типа сообщения. Для достижения того же эффекта в Limbo мы можем использовать Pick ADT. Если вы откроете файл интерфейса модуля Styx (/modules/styx.m), то увидите следующие строки:

Tmsg: adt {
	tag: int;
        pick {
	...
	Read =>
		fid: int;
                offset: big;
                count: int;
        Write =>
                fid: int;
                offset: big;
                data: array of byte;
	...
}

С помощью ключевого слова pick абстрактный тип Tmsg наделяется "подтипами" и может использоваться для представления различных типов сообщения. Вы могли бы создать новый экземпляр абстрактного типа Tmsg с подтипом Read следующим образом:

m := ref Tmsg.Read(112, 4, 5556, 45);

Что равно следующему коду:

m := ref Tmsg.Read;
m.tag = 112;
m.fid = 4;
m.offset = 5556;
m.count = 45;

Вернемся к функции server. Здесь pick используется по-другому, а именно для определения подтипа экземпляра абстрактного типа Tmsg, полученного из канала. В этом она становится очень похожей на оператор выбора case и служит для того, чтобы выбрать блок действий, обрабатывающий экземпляр Pick ADT, основываясь на его подтипе.

Идем дальше. Получив сообщение на запись в файл (Tmsg.Write) мы должны проверить, все ли условия для этого были выполнены (например, был ли файл перед этим открыт). Для этого можно использовать метод canwrite абстрактного типа Styxserver, который принимает качестве аргумента ссылку на экземпляр абстрактного типа Tmsg.Write и возвращает кортеж, состоящий из экземпляра абстрактного типа Fid, содержащего информацию о файле, и строки, содержащей сообщение об ошибке. Если Fid оказался равен nil, значит на стороне клиента произошла ошибка, и мы отправляем сообщение об ошибке обратно с помощью метода reply абстрактного типа Styxserver. В качестве аргумента он принимает ссылку на экземпляр абстрактного типа Rmsg (ответ на Tmsg), которую мы формируем на лету, создавая Rmsg с подтипом Error.

Далее мы проверяем, является ли записываемый файл файлом ctl, и если да, отправляем обратно сообщение о его получении (ответное сообщение на Tmsg.Write должно содержать только tag и количество прочитанных байт). Больше ничего не делаем (если вы захотите реализовать обработку данных, записываемых в ctl, просто возьмите массив m.data, содержащий эти данные, и делайте с ним что хотите).

Начало обработки сообщения на чтение файла очень похоже на начало обработки записи. Просто проверяем, может ли клиент читать файл, а затем обрабатываем операции чтения различных файлов с помощью оператора выбора case. Для сохранения простоты примера мы используем команды низлежащей ОС (предположим, что Inferno работает в гостевом режиме поверх Linux) для получения информации о памяти, заполненности дисков и времени работы машины. Функция run_cmd, описанная ниже, принимает в качестве аргумента имя команды и возвращает выводимые ей данные. Далее мы используем уже знакомый srv.reply для отправки ответа. Обратите внимание что в этот раз мы не стали самостоятельно формировать ответное сообщение, а вместо этого воспользовались функцией readbytes модуля Styxservers. Она сама создает ответное сообщение на запрос чтения файла, переданный в качестве первого аргумента и отправляет в нем данные, полученные из буфера, переданного во втором аргументе. Преимущество readbytes в том, что она правильно заполняет все поля сообщения, такие как текущее смещение файла, количество прочитанных байт и т.д. Без ее использования код получился бы заметно длиннее.

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

Функция run_cmd, применяемая для запуска команд хост-ОС, использует псевдо-устройство '#C' (cmd) ОС Inferno. Ее код приведен ниже.

run_cmd(cmd: string): array of byte
{
	# Если файл /cmd/clone не существует подключаем устройство
	# '#C' (cmd) к корню пространства имен
        if (sys->stat("/cmd/clone").t0 == -1)
        	sys->bind("#C", "/", Sys->MBEFORE);

	# Создаем новое подключение к устройству '#C' с помощью
	# открытия файла /cmd/clone
        cfd := sys->open("/cmd/clone", sys->ORDWR);
        if(cfd == nil) {
                sys->print("cannot open /cmd/clone");
		exit;
	}

	# Читаем из /cmd/clone идентификатор подключения
        buf := array[32] of byte;
        if((n := sys->read(cfd, buf, len buf)) <= 0) {
                sys->print("cannot read /cmd/clone");
		exit;
	}

	# dir теперь содержит путь к каталогу подключения
        dir := "/cmd/"+string buf[0:n];
	
	# Открываем файл /cmd/<номер подключения>/wait
        wfd := sys->open(dir+"/wait", Sys->OREAD);

	# Все процессы команды должны быть уничтожены после
	# закрытия файла /cmd/clone
        sys->fprint(cfd, "killonclose");

	# Запускаем команду
        if(sys->fprint(cfd, "exec %s", cmd) < 0) {
                sys->print("cannot exec");
		exit;
	}

	# Открываем файл /cmd/<номер подключения>/data для чтения
	# вывода команды
	if((fromcmd := sys->open(dir+"/data", sys->OREAD)) == nil) {
		sys->print("cannot open /cmd/data for reading");
		exit;
	}

	# Читаем вывод команды...
	cmdbuf := array[8192] of byte;
	num := 0;
        for(;;) {
                r := sys->read(fromcmd, cmdbuf, len cmdbuf);
                if(r <= 0)
                        break;
		num += r;
        }
	
	# ...и возвращаем его
	return cmdbuf[0:num];
}

Псевдо-устройство '#C' (cmd) представляет собой файловый сервер, работающий внутри ядра Inferno. Он реализует файловый интерфейс, сходный с интерфейсами многих других файловых серверов Inferno. Если вы захотите обратится к устройству '#I' (ip), представляющему сетевой стек, или устройству '#D' (draw), предназначенному для работы с графической подсистемой, то увидите, что они очень похожи, а для работы с ними используется примерно одна и та же последовательность действий.

Сначала вы должны проверить, подключено ли пространство имен файлового сервера к вашему текущему пространству имен. Для этого достаточно проверить, существует ли файл clone внутри стандартной точки монтирования этого сервера (например, для '#C' это каталог /cmd, для '#I' - /net, для '#D' - /dev/draw и т.д.). Если нет, подключите его с помощью функции bind модуля Sys.

Теперь вы должны инициировать подключение к файловому серверу с помощью открытия его файла clone и прочитать из него номер подключения. Далее откройте файл wait внутри каталога, имя которого соответствует номеру подключения. Теперь вы можете работать с файловым сервером с помощью чтения и записи файлов, расположенных внутри того же каталога, но здесь начинаются различия. Разные файловые серверы представляют различный набор файлов и способов взаимодействия с ними, поэтому придется прочитать man-страницу файлового сервера для определения дальнейших шагов.

Запустив команду 'man cmd' вы узнаете, что в качестве следующего шага вы должны записать строку "killonclose" в /cmd/clone чтобы файловый сервер уничтожил все процессы команды после закрытия файла соединения. Затем вы должны записать в него же строку "exec имя_команды" для запуска команды, вывод которой появится в файле /cmd/<номер подключения>/data.

Создание файлового сервера. Шаг 3

Теперь вы можете собрать все кусочки пазла воедино, поместить их в файл /usr/inferno.server.b внутри каталога с установленной Inferno, запустить ОС как это было показано во второй статье цикла и выполнить следующие команды для компиляции файла:

; cd /usr/inferno
; limbo server.b

Далее запустите файловый сервер и подключите его пространство имен к каталогу /mnt (или любому другому) чтобы проверить его работоспособность:

; mount {./server} /mnt

Команда "ls -l /mnt" должна вывести на экран следующие строки:

--rw-rw-rw- M 8 monitor monitor 0 Jan 01  1970 /mnt/ctl
--r--r--r-- M 8 monitor monitor 0 Jan 01  1970 /mnt/disk
--r--r--r-- M 8 monitor monitor 0 Jan 01  1970 /mnt/memory
--r--r--r-- M 8 monitor monitor 0 Jan 01  1970 /mnt/uptime

Как вы могли заметить, сервер не устанавливает правильные размеры файлов и дату. Это можно исправить добавив несколько строк в наш код, однако смысла в этом нет. Прочитав один из файлов с помощью команды cat вы получите на экран вывод закрепленных за ними команд.

Теперь вы можете экспортировать пространство имен каталога /mnt:

; styxlisten -A 'tcp!*!styx' export /mnt

И подлючить его к пространству имен удаленной машины:

; mount -A 'tcp!127.0.0.1!styx' /mnt

Осталось реализовать клиент, который может быть сложным графическим приложением, простым шелл-скриптом или даже командой cat. Более того, вы можете смонтировать пространство имен файлового сервера из Linux с помощью 9P-клиента v9fs для ядра Linux.

Выводы

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=772962
ArticleTitle=Inferno и Plan 9: Часть 4. Разработка распределенных приложений
publish-date=11082011