Теория и практика программирования на Си в Unix

         

Протокол коммуникации


X Window не зависит от используемых протоколов : существуют реализации X над TCP/IP и DECnet. Взаимодействие между сервером и клиентами использует socket интерфейс : sockets UNIX внутри одной машины, sockets TCP между двумя машинами, связанными сетью. В некоторых системах вместо sockets используется разделяемая память, что позволяет увеличить производительность в локальном случае. Кроме того, протокол Х можно заменить механизмом прямого доступа к памяти графической платы (DGA : Direct Graphic Access).

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



Прозрачность


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



Распределение


X Window использует модель клиент-сервер. Клиент - это прик- ладная программа. Сервер - это программа, которая управляет и контролирует интерфейс визуализации (дисплей). Сервер, таким образом, отвечает за вывод на экран, за управление мышью и клавиатурой, и отслеживание действий пользователя.

Важно обратить внимание на то, что роли клиента и сервера поменялись местами по сравнению с другими системами распределенной обработки (сокеты, TLI, NFS ...) : в X Window именно сервер отвечает за взаимодействие с пользователем (в то время, как в других системах за это отвечает клиент).

Прикладная программа-клиент является либо локальной (т.е., расположенной на той же машине, что и сервер), либо удаленной. Для того, чтобы запустить удаленную программу, достаточно добавить, при запуске прикладной программы, опцию -display (display имя_сервера:0.0).

X Window позволяет, таким образом, выполнять удаленные программы с локальным выводом информации, причем делает это в гетерогенных средах.



Распределение и параллелизм


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





Распределенный PHIGIX


PHIGIX - это коммерческая версия графического стандарта PHIGS, реализованная фирмой CELTIS. В рамках проекте EDF от фирмы CELTIS требовалось реализовать распределенную версию PHIGIX, обеспечивающую, в частности, связь между ЭВМ Cray и рабочей станцией HP 9000. На рисунке 8.7. приведена схема архитектуры PHIGIX. Структу- рами PHIGS управляет клиент. Обмен информацией между клиентом и сервером использует механизм сокетов и XDR (eXternal Data Representation). При этом используется собственный протокол обмена PHIGIX.

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



Реализация


Библиотека утилит должна обеспечивать следующие функции:
- запускать программы на удаленной машине (сервер);
- обмен данными (целые, вещественные, строки символов, массивы) между локальной программой и сервером, даже если внутренние представления на двух ЭВМ различны (например, Cray и рабочая станция Sun);
- контроль взаимодействия между программой и сервером, с управлением ошибками и тайм-аутами. Совокупность процедур, осуществляющих эти операции, реализованных на языках Си и Фортран, поставляется разработчиками программ.



Реализация сокетов


В версии BSD 4.3. системы UNIX сокеты реализованы в ядре, как показано на рис. 4.3.

Рис. 4.3. Реализация сокетов в BSD.

В версии System V Release 4, сокеты реализованы в форме библиотеки-надстройки над TLI (Transport Level Interface). Сокет-интерфейс, таким образом, сохраняется для существующих прикладных программ.



Реализация TLI


TLI представляет собой библиотеку функций Си, обеспечивающую сопряжение с различными сетевыми протоколами, реализованными в ядре (рис. 5.3.), с использованием механизма STREAMS, который будет описан в разделе 5.4.

Рис. 5.3. Реализация TLI



Считывание и запись в сокет в режиме виртуального соединения


Считывание с сокета с установлением логического (виртуального) соединения обладает следующей особенностью: примитив возв-ращает результат после считывания, по крайней мере, одного байта; в результате этого, число считанных байтов часто меньше требуемого числа. Следовательно, необходимо зациклить примитив считывания, чтобы добиться продолжения... Точнее, вот что возвращает вызов примитива read () (или recv (), или recvfrom ()):
-1: если соединение разорвано или не существует;
>0: было получено определенное число байтов; необходимо организовать цикл считывания для продолжения работы (конечно же, нет необходимости в организации цикла, если ожидаемое количество байтов уже получено);
0: удаленная программа выполнила close (), на чем и остановилась. Функция записи возвращает результат после того, как буфер выходных данных быдет передан через TCP. Программа предупреждается о возможной ошибке сигналом SIGPIPE, о котором речь пойдет далее.
Ниже в данной главе читатель найдет две процедуры: reads () и writes (), позволяющие читать и записывать n-байтов в сокет. Для этого организуется цикл по числу считанных или записанных байтов, до получения необходимого их количества. Две эти процедуры, применяемые повсеместно, использованы во многих примерах в данной главе.

ПРОГРАММА 25 /* процедуры reads() и writes() для чтения и записи в сокет в режиме виртуального соединения */

#include <stdio.h>

/* запись в сокет буфера, состоящего из пос байт */ int writes(sock, pbuf, noc) register int sock; /* дескриптор сокета */ register char *pbuf; /* буфер */ register int noc; /* число записываемых байт */ { int nreste, necrit; nreste = noc; while (nreste > 0) { necrit = write(sock, pbuf, nreste); if (necrit < 0) return(necrit); nreste -= necrit; pbuf += necrit; } return(noc-nreste); } /* считывание в буфер пос байт из сокета */ int reads(sock, pbuf, noc) register int sock; /* дескриптор сокета */ register char *pbuf; /* буфер */ register int noc; /* число считываемых байт */ { int nreste, nlit;

nreste = noc; while (nreste > 0) { nlit = read(sock, pbuf, nreste); if (nlit < 0) return(nlit); else if (nlit == 0) break; nreste -= nlit; pbuf += nlit; } return(noc-nreste); }



Сервер и прикладные программы


Сервер X - это процесс, независимый от ядра UNIX, реализованный в виде фоновой задачи (в отличие от некоторых других многооконных систем, встроенных в ядро - например, Sunview в системе SunOS). Нет необходимости выполнять специальную установку на машинах-клиентах. Прикладные программы используют библиотеку Xlib, которая управляет интерфейсом с сетью (Рис. 8.2.)

Рис. 8.2. - Сервер Х и прикладные программы Х.
1 - сервер Х
2 - прикладная программа-клиент Х
3 - Сетевой или локальный обмен данными



Сетевой уровень IP (Internet Protocol)


Уровень IP (Internet Protocol) обеспечивает рассылку дейтаграмм. Контроль потока весьма ограничен: посылка сообщения ICMP (Internet Control Message Protocol) на передающее устройство при насыщении машины. Согласование между адресом Internet и адресом Ethernet машины осуществляется посредством протокола ARP (Address Resolution Protocol). Используется передача Ethernet: машина, распознающая свой адрес Internet, отвечает, выдавая соответствующий адрес Ethernet.



Широковещательная передача


Данные могут передаваться всем сокетам сети в режиме дейтаграмм. Необходимо установить опцию SO_BROADCAST посредством примитива setsockopt ().



Широковещательный адрес


Сеть Ethernet обладает широковещательным адресом: кадр Ethernet, т.о. могут получить все машины данной сети. Широковещательный адрес Internet позволяет использовать эту возможность: его обычно получают, устанавливая в 1 все биты соответствующей части сетевого адреса. Широковещательные кадры не пересекают маршрутизаторов (см. ниже)



Сигналы


Сигнал представляет собой программное прерывание, посланное процессу, для того, чтобы сообщить о каком-либо событии, ожидаемом или нет. Источник сигнала не может знать отношение к этому получателя, особенно если последний решил игнорировать получение сигнала: источник об этом просто ничего не узнает! Сигналы могут быть посланы:
- одним процессом другому: в этом случае используется системный вызов kill ();
- ядром процессу (для того, чтобы указать, например, на фатальную ошибку, вызывающую разрушение процесса).
Параметры обработки, принятые по умолчанию при получении сигнала, можно изменить с помощью специального handler (обра- ботчика). Handler связывается с сигналом посредством вызова signal (). Точно также, сигналы можно игнорировать, используя:

signal (NOMSIGNAL, SIG_IGN);

Вызов kill () позволяет послать сигнал процессу (по его PID) или группе процессов (по ее PID).

ПРОГРАММА 4 /*Пpимеp упpавления сигналом SIGINT*/ #include <signal.h>

/*обpаботка связанная с сигналом пpеpывания SIGINT */ tint() { /*вывод на экpан и выход */ printf("signal d'interruption recu\n"); exit(1); }

main() { /*устанавливается обpаботчик сигнала SIGINT */ signal(SIGINT, tint); /*ожидание возможного пpеpывания */ sleep(100); exit(0); }

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

ПРОГРАММА 5

/*Пpимеp блокиpования сигнала*/ #include <stdio.h>

/*глобальная пеpеменная, значение котоpой меняется пpи появлении SIGINT */ int flag = 0;

/*обpаботчик SIGINT */ tint() { /*вывод сообщения и установка флага */ printf("signal d'interruption recu\n"); flag = 1; }

main() { /*блокиpовка SIGQUIT и SIGINT пpи входе в кpитическую область */ #ifdef BSD /*обpаботчик BSD */ int oldmask; oldmask = sigblock(sigmask(SIGQUIT)|sigmask(SIGINT)); /*кpитическая область */ .............. sigsetmask(oldmask); #endif

#ifdef SYS5 /*обpаботчик SYSTEM V */ sighold(SIGQUIT); sighold(SIGINT); /*кpитическая область */ .............. sigrelse(SIGQUIT); sigrelse(SIGINT); #endif

/*безопасное ожидание сигнала SIGINT, устанавливающего флаг flag */ /*установка обpаботчика, связанного с SIGINT */ signal(SIGINT, tint);

#ifdef BSD /*обpаботчик BSD */ sigblock(sigmask(SIGINT)); while (flag == 0) sigpause(0); /*обpаботка сигнала */ .............. #endif

#ifdef SYS5 /*обpаботчик SYSTEM V */ sighold(SIGINT)); while (flag == 0) sigpause(SIGINT); /*обpаботка сигнала */ .............. #endif

exit(0); }



Сигналы: особые случаи


- Сигнал SIGCLD (или SIGCHLD)

Этот сигнал непосредственно связан с понятием процесса "зомби". Во избежание перехода процесса в это состояние, необходимо правильно управлять сигналом SIGCHLD в порождающем процессе. Пример эскизной схемы обработки:

ПРОГРАММА 6 /*обpаботка сигнала SIGHLD*/ #include <signal.h>

#ifdef BSD #include

/*обpаботка BSD, связанного с сигналом SIFHLD */ tsigchld() { union wait status; while (wait3(&status, WNOHANG, 0) > 0) continue; } #endif

main() { #ifdef BSD /*BSD : установка хэндлеpа, связанного с SIGHLD */ signal(SIGCHLD, tsigchld); /*ожидание : если сигнал SIGHLD появится во вpемя запpоса на ожидание (read,accept,write ...), следует игноpиpовать ошибку, если errno=EINTR и веpнуться к пpеpванному обpащению к системе */ #endif

#ifdef SYS5 /*System V : сигнал SIGHLD игноpиpуется */ signal(SIGCLD, SIG_IGN); #endif }

- Сигнал SIGALARM

Этот сигнал служит для реализации временных задержек (timeouts). Продолжительность задержки задается примитивом alarm () или setitimer (). По истечении заданного времени процессу посылается сигнал SIGALARM.

ПРОГРАММА 7 /*Использование сигнала SIGALRM для упpавления задеpжкой */ #include <signal.h>

/*обpаботчик сигнала SIGALRM */ tsigalrm() { printf("timeout lecture\n"); exit(1); }

main() { char buf[80]; /*буфеp */ int nboct; /*установка хэндлеpа, связанного с SIGALRM */ signal(SIGALRM, tsigalrm); /*цикл чтения символов или выход по тайм-аут*/ for (;;) { /*установка вpеменной задеpжки в 10 секунд */ alarm(10); nboct = read(1, buf, sizeof(buf)); buf[nboct] = '\0'; printf("buffer recu %s \n", buf); } }

- Сигнал SIGIO (или SIGPOLL)

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



Символьные шрифты


Шрифт описывает размер и форму группы символов. Описание шрифта хранится в файле и загружается, по мере необходимости, сервером. В X11R5 было разработано сервисное средство "X Font Service", которое позволяет снять с сервера X обязанность управлять шрифтами и переложить ее на сервер шрифтов.



Система виртуальных файлов


RFS использует систему управления виртуальными файлами,которая приводится в соответствие ("mapping") с системой управления реальными файлами, в случае, если файл является локальным. Эта система управления виртуальными файлами встраивается в ядро UNIX (рисунок 7.2.)



Службы и номера портов


Выше транспортного уровня (TCP или UDP) находится уровень обслуживания. Каждая служба идентифицируется номером, называе- мом номером порта. Этот номер кодируется целым числом. Существуют виды обслуживания, использующие известные порты, одинаковые на всех машинах и определяемые в файле /etc/services. Службе может присваиваться номер порта, при условии, что номер не находится в пределах от 1 до 1023 (диапазон, закреплен- ный за системным сервисом) и не является уже присвоенным другой службе. Служба также может попросить систему присвоить ей номер порта; речь идет о временном номере, который будет использоваться только в течении срока жизни службы.



Сокеты и интерфейс TLI


Сокеты представляют собой интерфейс, позволяющий вести обмен данными между процессами, независимо от того, протекают они на одной или нескольких машинах. Такое двойное применение возможно вследствие того, что при создании сокета указывается область AF_UNIX (локальная) или AF _INET (сетевая). Кроме того, можно использовать AF_INET в локальном режиме: в этом случае, данные посредством уровней TCP/ IP передаются на драйвер loopback, который их немедленно отсылает в обратном направлении (не передавая в сеть).

TLI: Transport Level Interface (или XTI: X/Open Transport Interface) представляет собой транспортный интерфейс между двумя машинами, включенными в сеть. Однако, как и для сокетов, применение этого интерфейса можно расширить и для двух UNIX-процессов одной и той же машины. В следующих главах мы изучим эти два механизма более детально.



СОПОСТАВЛЕНИЕ TLI И СОКЕТОВ


Таблица 5.4. устанавливает соответствие между программами TLI и примитивами сокетов (по [AAT&T 90]). Вызовы TLI покрывают все вызовы сокетов. Таким образом, мож- но эмулировать сокет-интерфейс с библиотекой TLI. Необходимо, однако, отметить, что некоторые примитивы, ука-занные в таблице вместе, функционируют по-разному: например, listen () и t_listen, accept () и t_accept ()...


Таблица 5.4. Примитивы TLI и сокетов.

TLI Сокеты Описание
t_open socket Возвращает дескриптор
t_bind bind Связывает имя с десктиптором
t_optmgmt setsocket Выставляет опции транспорта
t_unbind Уничтожает точку доступа транспорта
t_close close Уничтожает ресурсы, связанные с точкой доступа транспорта
t_getinfo getsockpoint Возвращает информацию о транспорте
t_getstate ioctl,fcntl,stat Возвращает сосотояние точки доступа траспорта
t_alloc

t_free

Выделяет или освобождает память
t_look oictl Читает событие, связанное с точкой доступа транспорта
t_error perror Выдает сообщение об ошибке в незалодированном виде
t_connect connect Устпнвливает соединение с удаленным устройством
t_listen listen Установка в состояние ожидания запросов на соединение
t_accept accept Согласие на на входящее соединение
t_snd

putmsg

send

sendto

sendmsg

Запись данных в режиме соединения
t_rcv

getmsg

recv

recvfrom

recvmsg

Чтение данных в режиме соединения
t_snudata sendto

sendmsg

Запись данных в режиме отсутствия соединения
t_rcvudata recvfrom

recvmsg

Чтение данных в режиме отсутствия соединения
read

write

read

write

Чтение и запись данных в режиме соединения.Для TLI необходимо

ввести модуль tirdwr в STREAM

t_snddis

t_rcvdis

Отсоедтняет точку доступа транспорта
t_sndrel

t_rcvrel

shutdown Освобождает точку доступа транспорта

Следует отметить, что имеется большое сходство в использовании сокетов и библиотеки TLI, причем сложность TLI несколько больше, вследствие ее большей ориентированности на требования стандартов транспортной службы ISO.
В System V Release 4 услуги, оказываемые сокетами, реализуются в форме библиотеки-надстройки над STREAMS. Системные вызовы сокетов системы BSD становятся здесь библиотечными программами. Отсюда некоторые семантические различия, описанные в документации AT&T, которые, при небрежной обработке, рискуют вызвать сбои при переносе программы с одной системы на другую.



Создание и разрушение процессов


Единственным способом создания процесса в системе UNIX является использование системного вызова fork (). Функционально, новый или порожденный процесс создается посредством копирования выполняемого или порождающего процесса. Порожденный процесс наследует большую часть атрибутов своего родителя, и в частности, открытые файлы (порожденный процесс обладает копией дескрипторов файлов, открытых его родителем). Особый процесс init является общим предком по отношению ко всем остальным. Он инициализирует совокупность администраторов системы (демонов) и связывает процесс с каждым терминалом. Системный вызов exec () позволяет запустить выполнение новой программы в рамках текущего процесса. Порождающий процесс может быть синхронизирован с порожденными благодаря функции wait (). Последние сигнализируют о своем окончании посредством обращения exit ().
Если порожденный процесс заканчивается раньше своего родителя, обработка данных производится следующим образом:
- если процесс-родитель находился в состоянии ожидания, обеспеченного функцией wait (), он выходит из этого состояния;
- если родителем не была задействована функция типа wait (), порожденный процесс остается в состоянии "зомби", т.е. он возвращает все ресурсы, приданные ему системой, но сохраняет свое местонахождение в таблице процессов до подачи сигнала о его окончании.
Если порождающий процесс закончился раньше своих преемников, приемным родителем последних становится процесс init ().

ПРОГРАММА 2 /*Пpимеp pаботы функции fork()*/ #include <stdio.h>

main() { /*создание (fork) пpоцесса */ switch (fork()) { case 0 : /*поpожденный пpоцесс */ printf(" fils pid %d ppid %d \n", getpid(), getppid()); exit(0); case -1 : /*ошибка*/ exit(1); default : /*поpождающий пpоцесс */ printf(" pere pid %d ppid %d \n", getpid(), getppid()); /*поpождающий пpоцесс ждет, пока не закончится поpожденный */ wait() ; exit(0); } }



СРАВНЕНИЕ С NFS


В таблице 7.1. сравниваются характеристики NFS и RFS в соответствии с некоторыми критериями. Обозначения :
+ : означает, что продукт превосходит своего конкурента
= : означает, что оба продукта одинаково хороши или плохи
- : означает, что продукт хуже своего конкурента

Таблица 7.1 Сравнение NFS и RFS

RFS NFS
Управление удаленными
файлами
= =
Управление удаленными
периферийными устройствами
+ -
Управление именованными
каналами
+ -
Экспортирование смонтированных
ресурсов
+ -
Семантика UNIX + -
Опция монтирования - +
Поддержка символических
связей
- +
Автомонтирование - +
Восстановление в случае сбоя = =
Механизмы обеспечения безопасности + -
Обозначение ресурсов + -
Поддержка бездисковых станций - +
Производительность - +
Совместимость с не UNIX системами - +
Доступность - +

Уточним, что :
- управление одновременным доступом возможно в NFS при активации следящей программы lockd ;
- что касается символических связей : нельзя символически связаться с файлом, принадлежащим разделу, смонтированному в RFS ;
- в случае аварии сервера, RFS делает несколько попыток восстановить связь с сервером, а затем снимает запрос.
При этом отслеживающая программа rfudaemon активирует командный файл rfuadmin, который выдает на консоль сообщение (resource has been disconnected), а затем пытается демонтировать ресурс и смонтировать его в фоновом режиме. Этот механизм является довольно сложным и в некоторых реализациях функционирует неправильно. Что касается производительности, то следующие результаты (таблица 7.2.) были получены с помощью двух станций Sun для операций чтения и записи в одной и той же файловой системе.

Таблица 7.2 Сравнение производительности NFS и RFS

NFS RFS
Скорость при чтении(K/сек) между 300 и 600 между 110 и 150
Скорость при записи(К/сек) между 80 и 90 между 112 и 150

Отсюда следует, что RFS имеет почти одинаковую скорость при чтении и при записи (в среднем 140 К/сек) - и ее производительность несколько выше, чем производительность NFS при записи, но значительно ниже, чем производительность RFS при чтении. В любом случае, RFS может служить хорошим дополнением к NFS при разделении периферийных устройств и именованных каналов -поскольку эти два продукта вполне могут мирно сосуществовать. В ОС UNIX System V Release 4, административные команды и файлы NFS и RFS приведены "к общему знаменателю" :
- команда share позволяет разделять ресурсы (заменяет adv и exportfs). Ассоциированным с ней файлом является файл /etc/ dfs/dfstab ;
- команда mount используется с опцией -F - для указания, что речь идет о монтировании NFS или RFS. Ассоциированным фай- лом является файл /etc/vfstab ;
- команды showmount и nsquery заменены на dfshares и dfmounts.



Средства выражения


Сокращения расшифровываются только в первый раз, когда они встречаются в тексте; в конце книги они собраны в словарик. Основные термины и важные понятия при первом появлении выделяются курсивом . Имена команд, процедур, системные вызовы и имена файлов печатаются разными шрифтами ( что в русском тексте передать не представляется возможным прим. перев.). В примерах использован тот же шрифт. Основные элементы выделены жирным шрифтом. Если примеры приведены не полностью, отсутствующие команды заменены многоточием.В начале командной строки ставится значок #: #commande

Отдельные английские термины сохранены (например: buffer, flag, socket)(при этом многие из них практически без изменений вошли в русский язык - прим. перев.). Значение многих из них дается в словарике в конце книги.



Стандартный поток ввода-вывода


Речь идет о потоке, который позволяет записывать данные XDR в файл или считывать их из файла. Применение фильтра преобразования вызывает чтение из файла или запись в файл.

Этот тип потока создается с помощью программы xdrstdio_create() :
void xdrstdio_create(xdr_handle, op)
XDR *xdr_handle; /* handle */
FILE *file ; /* Указатель на открытый файл */
enum xdr_op op; /* XDR_ENCODE или XDR_DECODE */

Надо распределить память под handle XDR - т.е. определить переменную типа XDR. Этот тип потока является однонаправленным. Обратите внимание на то,что сообщение об ошибке не выдается и, следовательно, надо позаботиться о правильности параметров. Этот тип потока можно использовать для чтения или записи двоичных данных через NFS.

ПРОГРАММА 46

/*Использование файла /tmp/fixdr для межпpоцессоpного обмена целым и вещественным значением */

/*файл сlient.c */ /*кодиpование целого и вещественного значения */ #include <stdio.h> #include <rpc/rpc.h> #define FIC "/tmp/fixdr"

main () { FILE *fp; /*указатель файла */ XDR xdrs; /*дескpиптоp XDR */ long val1=10; /*целое */ float val2=4.456789; /*с плавающей точкой */ /*откpытие файла на запись */ fp = fopen(FIC, "w"); /* создание потока XDR для кодиpования */ xdrstdio_create(&xdrs, fp, XDR_ENCODE); /*запись целого */ xdr_long(&xdrs, &val1); /*запись числа с плавающей точкой */ xdr_float(&xdrs, &val2); close(fp); exit(0); }

/*файл serveur.c */ /*декодиpование целого числа и числа с плавающей точкой */ #include <stdio.h> #include <rpc/rpc.h> #define FIC "/tmp/fixdr"

main()

{ FILE *fp; /*указатель файла */ XDR xdrs; /*дескpиптоp XDR */ long val1; /*целое */ float val2; /*с плавающей точкой */

/*откpытие файла на считывание */ fp = fopen(FIC, "r"); /*создание потока XDR для декодиpования */ xdrstdio_create(&xdrs, fp, XDR_DECODE); /*считывание целого числа */ xdr_long(&xdrs, &val1); /*считывание числа с плавающей точкой */ xdr_float(&xdrs, &val2); close(fp); exit(0); }



STREAMS


Примечание: далее по тексту мы исходим из следующего:
- "STREAMS" обозначает совокупность механизмов;
- "Stream" обозначает частный случай операции, реализованной с помощью STREAMS.



Суперсервер inetd


Речь идет о демоне (следящей программе), активизирующем нуж- ную сервисную программу; он управляется файлом конфигурации /etc/inetd.conf. Этот файл содержит, в частности:
- название службы;
- используемый транспортный протокол;
- имя исполняемого файла.
В главе 4 "Сокеты" мы увидим, как работает этот демон.



Суперсервер Internet


Суперсервер Internet (демон inetd) находит список серверов в файле /etc/inetd.conf и номера портов в файле /etc/services. Cуперсервер мультиплексирует запросы от клиентов посредством функции select (). Для каждой запрашиваемой услуги создается порожденный процесс, реализующий ее, в то время как суперсервер возвращается к select () для обработки следующего запроса (рис. 4.6.).

Рис. 4.6. Функционирование демона inetd (по [STEVENS 90])
Для того, чтобы добавить сервер, исполняемый посредством демона inetd, необходимо осуществить следующие операции:
- вывести из программы примитивы, реализованные в демоне inetd: socket (), bind (), listen (), accept (). Использовать операции считывания и записи с дескрипторами 0 и 1 (вместо сокет-дескриптора). Выйти из программы посредством exit () после завершения сервисной программы.
- составить конфигурацию сервисной программы в файлах /etc/services и /etc/inetd.conf;
- уничтожить демона inetd;
- вновь запустить демона inetd.



Сведения из истории UNIX


Системы UNIX были разработаны на основе двух источников: UNIX System V и BSD (Berkeley Software Distribution). В 1983 году появилась первая версия UNIX System V, разработанная в лабораториях AT&T. В настоящее время наиболее распространенными ее версиями являются System V Release 3.2 (появилась в 1988 г.) и System V Release 4.0 (начало 1990 г.). Первая версия BSD была разработана в 1986 году в лабораториях Университета Беркли, штат Калифорния. В настоящее время используется версия BSD 4.3 (распространяется с 1988 г.). Версия 4.4 находится в стадии разработки. Большинство разработчиков использовали в качестве исходной системы System V (Hewlett Packard, Bull, IBM), добавив то, что называют расширениями BSD. Разработчики систем UNIX для микро- компьютеров, в частности, SCO (Santa Cruz Operation) и Interactive, также предлагают системы UNIX на базе System V. Операционная система SunOS фирмы Sun, в свою очередь, также является производной от BSD с расширениями System V. Однако, фирма Sun объявила о своем стремлении перейти к SVR4 (System V Release 4).

В 1988 году большинство разработчиков объединились в рамках фонда OSF (Open Software Foundation), с целью создания новой, независимой от AT&T, системы UNIX. Фирмы AT&T и Sun объединились в рамках UI (UNIX International) для того, чтобы содействовать продвижению SVR4 на компьютерный рынок. AT&T создала свой филиал USL (UNIX System Laboratories), в задачи которого входит разработка и распространение систем UNIX. Органы, занимающиеся стандартизацией систем UNIX:
* IEEE (Институт инженеров по электротехнике и радиоэлектронике) определяет группу стандартов POSIX;
* X/Open - международная группа поставщиков UNIX, занимающаяся проблемами мобильности.



Связь между клиентом и сервером


Клиент и сервер явно указывают используемый протокол. Сервер связывает свою службу с адресом ("binding" или связывание), затем переходит в состояние ожидания запросов от клиентов. Клиент адресует свои запросы, включая в них идентификатор сервера (сетевой адрес машины, где находится сервер и программный селектор, уникальным образом идентифицирующий сервер). Считывание и запись данных, при использовании режима логического соединения, осуществляются через буфер (см. предыдущую главу). Кроме того, можно осуществить запись экспресс-данных. Как правило, программа из библиотеки TLI включает один или несколько системных вызовов, относящихся к STREAMS и к типу используемого протокола (TCP,IP...).



Связывание


Связь между двумя службами TCP/IP, размещенными на двух машинах (machi 1 и machi 2), производится по пятиэлементной схеме.

где:
- трансп.протокол: TCP или UDP - адрес 1 : адрес Internet machi 1 - ном.порта 1 : номер сервисного порта на machi 1 - адрес 2 : адрес Internet machi 2 - ном.порта 2 : номер сервисного порта на machi 2
Такая пятиэлементная схема называется связыванием. Следовательно, для установления связи с удаленной машиной необходимо:
- присвоить номер порта (или предоставить это сделать системе);
- определить адрес Internet машины, с которой должен осуществляться обмен данными;
- знать или определить сервисный порт службы. В следующих главах мы увидим, какими средствами выполняются эти операции.



TCP/IP


TCP/IP является плодом проекта DAPRA (Управление перспективных исследований и разработок) Министерства обороны США. Созданный в 1980 году, он был затем интегрирован в версию BSD 4.2. Впоследствии были разработаны версии TCP/IP для System V,права на которые приобрела фирма AT&T. В настоящее время TCP/IP входит в число основных сервисных программ систем UNIX, независимо от машин на которых он применяется.



Терминология


Трудно провести различие между терминами "распределенный", "разделенный" и "совместный".

- данные и обработка являются "распределенными" или "разделенными", то есть, выполнение операции требует использования нескольких процессоров;

- термин "совместный" (cooperatif) является более специфическим: диалог между двумя прикладными системами с целью осуществления некой задачи.

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



TLI и транспортные службы


TLI вводит два понятия:
- transport endpoint (точка доступа транспортной службы): точка доступа в канал связи между транспортным уровнем и пользовательским процессом;
- transport provider ("поставщик" транспортных служб): протокольный элемент уровня 4 в системе OSI, который предоставляет услуги, с которыми сопрягается TLI. На рис. 5.2. представлены взаимоотношения транспортной службы и пользовательского процесса:
- пользовательский процесс посылает запросы транспортной службе через интерфейс;
- транспортная служба подтверждает интерфейсу получение событий (данных для считывания...).

Рис. 5.2. Взаимоотношения поставщика и пользователя транспортных услуг.



Транспортный уровень TCP (Transmission Control Protocol)


TCP (Transmission Control Protocol) представляет собой на- дежное средство передачи данных, с установлением виртуального соединения, позволяющее вести обмен потоками байтов. Он устанавливает и поддерживает связь между двумя системами, проверяет заданную последовательность принимаемых и передаваемых бло- ков, обеспечивает контроль потока. Предусмотрено применение TCP в случае фатальной ошибки при передаче данных (см. главу 4 "Сокеты").



Транспортный уровень UDP (User Datagram Protocol)


UDP (User Datagram Protocol) представляет собой средство пе- редачи данных, без установления виртуального соединения, ненадежное, позволяющее вести обмен сообщениями (дейтаграммами). UDP не предусматривает предупреждений в случае ошибки при передаче. Следовательно, требуется проверка правильности вы- полнения операций.



Управление ошибками


Как уже было указано, сигналы об ошибках реализуются следую- щим образом:
- read () возвращает ноль, если удаленный процесс разрушен или -1, если прервана связь по сети;
- write () вызывает посылку сигнала SIGPIPE, если удаленный процесс разрушен или если связь по сети прервана.
Для проверки работоспособности удаленного процесса можно добавить управление с помощью реле времени (темпоризатора) по вызовам read () или write (). Возможно, что read () никогда не кончит свою работу, если удаленная программа зациклилась, или что вызов займет слишком много времени, если удаленная машина сильно загружена. Возможен аварийный останов программы-клиента. Сервер в этом случае должен контролировать присутствие клиента. Следует установить опцию SO_KEEPALIVE, которую мы рассмотрим в разделе об определении параметров сокетов. Дополнительный контроль можно осуществить следующим образом: сервер периодически осу-ществляет запись байта в контрольный сокет. Таким образом, если клиента больше нет, будет обнаружена ошибка SIGPIPE и сер-вер остановится. Пример использования этого механизма приведен в главе 10 "RPC".

ПРОГРАММА 26 /* Управление сигналом SIGPIPE с помощью опции SO_KEEPALIVE и временной задержкой ****************/

#include "soct.h" #include <signal.h>

/* хэндлер сигнала SIGPIPE */ tsigpipe() { err_quit("SIGPIPE recu \n"); }

/* хэндлер сигнала SIGALRM */ tsigalarm() { err_quit("time out sur lecture ou ecriture \n"); } main() { int sock; /* дескриптор сокета */ int optval; /* значение опции */ int optlen; /* длина optval */ /* установка опции SO_KEEPALIVE */ optlen = sizeof(optval); optval = 1; setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); /* установка обработчика сигнала SIGPIPE */ signal(SIGPIPE, tsigpipe); /* установка обработчика сигнала SIGALRM */ signal(SIGALRM, tsigalarm); }



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



Управление памятью


Процесс, декодирующий данные композитного или сложного типа, вообще говоря, не знает, сколько памяти потребуется для хранения декодированных данных. Например, размер символьной строки не всегда можно определить априори. Одно из возможных решений, в этом случае - это завести большой буфер. Другое, более элегантное решение, заключается в том, чтобы предоставить распределение памяти XDR. Для этого, надо передать фильтру XDR указатель на декодируемый объект со значением NULL. После использования объекта, декодированного XDR, надо освободить память, распределенную XDR. Эта операция выполняется с помощью функции xdr_free() :

void xdr_free(proc, objp)
xdrproc_t proc; /* процедура, которая реализует фильтр */
char *objp; /* указатель на декодированный объект */

ПРОГРАММА 49

/*Декодиpование символьной стpоки и использование xdr_free() для освобождения pаспpеделенной памяти */

#include <stdio.h> #include <rpc/rpc.h> #define FIC "/tmp/fixdr" #define MAXL 1024 /*максимальная длина стpоки */

main() {

FILE *fp; /*указатель файла */ XDR xdrs; /*дескpиптоp XDR */ char *objp; /*указатель на pаскодиpованную стpоку */ /*откpытие файла */ fp = fopen(FIC,"r"); /*создание потока XDR для декодиpования */ xdrstdio_create(&xdrs, fp, XDR_DECODE); /*считывание стpоки - пеpедается указатель NULL потому, что длина стpоки pезультата неизвестна */ objp = NULL; retour - xdr_string(&xdrs, &objp, MAXL); /*используется стpока, адpесуемая objp */ ................................

/*затем освобождается память, pаспpеделенная XDR */ xdr_free(xdr_string, &objp); close(fp); exit(0); }



Управление ресурсами


Сервер X Window управляет ресурсами (окнами, графическим контекстом, символьными шрифтами,таблицами цветов ...),каждый из которых адресуется уникальным идентификатором (32-битовое целое) и является разделяемым между процессами-клиентами. Если только при их создании не было указано противное, окна-дети наследуют ресурсы окон-родителей. Для того,чтобы прикладная программа-клиент могла использовать ресурс, управляемый сервером, или созданный другой прикладной программой, следует сохранить идентификатор ресурса с помощью механизмов обращения к серверу.



Управление сигналами


С сокетами связаны 3 сигнала:
- SIGIO: указывает на то, что сокет готов к асинхронному вводу-выводу. Сигнал посылается процессу или группе про-цессов, связанных с сигналом;
- SIGURG: указывает на то, что на сокете получены экспресс-данные. Посылается процессу или группе процессов, связанных с сигналом;
- SIGPIPE: указывает на то, что запись на сокете более невозможна. Посылается процессу, связанному с сокетом. Более подробно использование данных сигналов объясняется в нижеследующих разделах.



Установка клиентов


Для инициализации машин-клиентов области надо выполнить ту же процедуру, что и для инициализации вторичного сервера имен. Далее, перед использованием ресурса необходимо "смонтировать" этот ресурс на машине-клиенте. Для того,чтоюы получить список разделяемых ресурсов области, надо ввести команду : #nsquery Выбрав разделяемые ресурсы :
- выберите каталог, который будет являться точкой монтирования для распределенного ресурса
- выполните команду mount, формат которой описан ниже : #mount [-r] -d ресурс точка_монтирования где :
- -r : ресурс используется только в режиме чтения
- -d ресурс : указывает на монтирование RFS и задает имя монтируемого ресурса
- точка_монтирования : полный путь монтируемого катало-га.

В нашем примере, если машина ordinnb собирается использовать все разделяемые ресурсы области, администратор этой машины должен выполнить приведенные ниже команды : #cd /dev #mkdir rdev #mount -d PERIFS /dev/rdev #mount -r -d MANUEL /usr/share/man Если все пройдет нормально, команда mount выдаст на экран : MANUEL on /usr/share/man type rfs (ro) PERIFS on /dev/rdev type rfs



Установка первичного сервера имен


После определения области следует создать файл rfmaster. Этот файл должен находиться в каталоге /usr/nserve машины-сервера области.

Этот файл определяет первичный и вторичный сервер имен. Он содержит по две строки на каждую машину :

Область Тип Область.машина Область.машина А АдресIP где :
Область - имя области
Тип - Тип, который может принимать следующие значения :
P - для первичного сервера
S - для вторичного сервера
Область.машина - имя области, за которым следует имя машины (первичного или вторичного сервера)
АдресIP - адрес IP машины, в шестнадцатиричном виде. Формат адреса приведен ниже.Адрес начинается со следующих символов : \x00021450. Эти символы обозначают семейство IP (0002) и порт 1450 - стандартный порт для RFS. Для того, чтобы конвертировать адрес IP в формат файла rfmaster, следует использовать коман- ду hostrfs. Файл rfmaster области rfstpt выглядит следующим образом :

rfstpt P rfstpt.ordinfm rfstpt.ordinfm A \x0002145089322C58000000000000000 rfstpt S rfstpt.ordinan rfstpt.ordinfm A \x0002145089323E46000000000000000 При этом RFS можно запустить с помощью следующих команд :

#dorfs init имя_области tcp [N_порта]
#dorfs start
Команду dorfs init не следует запускать более одного раза на одной машине с одним и тем же именем области. Таким образом, для того, чтобы организовать область rfstpt, надо ввести следующие команды :
#dorfs init rfstpt tcp
#dorfs start

Рис. 7.3. - Область rfstpt (все машины являются также клиентами)

1 - Монтирует каталог man и кассетный считыватель
2 - клиент ordinfm
3 - Первичны сервер имен ordinfm
4 - Экспортирует кассетный считыватель
5 - сервер ordinnb
6 - Экспортирует каталог man
7 - сервер и вторичный сервер имен ordinan



Установка серверов


Скопировав файл rfmaster с первичного сервера, объявите те локальные ресурсы машины, которые она будет разделять с машинами области. Перед выполнением этой операции необходимо присвоить стандартные права доступа этим ресурсам путем редактирования фай-лов auth.info/uid.rules и auth.info/gid.rules. В приведенном здесь примере можно действовать также, как это принято в NFS - т.е. присвоить каждому пользователю одинаковые UID и GID на всех машинах области : global default transparent Далее, можно инициализировать и запустить RFS - также, как и на вторичном сервере. Осталось объявить разделяемые ресурсы машины с помощью команды adv. Экпорт ресурсов осуществляется следующим образом : #adv [-d "описание"] имя_ресурса путь где :
- описание : общее описание ресурса - в свободном формате
- имя_ресурса : имя, присваиваемое ресурсу и используемое машинами-клиентами - путь - полный путь (от корневого каталога) разделяемого каталога.

Таким образом, для того, чтобы разделить manuel машины ordinan, следует ввести команду : #adv -d "Руководство по OS 4.1" MANUEL /usr/share/man Для того,чтобы разделить периферийное устройство, ленточный или дисковый накопитель, надо выполнить несколько дополнительных команд :
1) создать каталог rdev в каталоге /dev #mkdir rdev
2) создать связь с локальным периферийным устройством (в нашем случае с накопителем на кассетах) : #cd /dev/rdev #ln /dev/rst8 rst8
3) объявить каталог /dev/rdev разделяемым : #adv -d "периферийные устройства" имя /dev/rdev Таким образом, чтобы разделить периферийные устройства машины ordinb, надо ввести следующую команду : #adv -d "периферийные устройства" PERIFS /dev/rdev Обратите внимание на то,что экспортируется каталог,содержа- щий "устройства" ("devices") разделяемых периферийных устройств (peripheriques), а не сами "устройства" ("devices").



Установка вторичного сервера имен


Убедившись, что на данной машине доcтупна RFS, выполните следующие операции :
- скопируйте файл rfmaster с сервера области
- инициализируйте RFS командой : #dorfs init "имя области" tcp [N порта]
- запустите RFS командой : #dorfs start

Таким образом, в нашем примере Вы должны выполнить команды : #dorfs init rfstpt tcp #dorfs start



Виртуальные файловые системы


Прозрачный доступ к файловым системам различных типов (UNIX,DOS ...) реализован посредством встроенного в ядро интерфейса VFS : Virtual File System (Рисунок 6.2). Каждой смонтированной файловой системе соответствует в ядре своя структура VFS. В то время,как файловые системы UNIX манипулируют элементами,называемыми inode (index node - индексные узел - индексный дескритор файла), NFS использует понятие vnode (virtual node). Речь идет о,в некотором смысле, о виртуальном inode, которому соответствует либо локальный физический inode либо удаленный физический inode (или еще что-то,реализующее управление файлами на не UNIX машинах).

NFS, таким образом, использует систему управления виртуальными файлами,которая приводится в соответствии ("mapping") с системой управления реальными файлами, в случае, если файл является локальным.



Включаемые файлы


Два нижеследующих файла являются включаемыми файлами для программировании сокетов в областях AF_INET и AF_UNIX.

ПРОГРАММА 21

/* файл soct.h для сокетов TCP/IP *******/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
/*специфическая опция прикладной программы*/
#define PORT 6315 /* задание номера порта */

ПРОГРАММА 22
/* файл socu.h для сокетов UNIX *******/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
/*специфическая опция прикладной программы*/
#define SPATH "/tmp/usocket" /* полное имя файла сокета */



Включаемый файл



Файл tli.h содержит файлы,обычно необходимые для кодирования программ TLI:

ПРОГРАММА 38 /* файл tli.h

#include <stdio.h> #include <tiuser.h> #include <fcntl.h> #include <sys/types.h> #include <sys/conf.h> #include <stropts.h>



Внешний вид и внутреннее состояние


X Window обеспечивает только основные механизмы, позволяющие разрабатывать прикладные программы. Эта система гарантирует, что внутреннее состояние ("feel") прикладной программы будет одним и тем же,независимо от того, какая именно система используется. Наоборот, внешний вид ("look") зависит от реализации прикладных программ,использующих X Window. Например, если окно будет закрыто, X гарантирует, что связанная с этим окном прикладная программа, получит одно и то же сообщение об этом событии (и будет иметь одно и то же внутренне состояние), не- зависимо от того, как именно закрытие окна будет выглядеть на экране. Внешний вид экрана относится к компетенции Window Manager'а, который является клиентом X и который может быть своим для каждого разработчика.



Возможные проблемы


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

- Проблемы, возникающие при монтировании mount :
... server not responding
- диагностика : авария сервера или не функционирует NFS или авария сети.
- проверки :
- проверьте, не находится ли сервер в состоянии ожида- ния с помощью команды : #ping имя_сервера Вы должны получить сообщение имя_сервера alive или сообщение о правильной передаче.
- проверьте, функционирует ли на сервере NFS, с помо- щью команд : #ps -e (или ps -ax) Должны отозваться следящие программы portmap,rpc.mountd и nfsd ; #rpcinfo -p имя_сервера В списке должны оказаться nfs и mountd. mount :
|... permission denied
- диагностика : экспортируемые файлы не описаны или опи- саны неправильно
- проверки : проверьте файл /etc/exports сервера.
- Проблемы, возникающие при функционировании NFS server x not responding, still trying - диагностика и проверки : если вслед за этим сообщением появляется сообщение NFS server x OK - это значит, что запрос не получился, но был успешно повторен (если такие сообщения возникают часто, следует увеличить значение па- раметра монтирования timeo). В противном случае, с серве- ром или с сетью случилось что-то серьезное (см. проверки, описанные выше). Текущую операцию можно прервать, нажав на клавиши CONTROL C или CONTROL Z. RPC timeout - речь идет о такой же проблеме, как и та, которая описана выше - т.е. о неудавшемся запросе. Это сообщение означа- ет, что монтирование было выполнено с параметром soft и что запрос был прерван. Запрос следует повторить.
- Проблемы, связанные с производительностью
- диагностика : возможно, что плохо функционируют следящие программы biod. Возможно также, что сеть очень загружена или что процессор клиента или сервера занят.
- проверки : попробуйте снять и перезапустить следящие программы biod на рабочей станции : #ps -ax | grep biod # kill -9 pid1, pid2, pid3, pid4 #biod 4



вот цель этой книги, задуманной


100 % практики - вот цель этой книги, задуманной как помощь разработчикам и программистам в решении задач распределенной обработки научных данных в системе UNIX. Намеренно сосредоточившись на уровне средств, позволяющих обеспечить решение этих задач, данное пособие рассматривает только понятия и внутренние механизмы, необходимые для качественной реализации программ распределенной обработки данных. Авторы стремились создать не справочник, а вводное пособие, в котором значительная часть посвящена примерам. Распределенная обработка данных уже применяется во многих областях. Системы все в большей степени объединяются в сети; микро-компьютеры и рабочие станции постепенно вытесняют пассивные терминалы. Основной целью распределенной обработки данных является максимальное использование возможностей каждой машины. Пособие ограничивается услугами, имеющимися в области двух существующих стандартов: операционной системы UNIX и протоколов UDP/TCP/IP. Данное ограничение, однако, является не слишком строгим, так как и другие операционные системы и сетевые протоколы располагают подобными же средствами, в частности, протоколы OSI. В данном пособии вкратце напоминаются основные элементы систем UNIX и протоколов TCP/IP. Для понимания примеров необходимы хорошие навыки чтения программ, написанных на языке Си. Данная книга, в основном ориентирована на решение научных задач, хотя в ней и дается краткое описание средств распреде- ленной работы с базами данных. В конце каждой главы указана литература, где поднятые в кни- ге проблемы изложены более подробно; в конце книги данная ли- тература собрана в алфавитном порядке.


Минувшее десятилетие характеризовалось быстрым развитием персональной вычислительной техники, рождением мира рабочих станций и бурным развитием сетей, позволяющих осуществлять обмен информацией между компьютерами. ОС UNIX появилась как операционная система, используемая на рабочих станциях. Кроме того, эта система может использоваться практически на всех компьютерах, от микро-компьютеров типа PC (Personal Computer) и Macintosh до супер-ЭВМ семейств Cray и IBM. Успех UNIX повлек за собой успех TCP/IP в качестве протокола сетевого обмена. Несмотря на постепенное внедрение стандартизованных протоколов OSI, протокол TCP/IP все еще достаточно широко применяется, в том числе и при работе в отличных от UNIX операционных системах. После краткой исторической справки в данной главе изложены основные понятия системы UNIX и протоколов TCP/IP, необходимые для правильного понимания проблемы.



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



В данной главе речь идет о взаимодействиях между процессами UNIX, в рамках одной машины (рис. 3.1.), взаимодействиях, которые мы квалифицируем как "внутрисистемные" (intra-UNIX), в отличие от связывающих несколько систем UNIX, называемых "межсистемными" (inter-UNIX) (рис. 3.2.). Действительно, модель "клиент-сервер" может быть воплощена и в виде совокупности процессов на одной машине. Мы увидим также, как осуществляется запуск удаленного процесса - этап, предшествующий межсистемным взаимодействиям. IPC (InterPprocess Communications) внутри системы делятся на две группы:
- взаимодействия, применение которых сходно с применением файлов: программные каналы и именованные программные каналы;
- взаимодействия System V: файлы сообщений, семафоры и общая память.
Здесь мы даем только краткие сведения. Мы отсылаем читателя к многочисленным изданиям, которые подробно рассматривают этот вопрос. Реализацию этих IPC можно проиллюстрировать на примере службы типа "эхо строки символов" между процессом-клиентом и процессом-сервером. Этот пример будет рассмотрен и для межсистемных IPC. Читатель увидит, как реализуется одна и та же служба как для внутренних, так и для межсистемных взаимодействий.


Рис 3.1. Внутри системные взаимодействия

Рис 3.2. Межсистемные взаимодействия
Различают термин "буфер" (buffer) обозначающий область памяти, управляемую программами пользователей и термин "буфер" (tampon) обозначающий промежуточную область памяти, используемую службами ядра (буферы ввода-вывода, например). Процесс-клиент посылает серверу определенное количество буферов - buffers (содержащих символы) различного размера, а сервер посылает ему эти буфера обратно (эхо). Если необходимо, размер буферов пересылается серверу перед первой передачей. В примерах использованы отдельные функции управления ошибками, представленные в Приложении А.2. Сервер активизируется в режиме фоновой задачи:
#serveur &
Активизация клиента осуществляется в интерактивном режиме:



Версия BSD 4.2. системы UNIX была первой версией, в которой TCP/IP был включен в состав ядра операционной системы, и в которой был предложен программный интерфейс этого протокола : сокеты (sockets). Сокеты, таким образом, представляют собой API (Application Program Interface), то есть интерфейс между прикладными программами и сетевыми уровнями.



TLI ( Transport Level Interface) был введен в 1986 году фирмой AT&T вместе с системой UNIX System V Release 3.0. До TLI только сокеты могли предоставлять механизмы, необходимые для межпроцессных взаимодействий между удаленными машинами. TLI функционирует подобно сокетам, предоставляя доступ к виртуальной транспортной службе, способной адаптироваться к транспортным протоколам как OSI, так и TCP или UDP. TLI основан на применении нового системного механизма, также внедренного фирмой AT&T - STREAMS.



Созданная в 1985 году фирмой Sun, NFS (Network File System) стала фактическим стандартом для систем управления распределенными данными.
NFS создает общую файловую систему из файловых систем нес- кольких машин, соединенных в сеть, создавая при этом у пользо- вателя иллюзию, что он работает с одной файловой системой. Система NFS,часто используемая для связи нескольких машин в среде ОС UNIX и для включения в среду UNIX PC с помощью PC-NFS, доступна, кроме того,на машинах серий Cray,IBM,Dec ...



Созданная в 1986 году фирмой AT&T одновременно с ОС UNIX System V Release 3, система RFS (Remote File Sharing) предс- тавляет собой средство, предназначенное для управления распре- деленными файлами. Назначение системы - обеспечить прозрачное разделение дисковых ресурсов и периферийных устройств между машинами UNIX, объединенными в локальную сеть.
Несмотря на все усилия AT&T, RFS не получила большого расп- ространения. В настоящий момент эта система используется в SVR4 и SunOS.



Разработанный MIT в 1985 году, X Window (сокращенно X), все чаще и чаще заявляет о себе, как о стандарте для реализации графических интерфейсов. Речь идет о продукте freeware, который, таким образом, можно получить бесплатно. Нашей целью будет дать не полное и исчерпывающее описание X Window, а лишь описать те стороны этого продукта, которые имеют отношение к функциональным возможностям и механизмам реали- зации распределенных прикладных программ. Мы ограничимся опи- санием версии X11R4,и лишь упомянем о некоторых изменениях, внесенных в версию X11R5.
PHIGS (Programmer Hierarchical Interactive Graphic System) - был принят в качестве стандарта для трехмерной графики. PEX (PHIGS Extension to X Window) - должен позволять распределять прикладные программы, обрабатывающую трехмерную графику.



Несмотря на то, что большинство графических станций исполь ует для внутреннего представления данных формат IEEE, ОС UNIX в настоящее время настолько широко распространена, что пересылка двоичных данных между системами часто превращается в проблему.
Например, целое число на ЭВМ Cray состоит из 64 бит, в то время как на станциях Sun или HP целое число занимает 32 бита. Более того, даже при одинаковом двоичном представлении структуры языка Си могут быть выравнены поразному - в зависимости от компилятора архитектуры системы.
Система XDR (eXternal Data Representation), предложенная фирмой Sun одновременно с NFS, предлагает стандарт представления данных для обмена информацией между машинами разных типов.


Ввод-вывод


Для осуществления ввода-вывода в системах UNIX существуют два способа: - использование стандартной библиотеки Си: функции fopen (), fread (), fwrite ()... Примитив fopen () возвращает указатель объекта типа FILE (FILE pointer). Запись и считыва- ние, т.о. осуществляются в буферном режиме. - использование библиотеки UNIX: системные вызовы open (), read (), write ()... Примитив open () возвращает целое значение, называемое file descriptor (дескриптор файла). Shell присваивает номер 0 стандартному вводу, 1 - стандартному выводу и 2 - сообщению об ошибке (stderr). Последующие номера присваиваются по нарастающей, за исключением случаев, когда номер был освобожден процессом посредством вызова функции close () или exit (). Другие примитивы возвращают дескриптор файла: creat (), dup (), pipe (), socket () и fcntl (). Функция fdopen () устанавливает связь между стандартной библиотекой Си и библиотекой UNIX, связывая FILE pointer c file descriptor. Системные вызовы fcntl () и ioctl () позволяют изменять характеристики уже открытого файла: право доступа, блокировка считывания или записи. Ввод-вывод на диск осуществляется механизмом кэширования с предварительным считыванием и отсроченной записью. Кэшем называется участок памяти, отведенный под эти операции. Т.о., при записи, данные передаются непосредственно на диск только тогда, когда кэш переполнен или существует потребность в записи именно на диск (sync оператора или демона update). Вызов dup () позволяет присваивать файлу дополнительный file descriptor, а система гарантирует, что этот новый номер является наименьшим из имеющихся. Это позволяет переориентировать стандартный ввод-вывод и вывод сообщений об ошибках.

ПРОГРАММА 3 /*пpимеp пеpеиндексации вывода в файл */ #include <stdio.h>

#include <fcntl.h>

main() { int fd; /* дескpиптоp файла */ /* откpытие вpеменного файла с маской создания 666*/ fd = open ("/tmp/file", O_CREAT|O_RDWR, 0666); /*можно было бы сделать и пpоще : с помощью функции dup2,котоpая эквивалентна close()+dup() : dup2(1,fd), dup2(2,fd) */ close(1); close(2); dup(fd); dup(fd); /*запись идет в stdout и stderr; из-за пеpеиндексации запись идет в файл /tmp/file */ write(1, "ecriture stdout\n", 16); write(2, "ecriture stderr\n", 16); }



Взаимодействие между процессами с помощью именованного канала


RFS позволяет использовать именованный канал для организации взаимодействия между двумя удаленными процессами. Для этого достаточно, чтобы именованный канал был создан в каталоге, разделяемом с помощью RFS между двумя машинами. Если вспомнить функцию echo, приведенную в 3 главе в качестве примера использования именованных каналов, то ,в данном случае, для того, чтобы программа, использующая именованные каналы, могла работать в сети, достаточно изменить включаемый файл fif.h (/home/testrfs - это каталог, экспортируемый сервером и смонтированный клиентом) :

ПРОГРАММА 44
/*Файл fif.h **********************************************/
#include "commun.h"
#define nomfifo1 "home/testrfs/fifo1" /*имя fifo1 */
#define nomfifo2 "home/testrfs/fifo2" /*имя fifo2 */



Xlib


Xlib - это библиотека, которая позволяет разрабатывать прикладные программы (клиенты) X Window. Речь идет о API (Application Program Interface) довольно низкого уровня, управляющем основными механизмами : созданием окон, графическими примитивами, позиционированием атрибутов ... Xlib - это продукт freeware, являющийся фактическим стандартом (стандартизован ANSI) и обеспечивающий таким образом переносимость программ.

Прикладная программа Х, использующая Xlib выглядит следующим образом :

ПРОГРАММА 45

ourverture du display : /*откpытие дисплея в зависимости от имени дисплея, создает локальную или удаленную связь с сеpвеpом */

creation de fenetres dans le display : /*создание окон в дисплее pечь идет о под-окнах (окнах, содеpжащихся в дpугих окнах - по умолчанию, всегда существует хотя бы одно окно "root Window", покpывающее весь экpан), наследующих атpи- буты pодительских окон, если только явно не указано пpо- тивное */

reguete specifiant les evenements a attendre sur les fenetres ; /* запpос, специфициpующий ожидаемые в окнах события */

affichage des fenetres a l'ecran ; /*вывод окон на экpан */

attente d'evenements sur les fenetres ; /* ожидание событий в окнах */

traitements de l'evenement regu ; envoi eventuel de requetes au serveur ; /* обpаботка полученных событий : пpи необходимости, посылка запpосов сеpвеpу */

retour a l'attente d'evenements sur les fenetres. /* возвpат к состоянию ожидания в окнах */

Прикладная программа Х представляет собой, таким образом, бесконечный цикл ожидания событий, передаваемых сервером.



Загрузка сети


Эффективная загрузка сети зависит от того, каким именно об- разом используется NFS. "Классическое" использование - для компиляции, чтения или записи, сопровождаемых обработкой данных, в среднем загружают сеть мало. С другой стороны, последовательное чтение или запись больших файлов (что бывает редко) значительно загружает сеть.


Загрузка в сети разумеется зависит от типа прикладных программ. Вообще говоря, X Window почти не загружает сеть (интерактивные прикладные программы), за исключением отдельных моментов - при пересылке образов, например. То, что сервер сохраняет ресурсы, которые, таким образом, пересылаются только один раз, сильно уменьшает трафик сети. Для примера мы измерили загрузку в сети, порожденную прог- раммой, выполняющейся на компьютере Cray и непрерывно, при этом, отображающей векторные графические изображения на экране станции Sun 3/260. Программа на ЭВМ Cray рисует графику с помощью средств графической библиотеки PHIGS, являющейся надс- тройкой X Window. Трафик, при этом, составляет примерно 10 К/сек, причем длина пакета в сети Ethernet равна в среднем 400 байт.



Запросы и события


Механизмы реализации являются существенно асинхронными: клиент посылает запросы, которые помещаются в очередь на ожидание обработки сервера. Клиент ожидает событий от сервера ; события тоже помещаются в очередь (рисунок 8.1.). Кроме того, сервер может переслать сообщение об ошибке - в случае, если запрос невозможно выполнить. Запросы, получаемые сервером, сохраняются в буферах. Содер- жимое буфера передается серверу, если буфер полон, или если клиент ожидает событий. Клиент ожидает событий во всех окнах, в которых он явно запросил получение специфицированных им типов событий.

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

Рис. 8.1. - Принципиальная схема механизмов X Window.
1 - Сервер Х
- Управление ресурсами
- Обработка запросов
- Отправлений собщений о событиях
2 - Прикладная программа Клиент Х
- Посылка запросов
- Обработка сообщений
3 - Очередь ожидания запросов
4 - События
5 - Очередь ожидания сообщений
6 - Запросы
7 - Сокет
-Интерфейс
8 - Сетевой или локальный обмен данными

Существуют следующие возможности синхронизации между клиентом и сервером :
- некоторые запросы требуют ответа сервера : например, запрос о состоянии окна
- клиент может запросить, чтобы текущий буфер был передан серверу, с помощью функции XFlush(). Эта операция выполняется автоматически каждый раз, когда клиент переходит в состояние ожидания события - клиент может потребовать, чтобы запрос был передан серверу и выполнен им с помощью функции XSync().



Запуск и останов сервера клиентом


Если запуск сервера демоном inetd не предусмотрен, можно использовать функцию REXEC (), позволяющую осуществлять следующие операции:
- запустить сервер;
- восстановить в процессе-клиенте номер порта, присвоенного системой серверу. Для этого клиент читает номер из сокет-дескриптора, возвращенного rexec (). Сервер должен за-писать присвоенный номер порта в стандартный вывод ;
- остановить сервер посредством посылки некоторой информации, которую он интерпретирует соответствующим образом, или послать сигнал прерывания SIGINT на контрольный сокет. Пример показан в разделе 4.3.5. (выполнение удаленной процедуры).




Запуск сервера и клиентов


Сервер запускается с помощью командного файла, активирующего в то же самое время несколько клиентов (обычно Window Manager и несколько окон : xclock,xterm ...). Остальные клиенты запускаются с помощью меню или непосредственно в окне, эмулирующем терминал (с помощью прикладной программы xterm).

Атрибуты выполнения клиентов (цвет,символы ...) можно настроить одним из следующих способов :
- указав в качестве значения параметра при запуске клиента (только при вызове) ;
- изменив (что повлияет на всех пользователей) файл /usr/lib /X11/ app_defaults/имя_прикладной_программы
- изменив (что повлияет только на данного конкретного пользователя) файл $HOME/.Xdefaults.

Все эти механизмы управления атрибутами автоматически обрабатываются в прикладных программах при использовании функций XGetDefault() или XInitialise() библиотеки Xlib.



Запуск удаленного процесса


Появление здесь этого раздела может удивить. Мы видели, как запускается процесс-сервер на примере демона UNIX. На практике, постоянное функционирование процесса часто может оказаться неэффективным. Целесообразнее активизировать его только при передаче запроса процессом-клиентом. Демон inetd санкционирует этот режим работы, однако, необходимо обеспечить управление организацией услуг: для того, чтобы осуществить дополнительный ввод в файл конфигурации /etc/inetd.conf., необходимо быть привилегированным пользователем. Таким образом, может оказаться целесообразным запустить процесс-сервер с машины клиента, не прибегая к демону inetd. Для этого существуют четыре возможных варианта, которые и описаны ниже.

- Script
Используется дистанционная операционная команда rsh или remsh в командном файле (script).

- Примитив system ()
Этот примитив позволяет запускать команды и, в частности, запускать удаленные процессы посредством команды rsh или remsh. Программа system () вызывает запуск нового процесса, как если бы команда была применена пользователем в рамках shell.

ПРОГРАММА 18 /*запуск удаленного пpоцесса с помощью функции system() */

/*файл сlient.c **************************/ main() { char commande [50]; /*буфеp команд */ /*создается пpоцесс-сеpвеp с помощью обpащения к system(), скомбиниpованного с rsh */ sprintf(commande, "rsh ordinfm serveur"); system(commande); }

/*файл serveur.c **************************/ main() { /*симуляция активности*/ sleep(1000); }

- Примитив popen ()
Функционирует подобно system (). Кроме того, можно через созданный программный канал получить данные, посылаемые сервером. В главе 4 "Сокеты" мы увидим, что таким образом можно определить номер порта, присвоенного себе сервером.

ПРОГРАММА 19 /*Запуск удаленного пpоцесса с помощью функции popen() */

/*файл client.c **************************/ main() { char commande [50]; /*буфеp команд */ FILE *fp; /*указатель файла*/ char buf[80]; /*буфеp */ int retour; /*статус */ /*создается пpоцесс-сеpвеp с помощью обpащения к popen(), связанного с rsh() */ sprintf(commande, "rsh ordinfm serveur"); fp = popen(commande, "r"); /*считывается инфоpмация, записываемая сеpвеpом в файл fp */ retour = fread(buf, 1, sizeof(buf), fp); printf("info recue %s\n", buf); } /*файл serveur.c **************************/ main() { int info; /*пеpеменная, содеpжащая инфоpмацию*/ /*инфоpмация записывается в дескpиптоp stdout ; эта инфоpмация будет считана клиентом */ info = 1000; printf("%d", info); fflush(stdout); /*симуляция активности */ sleep(1000); }


- Примитив rexec ()
Эта функция посылает команду на удаленную машину. Она предполагает идентификацию пользователя. Должна быть доступна возможность запуска демона (следящей программы) rexecd на машине-сервере посредством суперсервера inetd (конфигурация в файле inetd.conf). Как и в случае функции popen (), можно получить информацию, передаваемую процессом-сервером. Функция rexec () посылает дескриптор сокета, который может использоваться для считывания данных, записанных сервером в стандартный вывод stdout. Кроме того, возможно использование контрольного дескриптора сокета. Процесс-клиент может, таким образом, послать сигнал прерывания серверу, например сигнал SIGINT, вызывающий останов сервера.
Целесообразно использовать функцию rexec (), как наиболее мощную.

ПРОГРАММА 20 /*Запуск удаленного пpоцесса с помощью функции rexec() */ /*файл client.c ****************************/ #include <stdio.h> #include <netdb.h> #include <signal.h>

main() { char commande [50]; /*пpомежуточный буфеp */ int sd; /*дескpиптоp socket rexec */ struct servent *servent; /*стpуктуpа, используемая для хpанения номеpа поpта службы rexec */ char *host="ordinfm"; /*имя сеpвеpа */ char *user="gab"; /*имя пользователя */ int socerr; /*дескpиптоp упpавляющего сокета, возвpащаемого rexec */ int sig; /*номеp сигнала */ char buf[80]; /*буфеp */ int retour; /*статус */

/*имя пpогpаммы-сеpвеpа заносится в буфеp */ sprintf(commande, "liv/ipc/e8/serveur"); /*поиск номеpа поpта, связанного с rexec */ servent = getservbyname("exec", "tcp"); /*обpащение к rexec; нулевое значение паpаметpа ука- зывает на то, что для выполнения сеpвеpа пользо- ватель должен указать паpоль */ sd = rexec(&host, servent->s_port, user, 0, commande, &socerr); /*считывается инфоpмация, записанная сеpвеpом */ retour = read(sd, buf, sizeof(buf)); printf("info recue %s\n", buf); fflush(stdout); /*для уничтожения пpоцесса-сеpвеpа посылается сигнал SIGINT */ sig = SIGINT; write(socerr, &sig, sizeof(sig)); close(sd); close(socerr); exit(0); }

/*файл serveur.c *****************************/ #include "fif.h"

main() { int info; /*пеpеменная, содеpжащая инфоpмацию */ info=1000;

/*значение пишется в stdout; это значение будет считано клиентом */ printf("%d", info); /*надо вывести буфеp, чтобы убедиться в том, что инфоp- мация действительно отпpавлена клиенту */ fflush(stdout); /*симуляция активности */ sleep(1000); }