В качестве примера рассмотрим программу uucp, которая обслуживает пере-
сылку файлов в сети и исполнение команд на удалении (см. [Nowitz 80]). Про-
цесс-клиент запрашивает в базе данных адрес и другую маршрутную информацию
(например, номер телефона), открывает автокоммутатор, записывает или прове-
ряет информацию в дескрипторе открываемого файла и вызывает удаленную маши-
ну. Удаленная машина может иметь специальные линии, выделенные для использо-
вания программой uucp; выполняющийся на этой машине процесс init порождает
getty-процессы - серверы, которые управляют линиями и получают извещения о
подключениях. После выполнения аппаратного подключения процесс-клиент регис-
трируется в системе в соответствии с обычным протоколом регистрации:
getty-процесс запускает специальный интерпретатор команд, uucico, указанный
в файле "/etc/passwd", а процесс-клиент передает на удаленную машину после-
довательность команд, тем самым заставляя ее исполнять процессы от имени ло-
кальной машины.
Сетевое взаимодействие в системе UNIX представляет серьезную проблему,
поскольку сообщения должны включать в себя как информационную, так и управ-
ляющую части. В управляющей части сообщения может располагаться адрес назна-
чения сообщения. В свою очередь, структура адресных данных зависит от типа
сети и используемого протокола. Следовательно, процессам нужно знать тип се-
ти, а это идет вразрез с тем принципом, по которому пользователи не должны
обращать внимания на тип файла, ибо все устройства для пользователей выгля-
дят как файлы. Традиционные методы реализации сетевого взаимодействия при
установке управляющих параметров в сильной степени полагаются на помощь сис-
темной функции ioctl, однако в разных типах сетей этот момент воплощается
по-разному. Отсюда возникает нежелательный побочный эффект, связанный с тем,
что программы, разработанные для одной сети, в других сетях могут не зарабо-
тать.
Чтобы разработать сетевые интерфейсы для системы UNIX, были предприняты
значительные усилия. Реализация потоков в последних редакциях версии V рас-
полагает элегантным механизмом поддержки сетевого взаимодействия, обеспечи-
вающим гибкое сочетание отдельных модулей протоколов и их согласованное ис-
пользование на уровне задач. Следующий раздел посвящен краткому описанию ме-
тода решения данных проблем в системе BSD, основанного на использовании
гнезд.


    11.4 ГНЕЗДА



В предыдущем разделе было показано, каким образом взаимодействуют между
собой процессы, протекающие на разных машинах, при этом обращалось внимание
на то, что способы реализации взаимодействия могут быть различаться в зави-
симости от используемых протоколов и сетевых средств. Более того, эти спосо-
бы не всегда применимы для обслуживания взаимодействия процессов, выполняю-
щихся на одной и той же машине, поскольку в них предполагается существование
обслуживающего (серверного) процесса, который при выполнении системных функ-
ций open или read будет приостанавливаться драйвером. В целях создания более
универсальных методов взаимодействия процессов на основе использования мно-
гоуровневых сетевых протоколов для системы BSD был разработан механизм, по-
лучивший название "sockets" (гнезда) (см. [Berkeley 83]). В данном разделе
мы рассмотрим некоторые аспекты применения гнезд (на пользовательском уровне
представления).


355

Процесс-клиент Процесс-сервер
| |
+--+ +--+
+-------------------------+--+ +--+--------------------------+
| Уровень гнезд | | Уровень гнезд |
+-------------------------+--+ +--+--------------------------+
| TCP | | TCP |
| Уровень протоколов | | | | Уровень протоколов |
| IP | | IP |
+-------------------------+--+ +--+--------------------------+
| Драйвер| | Драйвер |
| Уровень устройств Ethernet| |Ethernet Уровень устройств |
+-------------------------+--+ +--+--------------------------+
+---+ +---+
| |
С е т ь

Рисунок 11.18. Модель с использованием гнезд

Структура ядра имеет три уровня: гнезд, протоколов и устройств (Рисунок
11.18). Уровень гнезд выполняет функции интерфейса между обращениями к опе-
рационной системе (системным функциям) и средствами низких уровней, уровень
протоколов содержит модули, обеспечивающие взаимодействие процессов (на ри-
сунке упомянуты протоколы TCP и IP), а уровень устройств содержит драйверы,
управляющие сетевыми устройствами. Допустимые сочетания протоколов и драйве-
ров указываются при построении системы (в секции конфигурации); этот способ
уступает по гибкости вышеупомянутому потоковому механизму. Процессы взаимо-
действуют между собой по схеме клиент-сервер: сервер ждет сигнала от гнезда,
находясь на одном конце дуплексной линии связи, а процессы-клиенты взаимо-
действуют с сервером через гнездо, находящееся на другом конце, который мо-
жет располагаться на другой машине. Ядро обеспечивает внутреннюю связь и пе-
редает данные от клиента к серверу.
Гнезда, обладающие одинаковыми свойствами, например, опирающиеся на об-
щие соглашения по идентификации и форматы адресов (в протоколах), группиру-
ются в домены (управляемые одним узлом). В системе BSD 4.2 поддерживаются
домены: "UNIX system" - для взаимодействия процессов внутри одной машины и
"Internet" (межсетевой) - для взаимодействия через сеть с помощью протокола
DARPA (Управление перспективных исследований и разработок Министерства обо-
роны США) (см. [Postel 80] и [Postel 81]). Гнезда бывают двух типов: вирту-
альный канал (потоковое гнездо, если пользоваться терминологией Беркли) и
дейтаграмма. Виртуальный канал обеспечивает надежную доставку данных с сох-
ранением исходной последовательности. Дейтаграммы не гарантируют надежную
доставку с сохранением уникальности и последовательности, но они более эко-
номны в смысле использования ресурсов, поскольку для них не требуются слож-
ные установочные операции; таким образом, дейтаграммы полезны в отдельных
случаях взаимодействия. Для каждой допустимой комбинации типа домен-гнездо в
системе поддерживается умолчание на используемый протокол. Так, например,
для домена "Internet" услуги виртуального канала выполняет протокол транс-
портной связи (TCP), а функции дейтаграммы - пользовательский дейтаграммный
протокол (UDP).
Существует несколько системных функций работы с гнездами. Функция socket
устанавливает оконечную точку линии связи.

sd = socket(format,type,protocol);

Format обозначает домен ("UNIX system" или "Internet"), type - тип связи че-
рез гнездо (виртуальный канал или дейтаграмма), а protocol - тип протокола,
управляющего взаимодействием. Дескриптор гнезда sd, возвращаемый функцией
socket, используется другими системными функциями. Закрытие гнезд выполняет

356

функция close.
Функция bind связывает дескриптор гнезда с именем:

bind(sd,address,length);

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

connect(sd,address,length);

Семантический смысл параметров функции остается прежним (см. функцию bind),
но address указывает уже на выходное гнездо, образующее противоположный ко-
нец линии связи. Оба гнезда должны использовать одни и те же домен и прото-
кол связи, и тогда ядро удостоверит правильность установки линии связи. Если
тип гнезда - дейтаграмма, сообщаемый функцией connect ядру адрес будет ис-
пользоваться в последующих обращениях к функции send через данное гнездо; в
момент вызова никаких соединений не производится.
Пока процесс-сервер готовится к приему связи по виртуальному каналу, яд-
ру следует выстроить поступающие запросы в очередь на обслуживание. Макси-
мальная длина очереди задается с помощью системной функции listen:

listen(sd,qlength)

где sd - дескриптор гнезда, а qlength - максимально-допустимое число запро-
сов, ожидающих обработки.


+--------------------+ +-------------------------+
| Процесс-клиент | | Процесс-сервер |
| | | | | - |
| | | | +----+ ------ |
| | | | | - |
| | | |listen addr accept addr|
+---------+----------+ +-----+-------------------+
| | -
+--------------------------+-------------

Рисунок 11.19. Прием вызова сервером


Системная функция accept принимает запросы на подключение, поступающие
на вход процесса-сервера:

nsd = accept(sd,address,addrlen);

где sd - дескриптор гнезда, address - указатель на пользовательский массив,
в котором ядро возвращает адрес подключаемого клиента, addrlen - размер
пользовательского массива. По завершении выполнения функции ядро записывает
в переменную addrlen размер пространства, фактически занятого массивом. Фун-
кция возвращает новый дескриптор гнезда (nsd), отличный от дескриптора sd.
Процесс-сервер может продолжать слежение за состоянием объявленного гнезда,
поддерживая связь с клиентом по отдельному каналу (Рисунок 11.19).

357

Функции send и recv выполняют передачу данных через подключенное гнездо.
Синтаксис вызова функции send:

count = send(sd,msg,length,flags);

где sd - дескриптор гнезда, msg - указатель на посылаемые данные, length -
размер данных, count - количество фактически переданных байт. Параметр flags
может содержать значение SOF_OOB (послать данные out-of-band - "через тамож-
ню"), если посылаемые данные не учитываются в общем информационном обмене
между взаимодействующими процессами. Программа удаленной регистрации, напри-
мер, может послать out-of-band сообщение, имитирующее нажатие на клавиатуре
терминала клавиши "delete". Синтаксис вызова системной функции recv:

count = recv(sd,buf,length,flags);

где buf - массив для приема данных, length - ожидаемый объем данных, count -
количество байт, фактически переданных пользовательской программе. Флаги
(flags) могут быть установлены таким образом, что поступившее сообщение пос-
ле чтения и анализа его содержимого не будет удалено из очереди, или настро-
ены на получение данных out-of-band. В дейтаграммных версиях указанных функ-
ций, sendto и recvfrom, в качестве дополнительных параметров указываются ад-
реса. После выполнения подключения к гнездам потокового типа процессы могут
вместо функций send и recv использовать функции read и write. Таким образом,
согласовав тип протокола, серверы могли бы порождать процессы, работающие
только с функциями read и write, словно имеют дело с обычными файлами.
Функция shutdown закрывает гнездовую связь:

shutdown(sd,mode)

где mode указывает, какой из сторон (посылающей, принимающей или обеим вмес-
те) отныне запрещено участие в процессе передачи данных. Функция сообщает
используемому протоколу о завершении сеанса сетевого взаимодействия, остав-
ляя, тем не менее, дескрипторы гнезд в неприкосновенности. Освобождается
дескриптор гнезда только в результате выполнения функции close.
Системная функция getsockname получает имя гнездовой связи, установлен-
ной ранее с помощью функции bind:

getsockname(sd,name,length);

Функции getsockopt и setsockopt получают и устанавливают значения раз-
личных связанных с гнездом параметров в соответствии с типом домена и прото-
кола.
Рассмотрим обслуживающую программу, представленную на Рисунке 11.20.
Процесс создает в домене "UNIX system" гнездо потокового типа и присваивает
ему имя sockname. Затем с помощью функции listen устанавливается длина оче-
реди поступающих сообщений и начинается цикл ожидания поступления запросов.
Функция accept приостанавливает свое выполнение до тех пор, пока протоколом
не будет зарегистрирован запрос на подключение к гнезду с означенным именем;
после этого функция завершается, возвращая поступившему запросу новый деск-
риптор гнезда. Процесс-сервер порождает потомка, через которого будет под-
держиваться связь с процессом-клиентом; родитель и потомок при этом закрыва-
ют свои дескрипторы, чтобы они не становились помехой для коммуникационного
траффика другого процесса. Процесс-потомок ведет разговор с клиентом и за-
вершается после выхода из функции read. Процесс-сервер возвраща-
ется к началу цикла и ждет поступления следующего запроса на подключение.
На Рисунке 11.21 показан пример процесса-клиента, ведущего общение с
сервером. Клиент создает гнездо в том же домене, что и сервер, и посылает
запрос на подключение к гнезду с именем sockname. В результате подключения


358

+------------------------------------------------------------+
| #include |
| #include |
| |
| main() |
| { |
| int sd,ns; |
| char buf[256]; |
| struct sockaddr sockaddr; |
| int fromlen; |
| |
| sd = socket(AF_UNIX,SOCK_STREAM,0); |
| |
| /* имя гнезда - не может включать пустой символ */ |
| bind(sd,"sockname",sizeof("sockname") - 1); |
| listen(sd,1); |
| |
| for (;;) |
| { |
| |
| ns = accept(sd,&sockaddr,&fromlen); |
| if (fork() == 0) |
| { |
| /* потомок */ |
| close(sd); |
| read(ns,buf,sizeof(buf)); |
| printf("сервер читает '%s'\n",buf); |
| exit(); |
| } |
| close(ns); |
| } |
| } |
+------------------------------------------------------------+

Рисунок 11.20. Процесс-сервер в домене "UNIX system"

+------------------------------------------------------------+
| #include |
| #include |
| |
| main() |
| { |
| int sd,ns; |
| char buf[256]; |
| struct sockaddr sockaddr; |
| int fromlen; |
| |
| sd = socket(AF_UNIX,SOCK_STREAM,0); |
| |
| /* имя в запросе на подключение не может включать |
| /* пустой символ */ |
| if (connect(sd,"sockname",sizeof("sockname") - 1) == -1)|
| exit(); |
| |
| write(sd,"hi guy",6); |
| } |
+------------------------------------------------------------+

Рисунок 11.21. Процесс-клиент в домене "UNIX system"

359



процесс-клиент получает виртуальный канал связи с сервером. В рассматривае-
мом примере клиент передает одно сообщение и завершается.
Если сервер обслуживает процессы в сети, указание о том, что гнездо при-
надлежит домену "Internet", можно сделать следующим образом:

socket(AF_INET,SOCK_STREAM,0);

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


    11.5 ВЫВОДЫ



Мы рассмотрели несколько форм взаимодействия процессов. Первой формой,
положившей начало обсуждению, явилась трассировка процессов - взаимодействие
двух процессов, выступающее в качестве полезного средства отладки программ.
При всех своих преимуществах трассировка процессов с помощью функции ptrace
все же достаточно дорогостоящее и примитивное мероприятие, поскольку за один
сеанс функция способна передать строго ограниченный объем данных, требуется
большое количество переключений контекста, взаимодействие ограничивается
только формой отношений родитель-потомок, и наконец, сама трассировка произ-
водится только по обоюдному согласию участвующих в ней процессов. В версии V
системы UNIX имеется пакет взаимодействия процессов (IPC), включающий в себя
механизмы обмена сообщениями, работы с семафорами и разделения памяти. К со-
жалению, все эти механизмы имеют узкоспециальное назначение, не имеют хоро-
шей стыковки с другими элементами операционной системы и не действуют в се-
ти. Тем не менее, они используются во многих приложениях и по сравнению с
другими схемами отличаются более высокой эффективностью.
Система UNIX поддерживает широкий спектр вычислительных сетей. Традици-
онные методы согласования протоколов в сильной степени полагаются на помощь
системной функции ioctl, однако в разных типах сетей они реализуются по-раз-
ному. В системе BSD имеются системные функции для работы с гнездами, поддер-
живающие более универсальную структуру сетевого взаимодействия. В будущем в
версию V предполагается включить описанный в главе 10 потоковый механизм,
повышающий согласованность работы в сети.


    11.6 УПРАЖНЕНИЯ



1. Что произойдет в том случае, если в программе debug будет отсутствовать
вызов функции wait (Рисунок 11.3) ? (Намек: возможны два исхода.)
2. С помощью функции ptrace отладчик считывает данные из пространства
трассируемого процесса по одному слову за одну операцию. Какие измене-
ния следует произвести в ядре операционной системы для того, чтобы уве-
личить количество считываемых слов ? Какие изменения при этом необходи-
мо сделать в самой функции ptrace ?
3. Расширьте область действия функции ptrace так, чтобы в качестве пара-
метра pid можно было указывать идентификатор процесса, не являющегося
потомком текущего процесса. Подумайте над вопросами, связанными с защи-
той информации: При каких обстоятельствах процессу может быть позволено

360

читать данные из адресного пространства другого, произвольного процесса
? При каких обстоятельствах разрешается вести запись в адресное прост-
ранство другого процесса ?
4. Организуйте из функций работы с сообщениями библиотеку пользовательско-
го уровня с использованием обычных файлов, поименованных каналов и эле-
ментов блокировки. Создавая очередь сообщений, откройте управляющий
файл для записи в него информации о состоянии очереди; защитите файл с
помощью средств захвата файлов и других удобных для вас механизмов. По-
сылая сообщение данного типа, создавайте поименованный канал для всех
сообщений этого типа, если такого канала еще не было, и передавайте со-
общение через него (с подсчетом переданных байт). Управляющий файл дол-
жен соотносить тип сообщения с именем поименованного канала. При чтении
сообщений управляющий файл направляет процесс к соответствующему поиме-
нованному каналу. Сравните эту схему с механизмом, описанным в настоя-
щей главе, по эффективности, сложности реализации и функциональным воз-
можностям.
5. Какие действия пытается выполнить программа, представленная на Рисунке
11.22 ?
*6. Напишите программу, которая подключала бы область разделяемой памяти
слишком близко к вершине стека задачи и позволяла бы стеку при увеличе-
нии пересекать границу разделяемой области. В какой момент произойдет
фатальная ошибка памяти ?
7. Используйте в программе, представленной на Рисунке 11.14, флаг
IPC_NOWAIT, реализуя условный тип семафора. Продемонстрируйте, как за
счет этого можно избежать возникновения взаимных блокировок.
8. Покажите, как операции над семафорами типа P и V реализуются при работе
с поименованными каналами. Как бы вы реализовали операцию P условного
типа ?
9. Составьте программы захвата ресурсов, использующие (а) поименованные
каналы, (б) системные функции creat и unlink, (в) функции обмена сооб-
щениями. Проведите сравнительный анализ их эффективности.
10. На практических примерах работы с поименованными каналами сравните эф-
фективность использования функций обмена сообщениями, с одной стороны,
с функциями read и write, с другой.
11. Сравните на конкретных программах скорость передачи данных при работе с
разделяемой памятью и при использовании механизма обмена сообщениями.
Программы, использующие разделяемую память, для синхронизации заверше-
ния операций чтения-записи должны опираться на семафоры.

+------------------------------------------------------------+
| #include |
| #include |
| #include |
| #define ALLTYPES 0 |
| |
| main() |
| { |
| struct msgform |
| { |
| long mtype; |
| char mtext[1024]; |
| } msg; |
| register unsigned int id; |
| |
| for (id = 0; ; id++) |
| while (msgrcv(id,&msg,1024,ALLTYPES,IPC_NOWAIT) > 0)|
| ; |
| } |
+------------------------------------------------------------+

361