|
Часть 11
ГЛАВА 11
EВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВF
Наличие механизмов взаимодействия дает произвольным процессам возмож-
ность осуществлять обмен данными и синхронизировать свое выполнение с други-
ми процессами. Мы уже рассмотрели несколько форм взаимодействия процессов,
такие как канальная связь, использование поименованных каналов и посылка
сигналов. Каналы (непоименованные) имеют недостаток, связанный с тем, что
они известны только потомкам процесса, вызвавшего системную функцию pipe: не
имеющие родственных связей процессы не могут взаимодействовать между собой с
помощью непоименованных каналов. Несмотря на то, что поименованные каналы
позволяют взаимодействовать между собой процессам, не имеющим родственных
связей, они не могут использоваться ни в сети (см. главу 13), ни в организа-
ции множественных связей между различными группами взаимодействующих процес-
сов: поименованный канал не поддается такому мультиплексированию, при кото-
ром у каждой пары взаимодействующих процессов имелся бы свой выделенный ка-
нал. Произвольные процессы могут также связываться между собой благодаря по-
сылке сигналов с помощью системной функции kill, однако такое "сообщение"
состоит из одного только номера сигнала.
В данной главе описываются другие формы взаимодействия процессов. В на-
чале речь идет о трассировке процессов, о том, каким образом один процесс
следит за ходом выполнения другого процесса, затем рассматривается пакет
IPC: сообщения, разделяемая память и семафоры. Делается обзор традиционных
методов сетевого взаимодействия процессов, выполняющихся на разных машинах,
и, наконец, дается представление о "гнездах", применяющихся в системе BSD.
Вопросы сетевого взаимодействия, имеющие специальный характер, такие как
протоколы, адресация и др., не рассматриваются, поскольку они выходят за
рамки настоящей работы.
E11.1 ТРАССИРОВКА ПРОЦЕССОВF
В системе UNIX имеется простейшая форма взаимодействия процессов, ис-
пользуемая в целях отладки, - трассировка процессов. Процесс-отладчик, нап-
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і if ((pid = fork()) == 0) і
і { і
і /* потомок - трассируемый процесс */ і
і ptrace(0,0,0,0); і
і exec("имя трассируемого процесса"); і
і } і
і /* продолжение выполнения процесса-отладчика */ і
і for (;;) і
і { і
і wait((int *) 0); і
і read(входная информация для трассировки команд) і
і ptrace(cmd,pid,...); і
і if (условие завершения трассировки) і
і break; і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.1. Структура процесса отладки
330
ример sdb, порождает трассируемый процесс и управляет его выполнением с по-
мощью системной функции ptrace, расставляя и сбрасывая контрольные точки,
считывая и записывая данные в его виртуальное адресное пространство. Трасси-
ровка процессов, таким образом, включает в себя синхронизацию выполнения
процесса-отладчика и трассируемого процесса и управление выполнением послед-
него.
Псевдопрограмма, представленная на Рисунке 11.1, имеет типичную структу-
ру отладочной программы. Отладчик порождает новый процесс, запускающий сис-
темную функцию ptrace, в результате чего в соответствующей процессу-потомку
записи таблицы процессов ядро устанавливает бит трассировки. Процесс-потомок
предназначен для запуска (exec) трассируемой программы. Например, если поль-
зователь ведет отладку программы a.out, процесс-потомок запускает файл с тем
же именем. Ядро отрабатывает функцию exec обычным порядком, но в финале за-
мечает, что бит трассировки установлен, и посылает процессу-потомку сигнал
прерывания. На выходе из функции exec, как и на выходе из любой другой функ-
ции, ядро проверяет наличие сигналов, обнаруживает только что посланный сиг-
нал прерывания и исполняет программу трассировки процесса как особый случай
обработки сигналов. Заметив установку бита трассировки, процесс-потомок вы-
водит своего родителя из состояния приостанова, в котором последний находит-
ся вследствие исполнения функции wait, сам переходит в состояние трассиров-
ки, подобное состоянию приостанова (но не показанное на диаграмме состояний
процесса, см. Рисунок 6.1), и выполняет переключение контекста.
Тем временем в обычной ситуации процесс-родитель (отладчик) переходит на
пользовательский уровень, ожидая получения известия от трассируемого процес-
са. Когда соответствующее известие процессом-родителем будет получено, он
выйдет из состояния ожидания (wait), прочитает (read) введенные пользовате-
лем команды и превратит их в серию обращений к функции ptrace, управляющих
трассировкой процесса-потомка. Синтаксис вызова системной функции ptrace:
ptrace(cmd,pid,addr,data);
где в качестве cmd указываются различные команды, например, чтения данных,
записи данных, возобновления выполнения и т.п., pid - идентификатор трасси-
руемого процесса, addr - виртуальный адрес ячейки в трассируемом процессе,
где будет производиться чтение или запись, data - целое значение, предназна-
ченное для записи. Во время исполнения системной функции ptrace ядро прове-
ряет, имеется ли у отладчика потомок с идентификатором pid и находится ли
этот потомок в состоянии трассировки, после чего заводит глобальную структу-
ру данных, предназначенную для передачи данных между двумя процессами. Чтобы
другие процессы, выполняющие трассировку, не могли затереть содержимое этой
структуры, она блокируется ядром, ядро записывает в нее параметры cmd, addr
и data, возобновляет процесс-потомок, переводит его в состояние "готовности
к выполнению" и приостанавливается до получения от него ответа. Когда про-
цесс-потомок продолжит свое выполнение (в режиме ядра), он исполнит соответ-
ствующую (трассируемую) команду, запишет результат в глобальную структуру и
"разбудит" отладчика. В зависимости от типа команды потомок может вновь пе-
рейти в состояние трассировки и ожидать поступления новой команды или же
выйти из цикла обработки сигналов и продолжить свое выполнение. При возоб-
новлении работы отладчика ядро запоминает значение, возвращенное трассируе-
мым процессом, снимает с глобальной структуры блокировку и возвращает управ-
ление пользователю.
Если в момент перехода процесса-потомка в состояние трассировки отладчик
не находится в состоянии приостанова (wait), он не обнаружит потомка, пока
не обратится к функции wait, после чего немедленно выйдет из функции и про-
должит работу по вышеописанному плану.
331
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і int data[32]; і
і main() і
і { і
і int i; і
і for (i = 0; i < 32; i++) і
і printf("data[%d] = %d\n@,i,data[i]); і
і printf("ptrace data addr Ox%x\n",data); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.2. Программа trace (трассируемый процесс)
Рассмотрим две программы, приведенные на Рисунках 11.2 и 11.3 и именуе-
мые trace и debug, соответственно. При запуске программы trace с терминала
массив data будет содержать нулевые значения; процесс выводит адрес массива
и завершает работу. При запуске программы debug с передачей ей в качестве
параметра значения, выведенного программой trace, происходит следующее:
программа запоминает значение параметра в переменной addr, создает новый
процесс, с помощью функции ptrace подготавливающий себя к трассировке, и за-
пускает программу trace. На выходе из функции exec ядро посылает процес-
су-потомку (назовем его тоже trace) сигнал SIGTRAP (сигнал прерывания), про-
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #define TR_SETUP 0 і
і #define TR_WRITE 5 і
і #define TR_RESUME 7 і
і int addr; і
і і
і main(argc,argv) і
і int argc; і
і char *argv[]; і
і { і
і int i,pid; і
і і
і sscanf(argv[1],"%x",&addr); і
і і
і if ((pid = fork() == 0) і
і { і
і ptrace(TR_SETUP,0,0,0); і
і execl("trace","trace",0); і
і exit(); і
і } і
і for (i = 0; i < 32, i++) і
і { і
і wait((int *) 0); і
і /* записать значение i в пространство процесса с і
і * идентификатором pid по адресу, содержащемуся в і
і * переменной addr */ і
і if (ptrace(TR_WRITE,pid,addr,i) == -1) і
і exit(); і
і addr += sizeof(int); і
і } і
і /* трассируемый процесс возобновляет выполнение */ і
і ptrace(TR_RESUME,pid,1,0); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.3. Программа debug (трассирующий процесс)
332
цесс trace переходит в состояние трассировки, ожидая поступления команды от
программы debug. Если процесс, реализующий программу debug, находился в сос-
тоянии приостанова, связанного с выполнением функции wait, он "пробуждает-
ся", обнаруживает наличие порожденного трассируемого процесса и выходит из
функции wait. Затем процесс debug вызывает функцию ptrace, записывает значе-
ние переменной цикла i в пространство данных процесса trace по адресу, со-
держащемуся в переменной addr, и увеличивает значение переменной addr; в
программе trace переменная addr хранит адрес точки входа в массив data. Пос-
леднее обращение процесса debug к функции ptrace вызывает запуск программы
trace, и в этот момент массив data содержит значения от 0 до 31. Отлад-
чики, подобные sdb, имеют доступ к таблице идентификаторов трассируемого
процесса, из которой они получают информацию об адресах данных, используемых
в качестве параметров функции ptrace.
Использование функции ptrace для трассировки процессов является обычным
делом, но оно имеет ряд недостатков.
* Для того, чтобы произвести передачу порции данных длиною в слово между
процессом-отладчиком и трассируемым процессом, ядро должно выполнить че-
тыре переключения контекста: оно переключает контекст во время вызова
отладчиком функции ptrace, загружает и выгружает контекст трассируемого
процесса и переключает контекст вновь на процесс-отладчик по получении
ответа от трассируемого процесса. Все вышеуказанное необходимо, посколь-
ку у отладчика нет иного способа получить доступ к виртуальному адресно-
му пространству трассируемого процесса, отсюда замедленность протекания
процедуры трассировки.
* Процесс-отладчик может вести одновременную трассировку нескольких про-
цессов-потомков, хотя на практике эта возможность используется редко.
Если быть более критичным, следует отметить, что отладчик может трасси-
ровать только своих ближайших потомков: если трассируемый процесс-пото-
мок вызовет функцию fork, отладчик не будет иметь контроля над порождае-
мым, внучатым для него, процессом, что является серьезным препятствием в
отладке многоуровневых программ. Если трассируемый процесс вызывает фун-
кцию exec, запускаемые образы задач тоже подвергаются трассировке под
управлением ранее вызванной функции ptrace, однако отладчик может не
знать имени исполняемого образа, что затрудняет проведение символьной
отладки.
* Отладчик не может вести трассировку уже выполняющегося процесса, если
отлаживаемый процесс не вызвал предварительно функцию ptrace, дав тем
самым ядру свое согласие на трассировку. Это неудобно, так как в указан-
ном случае выполняющийся процесс придется удалить из системы и переза-
пустить в режиме трассировки.
* Не разрешается трассировать setuid-программы, поскольку это может при-
вести к нарушению защиты данных (ибо в результате выполнения функции
ptrace в их адресное пространство производилась бы запись данных) и к
выполнению недопустимых действий. Предположим, например, что
setuid-программа запускает файл с именем "privatefile". Умелый пользова-
тель с помощью функции ptrace мог бы заменить имя файла на "/bin/sh",
запустив на выполнение командный процессор shell (и все программы, ис-
полняемые shell'ом), не имея на то соответствующих полномочий. Функция
exec игнорирует бит setuid, если процесс подвергается трассировке, тем
самым адресное пространство setuid-программ защищается от пользователь-
ской записи.
Киллиан [Killian 84] описывает другую схему трассировки процессов, осно-
ванную на переключении файловых систем (см. главу 5). Администратор монтиру-
ет файловую систему под именем "/proc"; пользователи идентифицируют процессы
с помощью кодов идентификации и трактуют их как файлы, принадлежащие катало-
гу "/proc". Ядро дает разрешение на открытие файлов, исходя из кода иденти-
333
фикации пользователя процесса и кода идентификации группы. Пользователи мо-
гут обращаться к адресному пространству процесса путем чтения (read) файла и
устанавливать точки прерываний путем записи (write) в файл. Функция stat со-
общает различную статистическую информацию, касающуюся процесса. В данном
подходе устранены три недостатка, присущие функции ptrace. Во-первых, эта
схема работает быстрее, поскольку процесс-отладчик за одно обращение к ука-
занным системным функциям может передавать больше информации, чем при работе
с ptrace. Во-вторых, отладчик здесь может вести трассировку совершенно про-
извольных процессов, а не только своих потомков. Наконец, трассируемый про-
цесс не должен предпринимать предварительно никаких действий по подготовке к
трассировке; отладчик может трассировать и существующие процессы. Возмож-
ность вести отладку setuid-программ, предоставляемая только суперпользовате-
лю, реализуется как составная часть традиционного механизма защиты файлов.
E11.2 ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В ВЕРСИИ V СИСТЕМЫF
Пакет IPC (interprocess communication) в версии V системы UNIX включает
в себя три механизма. Механизм сообщений дает процессам возможность посылать
другим процессам потоки сформатированных данных, механизм разделения памяти
позволяет процессам совместно использовать отдельные части виртуального ад-
ресного пространства, а семафоры - синхронизировать свое выполнение с выпол-
нением параллельных процессов. Несмотря на то, что они реализуются в виде
отдельных блоков, им присущи общие свойства.
* С каждым механизмом связана таблица, в записях которой описываются все
его детали.
* В каждой записи содержится числовой ключ (key), который представляет со-
бой идентификатор записи, выбранный пользователем.
* В каждом механизме имеется системная функция типа "get", используемая
для создания новой или поиска существующей записи; параметрами функции
являются идентификатор записи и различные флаги (flag). Ядро ведет поиск
записи по ее идентификатору в соответствующей таблице. Процессы могут с
помощью флага IPC_PRIVATE гарантировать получение еще неиспользуемой за-
писи. С помощью флага IPC_CREAT они могут создать новую запись, если за-
писи с указанным идентификатором нет, а если еще к тому же установить
флаг IPC_EXCL, можно получить уведомление об ошибке в том случае, если
запись с таким идентификатором существует. Функция возвращает некий выб-
ранный ядром дескриптор, предназначенный для последующего использования
в других системных функциях, таким образом, она работает аналогично сис-
темным функциям creat и open.
* В каждом механизме ядро использует следующую формулу для поиска по деск-
риптору указателя на запись в таблице структур данных:
указатель = значение дескриптора по модулю от числа записей в таблице
Если, например, таблица структур сообщений состоит из 100 записей, деск-
рипторы, связанные с записью номер 1, имеют значения, равные 1, 101, 201
и т.д. Когда процесс удаляет запись, ядро увеличивает значение связанно-
го с ней дескриптора на число записей в таблице: полученный дескриптор
станет новым дескриптором этой записи, когда к ней вновь будет произве-
дено обращение при помощи функции типа "get". Процессы, которые будут
пытаться обратиться к записи по ее старому дескриптору, потерпят неуда-
чу. Обратимся вновь к предыдущему примеру. Если с записью 1 связан деск-
риптор, имеющий значение 201, при его удалении ядро назначит записи но-
вый дескриптор, имеющий значение 301. Процессы, пытающиеся обратиться к
дескриптору 201, получат ошибку, поскольку этого дескриптора больше нет.
В конечном итоге ядро произведет перенумерацию дескрипторов, но пока это
произойдет, может пройти значительный промежуток времени.
* Каждая запись имеет некую структуру данных, описывающую права доступа к
334
ней и включающую в себя пользовательский и групповой коды идентификации,
которые имеет процесс, создавший запись, а также пользовательский и
групповой коды идентификации, установленные системной функцией типа
"control" (об этом ниже), и двоичные коды разрешений чтения-записи-ис-
полнения для владельца, группы и прочих пользователей, по аналогии с ус-
тановкой прав доступа к файлам.
* В каждой записи имеется другая информация, описывающая состояние записи,
в частности, идентификатор последнего из процессов, внесших изменения в
запись (посылка сообщения, прием сообщения, подключение разделяемой па-
мяти и т.д.), и время последнего обращения или корректировки.
* В каждом механизме имеется системная функция типа "control", запрашиваю-
щая информацию о состоянии записи, изменяющая эту информацию или удаляю-
щая запись из системы. Когда процесс запрашивает информацию о состоянии
записи, ядро проверяет, имеет ли процесс разрешение на чтение записи,
после чего копирует данные из записи таблицы по адресу, указанному поль-
зователем. При установке значений принадлежащих записи параметров ядро
проверяет, совпадают ли между собой пользовательский код идентификации
процесса и идентификатор пользователя (или создателя), указанный в запи-
си, не запущен ли процесс под управлением суперпользователя; одного раз-
решения на запись недостаточно для установки параметров. Ядро копирует
сообщенную пользователем информацию в запись таблицы, устанавливая зна-
чения пользовательского и группового кодов идентификации, режимы доступа
и другие параметры (в зависимости от типа механизма). Ядро не изменяет
значения полей, описывающих пользовательский и групповой коды идентифи-
кации создателя записи, поэтому пользователь, создавший запись, сохраня-
ет управляющие права на нее. Пользователь может удалить запись, либо ес-
ли он является суперпользователем, либо если идентификатор процесса сов-
падает с любым из идентификаторов, указанных в структуре записи. Ядро
увеличивает номер дескриптора, чтобы при следующем назначении записи ей
был присвоен новый дескриптор. Следовательно, как уже ранее говорилось,
если процесс попытается обратиться к записи по старому дескриптору, выз-
ванная им функция получит отказ.
E11.2.1 СообщенияF
С сообщениями работают четыре системных функции: msgget, которая возвра-
щает (и в некоторых случаях создает) дескриптор сообщения, определяющий оче-
редь сообщений и используемый другими системными функциями, msgctl, которая
устанавливает и возвращает связанные с дескриптором сообщений параметры или
удаляет дескрипторы, msgsnd, которая посылает сообщение, и msgrcv, которая
получает сообщение.
Синтаксис вызова системной функции msgget:
msgqid = msgget(key,flag);
где msgqid - возвращаемый функцией дескриптор, а key и flag имеют ту же се-
мантику, что и в системной функции типа "get". Ядро хранит сообщения в связ-
ном списке (очереди), определяемом значением дескриптора, и использует зна-
чение msgqid в качестве указателя на массив заголовков очередей. Кроме выше-
указанных полей, описывающих общие для всего механизма права доступа, заго-
ловок очереди содержит следующие поля:
* Указатели на первое и последнее сообщение в списке;
* Количество сообщений и общий объем информации в списке в байтах;
* Максимальная емкость списка в байтах;
* Идентификаторы процессов, пославших и принявших сообщения последними;
* Поля, указывающие время последнего выполнения функций msgsnd, msgrcv и
msgctl.
Когда пользователь вызывает функцию msgget для того, чтобы создать новый
335
дескриптор, ядро просматривает массив очередей сообщений в поисках существу-
ющей очереди с указанным идентификатором. Если такой очереди нет, ядро выде-
ляет новую очередь, инициализирует ее и возвращает идентификатор пользовате-
лю. В противном случае ядро проверяет наличие необходимых прав доступа и за-
вершает выполнение функции.
Для посылки сообщения процесс использует системную функцию msgsnd:
msgsnd(msgqid,msg,count,flag);
где msgqid - дескриптор очереди сообщений, обычно возвращаемый функцией
msgget, msg - указатель на структуру, состоящую из типа в виде назначаемого
пользователем целого числа и массива символов, count - размер информационно-
го массива, flag - действие, предпринимаемое ядром в случае переполнения
внутреннего буферного пространства.
Ядро проверяет (Рисунок 11.4), имеется ли у посылающего сообщение про-
цесса разрешения на запись по указанному дескриптору, не выходит ли размер
сообщения за установленную системой границу, не содержится ли в очереди
слишком большой объем информации, а также является ли тип сообщения положи-
тельным целым числом. Если все условия соблюдены, ядро выделяет сообщению
место, используя карту сообщений (см. раздел 9.1), и копирует в это место
данные из пространства пользователя. К сообщению присоединяется заголовок,
после чего оно помещается в конец связного списка заголовков сообщений. В
заголовке сообщения записывается тип и размер сообще-
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і алгоритм msgsnd /* послать сообщение */ і
і входная информация: (1) дескриптор очереди сообщений і
і (2) адрес структуры сообщения і
і (3) размер сообщения і
і (4) флаги і
і выходная информация: количество посланных байт і
і { і
і проверить правильность указания дескриптора и наличие і
і соответствующих прав доступа; і
і выполнить пока (для хранения сообщения не будет выделеноі
і место) і
і { і
і если (флаги не разрешают ждать) і
і вернуться; і
і приостановиться (до тех пор, пока место не освобо- і
і дится); і
і } і
і получить заголовок сообщения; і
і считать текст сообщения из пространства задачи в прост- і
і ранство ядра; і
і настроить структуры данных: выстроить очередь заголовкові
і сообщений, установить в заголовке указатель на текст і
і сообщения, заполнить поля, содержащие счетчики, время і
і последнего выполнения операций и идентификатор процес- і
і са; і
і вывести из состояния приостанова все процессы, ожидающиеі
і разрешения считать сообщение из очереди; і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.4. Алгоритм посылки сообщения
ния, устанавливается указатель на текст сообщения и производится корректи-
336
ровка содержимого различных полей заголовка очереди, содержащих статистичес-
кую информацию (количество сообщений в очереди и их суммарный объем в бай-
тах, время последнего выполнения операций и идентификатор процесса, послав-
шего сообщение). Затем ядро выводит из состояния приостанова все процессы,
ожидающие пополнения очереди сообщений. Если размер очереди в байтах превы-
шает границу допустимости, процесс приостанавливается до тех пор, пока дру-
гие сообщения не уйдут из очереди. Однако, если процессу было дано указание
не ждать (флаг IPC_NOWAIT), он немедленно возвращает управление с уведомле-
нием об ошибке. На Рисунке 11.5 показана очередь сообщений, состоящая из за-
головков сообщений, организованных в связные списки, с указателями на об-
ласть текста.
Рассмотрим программу, представленную на Рисунке 11.6. Процесс вызывает
функцию msgget для того, чтобы получить дескриптор для записи с идентифика-
тором MSGKEY. Длина сообщения принимается равной 256 байт, хотя используется
только первое поле целого типа, в область текста сообщения копируется иден-
тификатор процесса, типу сообщения присваивается значение 1, после чего вы-
зывается функция msgsnd для посылки сообщения. Мы вернемся к этому примеру
позже.
Процесс получает сообщения, вызывая функцию msgrcv по следующему форма-
ту:
count = msgrcv(id,msg,maxcount,type,flag);
где id - дескриптор сообщения, msg - адрес пользовательской структуры, кото-
рая будет содержать полученное сообщение, maxcount - размер структуры msg,
type - тип считываемого сообщения, flag - действие, предпринимаемое ядром в
том случае, если в очереди со-
Заголовки Область
очередей текста
ЪДДДДДДї Заголовки сообщений ЪД>ЪДДДДДДї
і і ЪДДДДДДї ЪДДДДДДї ЪДДДДДДї і і і
і ДДЕДДДД>і ГДДД>і ГДДД>і і і і і
і і АДДДЕДДЩ АДДДЕДДЩ АДДДЕДДЩ і і і
ГДДДДДДґ і і АДДДДЩ і і
і і АДДДДДДДДДДДіДДДДДДДДДДДДДДДДДД>ГДДДДДДґ
і і і і і
і і і і і
ГДДДДДДґ і і і
і і ЪДДДДДДї і і і
і ДДЕДДДД>і і і і і
і і АДДДЕДДЩ і і і
ГДДДДДДґ і і і і
і щ і і і ГДДДДДДґ
і щ і АДДДДДДДДДДДіДДДДДДДДДДДДДДДДДД>ГДДДДДДґ
і щ і і і і
і щ і і і і
і щ і АДДДДДДДДДДДДДДДДДД>ГДДДДДДґ
і щ і і і
і щ і ГДДДДДДґ
і щ і і щ і
і щ і і щ і
і щ і і щ і
АДДДДДДЩ АДДДДДДЩ
Рисунок 11.5. Структуры данных, используемые в организации сообщений
общений нет. В переменной count пользователю возвращается число прочитанных
байт сообщения.
337
Ядро проверяет (Рисунок 11.7), имеет ли пользователь необходимые права
доступа к очереди сообщений. Если тип считываемого сообщения имеет нулевое
значение, ядро ищет первое по счету сообщение в связном списке. Если его
размер меньше или равен размеру, указанному пользователем, ядро копирует
текст сообщения в пользовательскую структуру и соответствующим образом наст-
раивает свои внутренние структуры: уменьшает счетчик сообщений в очереди и
суммарный объем информации в байтах, запоминает время получения сообщения и
идентификатор процесса-получателя, перестраивает связный список и освобожда-
ет место в системном пространстве, где хранился текст сообщения. Если ка-
кие-либо процессы, ожидавшие получения сообщения, находились в состоянии
приостанова из-за отсутствия свободного места в списке, ядро выводит их из
этого состояния. Если размер сообщения превышает значение maxcount, указан-
ное пользователем, ядро посылает системной функции уведомление об ошибке и
оставляет сообщение в очереди. Если, тем не менее, процесс игнорирует огра-
ничения на размер (в поле flag установлен бит MSG_NOERROR), ядро обрезает
сообщение, возвращает запрошенное количество байт и удаляет сообщение из
списка целиком.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #include і
і #include і
і #include і
і і
і #define MSGKEY 75 і
і і
і struct msgform { і
і long mtype; і
і char mtext[256]; і
і }; і
і і
і main() і
і { і
і struct msgform msg; і
і int msgid,pid,*pint; і
і і
і msgid = msgget(MSGKEY,0777); і
і і
і pid = getpid(); і
і pint = (int *) msg.mtext; і
і *pint = pid; /* копирование идентификатора і
і * процесса в область текста і
і * сообщения */ і
і msg.mtype = 1; і
і і
і msgsnd(msgid,&msg,sizeof(int),0); і
і msgrcv(msgid,&msg,256,pid,0); /* идентификатор і
і * процесса используется в і
і * качестве типа сообщения */ і
і printf("клиент: получил от процесса с pid %d\n", і
і *pint); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.6. Пользовательский процесс
Процесс может получать сообщения определенного типа, если присвоит пара-
метру type соответствующее значение. Если это положительное целое число,
функция возвращает первое значение данного типа, если отрицательное, ядро
338
определяет минимальное значение типа сообщений в очереди, и если оно не пре-
вышает абсолютное значение параметра type, возвращает процессу первое сооб-
щение этого типа. Например, если очередь состоит из трех сообщений, имеющих
тип 3, 1 и 2, соответственно, а пользователь запрашивает сообщение с типом
-2, ядро возвращает ему сообщение типа 1. Во всех случаях, если условиям
запроса не удовлетворяет ни одно из сообщений в очереди, ядро переводит про-
цесс в состояние приостанова, разумеется если только в параметре flag не ус-
тановлен бит IPC_NOWAIT (иначе процесс немедленно выходит из функции).
Рассмотрим программы, представленные на Рисунках 11.6 и 11.8. Программа
на Рисунке 11.8 осуществляет общее обслуживание запросов пользовательских
процессов (клиентов). Запросы, например, могут касаться информации, храня-
щейся в базе данных; обслуживающий процесс (сервер) выступает необходимым
посредником при обращении к базе данных, такой порядок облегчает поддержание
целостности данных и организацию их защиты от несанкционированного доступа.
Обслуживающий процесс создает сообщение путем установки флага IPC _CREAT при
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і алгоритм msgrcv /* получение сообщения */ і
і входная информация: (1) дескриптор сообщения і
і (2) адрес массива, в который заноситсяі
і сообщение і
і (3) размер массива і
і (4) тип сообщения в запросе і
і (5) флаги і
і выходная информация: количество байт в полученном сообщенииі
і { і
і проверить права доступа; і
і loop: і
і проверить правильность дескриптора сообщения; і
і /* найти сообщение, нужное пользователю */ і
і если (тип сообщения в запросе == 0) і
і рассмотреть первое сообщение в очереди; і
і в противном случае если (тип сообщения в запросе > 0) і
і рассмотреть первое сообщение в очереди, имеющее і
і данный тип; і
і в противном случае /* тип сообщения в запросе < 0 */і
і рассмотреть первое из сообщений в очереди с наи- і
і меньшим значением типа при условии, что его тип і
і не превышает абсолютное значение типа, указанно-і
і го в запросе; і
і если (сообщение найдено) і
і { і
і переустановить размер сообщения или вернуть ошиб-і
і ку, если размер, указанный пользователем слишкомі
і мал; і
і скопировать тип сообщения и его текст из прост- і
і ранства ядра в пространство задачи; і
і разорвать связь сообщения с очередью; і
і вернуть управление; і
і } і
і /* сообщений нет */ і
і если (флаги не разрешают приостанавливать работу) і
і вернуть управление с ошибкой; і
і приостановиться (пока сообщение не появится в очере- і
і ди); і
і перейти на loop; і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.7. Алгоритм получения сообщения
339
выполнении функции msgget и получает все сообщения ти-
па 1 - запросы от процессов-клиентов. Он читает текст сообщения, находит
идентификатор процесса-клиента и приравнивает возвращаемое значение типа со-
общения значению этого идентификатора. В данном примере обслуживающий про-
цесс возвращает в тексте сообщения процессу-клиенту его идентификатор, и
клиент получает сообщения с типом, равным идентификатору клиента. Таким об-
разом, обслуживающий процесс получает сообщения только от клиентов, а клиент
- только от обслуживающего процесса. Работа процессов реализуется в виде
многоканального взаимодействия, строящегося на основе одной очереди сообще-
ний.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #include і
і #include і
і #include і
і і
і #define MSGKEY 75 і
і struct msgform і
і { і
і long mtype; і
і char mtext[256]; і
і }msg; і
і int msgid; і
і і
і main() і
і { і
і int i,pid,*pint; і
і extern cleanup(); і
і і
і for (i = 0; i < 20; i++) і
і signal(i,cleanup); і
і msgid = msgget(MSGKEY,0777іIPC_CREAT); і
і і
і for (;;) і
і { і
і msgrcv(msgid,&msg,256,1,0); і
і pint = (int *) msg.mtext; і
і pid = *pint; і
і printf("сервер: получил от процесса с pid %d\n",і
і pid); і
і msg.mtype = pid; і
і *pint = getpid(); і
і msgsnd(msgid,&msg,sizeof(int),0); і
і } і
і } і
і і
і cleanup() і
і { і
і msgctl(msgid,IPC_RMID,0); і
і exit(); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.8. Обслуживающий процесс (сервер)
Сообщения имеют форму "тип - текст", где текст представляет собой поток
340
байтов. Указание типа дает процессам возможность выбирать сообщения только
определенного рода, что в файловой системе не так легко сделать. Таким обра-
зом, процессы могут выбирать из очереди сообщения определенного типа в по-
рядке их поступления, причем эта очередность гарантируется ядром. Несмотря
на то, что обмен сообщениями может быть реализован на пользовательском уров-
не средствами файловой системы, представленный вашему вниманию механизм
обеспечивает более эффективную организацию передачи данных между процессами.
С помощью системной функции msgctl процесс может запросить информацию о
статусе дескриптора сообщения, установить этот статус или удалить дескриптор
сообщения из системы. Синтаксис вызова функции:
msgctl(id,cmd,mstatbuf)
где id - дескриптор сообщения, cmd - тип команды, mstatbuf - адрес пользова-
тельской структуры, в которой будут храниться управляющие параметры или ре-
зультаты обработки запроса. Более подробно об аргументах функции пойдет речь
в Приложении.
Вернемся к примеру, представленному на Рисунке 11.8. Обслуживающий про-
цесс принимает сигналы и с помощью функции cleanup удаляет очередь сообщений
из системы. Если же им не было поймано ни одного сигнала или был получен
сигнал SIGKILL, очередь сообщений остается в системе, даже если на нее не
ссылается ни один из процессов. Дальнейшие попытки исключительно создания
новой очереди сообщений с данным ключом (идентификатором) не будут иметь ус-
пех до тех пор, пока старая очередь не будет удалена из системы.
E11.2.2 Разделение памятиF
Процессы могут взаимодействовать друг с другом непосредственно путем
разделения (совместного использования) участков виртуального адресного прос-
транства и обмена данными через разделяемую память. Системные функции для
работы с разделяемой памятью имеют много сходного с системными функциями для
работы с сообщениями. Функция shmget создает новую область разделяемой памя-
ти или возвращает адрес уже существующей области, функция shmat логически
присоединяет область к виртуальному адресному пространству процесса, функция
shmdt отсоединяет ее, а функция shmctl имеет дело с различными параметрами,
связанными с разделяемой памятью. Процессы ведут чтение и запись данных в
области разделяемой памяти, используя для этого те же самые машинные коман-
ды, что и при работе с обычной памятью. После присоединения к виртуальному
адресному пространству процесса область разделяемой памяти становится дос-
тупна так же, как любой участок виртуальной памяти; для доступа к находящим-
ся в ней данным не нужны обращения к каким-то дополнительным системным функ-
циям.
Синтаксис вызова системной функции shmget:
shmid = shmget(key,size,flag);
где size - объем области в байтах. Ядро использует key для ведения поиска в
таблице разделяемой памяти: если подходящая запись обнаружена и если разре-
шение на доступ имеется, ядро возвращает вызывающему процессу указанный в
записи дескриптор. Если запись не найдена и если пользователь установил флаг
IPC_CREAT, указывающий на необходимость создания новой области, ядро прове-
ряет нахождение размера области в установленных системой пределах и выделяет
область по алгоритму allocreg (раздел 6.5.2). Ядро записывает установки прав
доступа, размер области и указатель на соответствующую запись таблицы облас-
тей в таблицу разделяемой памяти (Рисунок 11.9) и устанавливает флаг, свиде-
тельствующий о том, что с областью не связана отдельная память. Области вы-
деляется память (таблицы страниц и т.п.) только тогда, когда процесс присое-
диняет область к своему адресному пространству. Ядро устанавливает также
341
флаг, говорящий о том, что по завершении последнего связанного с областью
процесса область не должна освобождаться. Таким образом, данные в разделяе-
мой памяти остаются в сохранности, даже если она не принадлежит ни одному из
процессов (как часть виртуального адресного пространства последнего).
Таблица раз- Таблица процессов -
деляемой па- Таблица областей частная таблица об-
мяти ластей процесса
ЪДДДДДДДДДДї ЪДДДДДДДДДДДДДДї ЪДДДДДДДДДї
і ДДДДЕДДДДї і і ЪДДДДЕДДДД і
ГДДДДДДДДДДґ ЪіД>ГДДДДДДДДДДДДДДґ<ДДДДЩ ГДДДДДДДДДґ
і ДДДДЕДДДЩі і і ЪДДДЕДДДД і
ГДДДДДДДДДДґ і ГДДДДДДДДДДДДДДґ<ДДДДїі ГДДДДДДДДДґ
і ДДДДЕДДї і і і АіДДДЕДДДД і
ГДДДДДДДДДДґ і і ГДДДДДДДДДДДДДДґ і ГДДДДДДДДДґ
і щ і і і і і і і і
і щ і і АД>ГДДДДДДДДДДДДДДґ і ГДДДДДДДДДґ
і щ і і і і і і і
і щ і АДДД>ГДДДДДДДДДДДДДДґ<ДДДДДЩ ГДДДДДДДДДґ
і щ і і і (после і і
і щ і ГДДДДДДДДДДДДДДґ shmat) ГДДДДДДДДДґ
і щ і і щ і і і
і щ і і щ і ГДДДДДДДДДґ
і щ і АДДДДДДДДДДДДДДЩ і щ і
і щ і і щ і
АДДДДДДДДДДЩ АДДДДДДДДДЩ
Рисунок 11.9. Структуры данных, используемые при разделении памяти
Процесс присоединяет область разделяемой памяти к своему виртуальному
адресному пространству с помощью системной функции shmat:
virtaddr = shmat(id,addr,flags);
Значение id, возвращаемое функцией shmget, идентифицирует область разделяе-
мой памяти, addr является виртуальным адресом, по которому пользователь хо-
чет подключить область, а с помощью флагов (flags) можно указать, предназна-
чена ли область только для чтения и нужно ли ядру округлять значение указан-
ного пользователем адреса. Возвращаемое функцией значение, virtaddr, предс-
тавляет собой виртуальный адрес, по которому ядро произвело подключение об-
ласти и который не всегда совпадает с адресом, указанным пользователем.
В начале выполнения системной функции shmat ядро проверяет наличие у
процесса необходимых прав доступа к области (Рисунок 11.10). Оно исследует
указанный пользователем адрес; если он равен 0, ядро выбирает виртуальный
адрес по своему усмотрению.
Область разделяемой памяти не должна пересекаться в виртуальном адресном
пространстве процесса с другими областями; следовательно, ее выбор должен
производиться разумно и осторожно. Так, например, процесс может увеличить
размер принадлежащей ему области данных с помощью системной функции brk, и
новая область данных будет содержать адреса, смежные с прежней областью; по-
этому, ядру не следует присоединять область разделяемой памяти слишком близ-
ко к области данных процесса. Так же не следует размещать область разделяе-
мой памяти вблизи от вершины стека, чтобы стек при своем последующем увели-
чении не залезал за ее пределы. Если, например, стек растет в направлении
увеличения адресов, лучше всего разместить область разделяемой памяти непос-
редственно перед началом области стека.
Ядро проверяет возможность размещения области разделяемой памяти в ад-
342
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і алгоритм shmat /* подключить разделяемую память */ і
і входная информация: (1) дескриптор области разделяемой і
і памяти і
і (2) виртуальный адрес для подключения і
і области і
і (3) флаги і
і выходная информация: виртуальный адрес, по которому областьі
і подключена фактически і
і { і
і проверить правильность указания дескриптора, права до- і
і ступа к области; і
і если (пользователь указал виртуальный адрес) і
і { і
і округлить виртуальный адрес в соответствии с фла- і
і гами; і
і проверить существование полученного адреса, размері
і области; і
і } і
і в противном случае /* пользователь хочет, чтобы ядро і
і * само нашло подходящий адрес */ і
і ядро выбирает адрес: в случае неудачи выдается і
і ошибка; і
і присоединить область к адресному пространству процесса і
і (алгоритм attachreg); і
і если (область присоединяется впервые) і
і выделить таблицы страниц и отвести память под нее і
і (алгоритм growreg); і
і вернуть (виртуальный адрес фактического присоединения і
і области); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.10. Алгоритм присоединения разделяемой памяти
ресном пространстве процесса и присоединяет ее с помощью алгоритма
attachreg. Если вызывающий процесс является первым процессом, который присо-
единяет область, ядро выделяет для области все необходимые таблицы, исполь-
зуя алгоритм growreg, записывает время присоединения в соответствующее поле
таблицы разделяемой памяти и возвращает процессу виртуальный адрес, по кото-
рому область была им подключена фактически.
Отсоединение области разделяемой памяти от виртуального адресного прост-
ранства процесса выполняет функция
shmdt(addr)
где addr - виртуальный адрес, возвращенный функцией shmat. Несмотря на то,
что более логичной представляется передача идентификатора, процесс использу-
ет виртуальный адрес разделяемой памяти, поскольку одна и та же область раз-
деляемой памяти может быть подключена к адресному пространству процесса нес-
колько раз, к тому же ее идентификатор может быть удален из системы. Ядро
производит поиск области по указанному адресу и отсоединяет ее от адресного
пространства процесса, используя алгоритм detachreg (раздел 6.5.7). Посколь-
ку в таблицах областей отсутствуют обратные указатели на таблицу разделяемой
памяти, ядру приходится просматривать таблицу разделяемой памяти в поисках
записи, указывающей на данную область, и записывать в соответствующее поле
время последнего отключения области.
Рассмотрим программу, представленную на Рисунке 11.11. В ней описывается
343
процесс, создающий область разделяемой памяти размером 128 Кбайт и дважды
присоединяющий ее к своему адресному пространству по разным виртуальным ад-
ресам. В "первую" область он записывает данные, а читает их из "второй" об-
ласти. На Рисунке 11.12 показан другой процесс, присоединяющий ту же область
(он получает только 64 Кбайта, таким образом, каждый процесс может использо-
вать разный объем области разделяемой памяти); он ждет момента, когда первый
процесс запишет в первое принадлежащее области слово любое отличное от нуля
значение, и затем принимается считывать данные из области. Первый процесс
делает "паузу" (pause), предоставляя второму процессу возможность выполне-
ния; когда первый процесс принимает сигнал, он удаляет область разделяемой
памяти из системы.
Процесс запрашивает информацию о состоянии области разделяемой памяти и
производит установку параметров для нее с помощью системной функции shmctl:
shmctl(id,cmd,shmstatbuf);
Значение id идентифицирует запись таблицы разделяемой памяти, cmd определяет
тип операции, а shmstatbuf является адресом пользовательской структуры, в
которую помещается информация о состоянии области. Ядро трактует тип опера-
ции точно так же, как и при управлении сообщениями. Удаляя область разделяе-
мой памяти, ядро освобождает соответствующую ей запись в таблице разделяемой
памяти и просматривает таблицу областей: если область не была присоединена
ни к одному из процессов, ядро освобождает запись таблицы и все выделенные
области ресурсы, используя для этого алгоритм freereg (раздел 6.5.6). Если
же область по-прежнему подключена к каким-то процессам (значение счетчика
ссылок на нее больше 0), ядро только сбрасывает флаг, говорящий о том, что
по завершении последнего связанного с нею процесса область не должна осво-
бождаться. Процессы, уже использующие область разделяемой памяти, продолжают
работать с ней, новые же процессы не могут присоединить ее. Когда все про-
цессы отключат область, ядро освободит ее. Это похоже на то, как в файловой
системе после разрыва связи с файлом процесс может вновь открыть его и про-
должать с ним работу.
E11.2.3 СемафорыF
Системные функции работы с семафорами обеспечивают синхронизацию выпол-
нения параллельных процессов, производя набор действий единственно над груп-
пой семафоров (средствами низкого уровня). До использования семафоров, если
процессу нужно было заблокировать некий ресурс, он прибегал к созданию с по-
мощью системной функции creat специального блокирующего файла. Если файл уже
существовал, функция creat завершалась неудачно, и процесс делал вывод о
том, что ресурс уже заблокирован другим процессом. Главные недостатки такого
подхода заключались в том, что процесс не знал, в какой момент ему следует
предпринять следующую попытку, а также в том, что блокирующие файлы случайно
оставались в системе в случае ее
аварийного завершения или перезагрузки.
Дийкстрой был опубликован алгоритм Деккера, описывающий реализацию сема-
форов как целочисленных объектов, для которых определены две элементарные
операции: P и V (см. [Dijkstra 68]). Операция P заключается в уменьшении
значения семафора в том случае, если оно больше 0, операция V - в увеличении
этого значения (и там, и там на единицу). Поскольку операции элементарные, в
любой момент времени для каждого семафора выполняется не более одной опера-
ции P или V. Связанные с семафорами системные функции являются обобщением
операций, предложенных Дийкстрой, в них допускается одновременное выполнение
нескольких операций, причем операции уменьшения и увеличения выполняются над
значениями, превышающими 1. Ядро выполняет операции комплексно; ни один из
посторонних процессов не сможет переустанавливать значения семафоров, пока
344
все операции не будут выполнены. Если ядро по каким-либо причинам не может
выполнить все операции, оно не выполняет ни одной; процесс приостанавливает
свою работу до тех пор, пока эта возможность не будет предоставлена.
Семафор в версии V системы UNIX состоит из следующих элементов:
* Значение семафора,
* Идентификатор последнего из процессов, работавших с семафором,
* Количество процессов, ожидающих увеличения значения семафора,
* Количество процессов, ожидающих момента, когда значение семафора станет
равным 0.
Для создания набора семафоров и получения доступа к ним используется
системная функция semget, для выполнения различных управляющих операций над
набором - функция semctl, для работы со значениями семафоров - функция
semop.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #include і
і #include і
і #include і
і #define SHMKEY 75 і
і #define K 1024 і
і int shmid; і
і і
і main() і
і { і
і int i, *pint; і
і char *addr1, *addr2; і
і extern char *shmat(); і
і extern cleanup(); і
і і
і for (i = 0; i < 20; i++) і
і signal(i,cleanup); і
і shmid = shmget(SHMKEY,128*K,0777іIPC_CREAT); і
і addr1 = shmat(shmid,0,0); і
і addr2 = shmat(shmid,0,0); і
і printf("addr1 Ox%x addr2 Ox%x\n",addr1,addr2); і
і pint = (int *) addr1; і
і і
і for (i = 0; i < 256, i++) і
і *pint++ = i; і
і pint = (int *) addr1; і
і *pint = 256; і
і і
і pint = (int *) addr2; і
і for (i = 0; i < 256, i++) і
і printf("index %d\tvalue %d\n",i,*pint++); і
і і
і pause(); і
і } і
і і
і cleanup() і
і { і
і shmctl(shmid,IPC_RMID,0); і
і exit(); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.11. Присоединение процессом одной и той же области
разделяемой памяти дважды
345
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #include і
і #include і
і #include і
і і
і #define SHMKEY 75 і
і #define K 1024 і
і int shmid; і
і і
і main() і
і { і
і int i, *pint; і
і char *addr; і
і extern char *shmat(); і
і і
і shmid = shmget(SHMKEY,64*K,0777); і
і і
і addr = shmat(shmid,0,0); і
і pint = (int *) addr; і
і і
і while (*pint == 0) і
і ; і
і for (i = 0; i < 256, i++) і
і printf("%d\n",*pint++); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.12. Разделение памяти между процессами
Таблица семафоров Массивы семафоров
ЪДДДДДДДї
і і ЪДДДВДДДВДДДВДДДВДДДВДДДВДДДї
і ГДДДДДДД>і 0 і 1 і 2 і 3 і 4 і 5 і 6 і
і і АДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
ГДДДДДДДґ
і і ЪДДДВДДДВДДДї
і ГДДДДДДД>і 0 і 1 і 2 і
і і АДДДБДДДБДДДЩ
ГДДДДДДДґ
і і ЪДДДї
і ГДДДДДДД>і 0 і
і і АДДДЩ
ГДДДДДДДґ
і і ЪДДДВДДДВДДДї
і ГДДДДДДД>і 0 і 1 і 2 і
і і АДДДБДДДБДДДЩ
ГДДДДДДДґ
і щ і
і щ і
і щ і
і щ і
і щ і
АДДДДДДДЩ
Рисунок 11.13. Структуры данных, используемые в работе над семафорами
346
Синтаксис вызова системной функции semget:
id = semget(key,count,flag);
где key, flag и id имеют тот же смысл, что и в других механизмах взаимодейс-
твия процессов (обмен сообщениями и разделение памяти). В результате выпол-
нения функции ядро выделяет запись, указывающую на массив семафоров и содер-
жащую счетчик count (Рисунок 11.13). В записи также хранится количество се-
мафоров в массиве, время последнего выполнения функций semop и semctl. Сис-
темная функция semget на Рисунке 11.14, например, создает семафор из двух
элементов.
Синтаксис вызова системной функции semop:
oldval = semop(id,oplist,count);
где id - дескриптор, возвращаемый функцией semget, oplist - указатель на
список операций, count - размер списка. Возвращаемое функцией значение
oldval является прежним значением семафора, над
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і #include і
і #include і
і #include і
і і
і #define SEMKEY 75 і
і int semid; і
і unsigned int count; і
і /* определение структуры sembuf в файле sys/sem.h і
і * struct sembuf { і
і * unsigned shortsem_num; і
і * short sem_op; і
і * short sem_flg; і
і }; */ і
і struct sembuf psembuf,vsembuf; /* операции типа P и V */і
і і
і main(argc,argv) і
і int argc; і
і char *argv[]; і
і { і
і int i,first,second; і
і short initarray[2],outarray[2]; і
і extern cleanup(); і
і і
і if (argc == 1) і
і { і
і for (i = 0; i < 20; i++) і
і signal(i,cleanup); і
і semid = semget(SEMKEY,2,0777іIPC_CREAT); і
і initarray[0] = initarray[1] = 1; і
і semctl(semid,2,SETALL,initarray); і
і semctl(semid,2,GETALL,outarray); і
і printf("начальные значения семафоров %d %d\n", і
і outarray[0],outarray[1]); і
і pause(); /* приостанов до получения сигнала */ і
і } і
і і
і /* продолжение на следующей странице */ і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.14. Операции установки и снятия блокировки
347
которым производилась операция. Каждый элемент списка операций имеет следую-
щий формат:
* номер семафора, идентифицирующий элемент массива семафоров, над которым
выполняется операция,
* код операции,
* флаги.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і else if (argv[1][0] == 'a') і
і { і
і first = 0; і
і second = 1; і
і } і
і else і
і { і
і first = 1; і
і second = 0; і
і } і
і і
і semid = semget(SEMKEY,2,0777); і
і psembuf.sem_op = -1; і
і psembuf.sem_flg = SEM_UNDO; і
і vsembuf.sem_op = 1; і
і vsembuf.sem_flg = SEM_UNDO; і
і і
і for (count = 0; ; count++) і
і { і
і psembuf.sem_num = first; і
і semop(semid,&psembuf,1); і
і psembuf.sem_num = second; і
і semop(semid,&psembuf,1); і
і printf("процесс %d счетчик %d\n",getpid(),count); і
і vsembuf.sem_num = second; і
і semop(semid,&vsembuf,1); і
і vsembuf.sem_num = first; і
і semop(semid,&vsembuf,1); і
і } і
і } і
і і
і cleanup() і
і { і
і semctl(semid,2,IPC_RMID,0); і
і exit(); і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.14. Операции установки и снятия блокировки (продолжение)
Ядро считывает список операций oplist из адресного пространства задачи и
проверяет корректность номеров семафоров, а также наличие у процесса необхо-
димых разрешений на чтение и корректировку семафоров (Рисунок 11.15). Если
таких разрешений не имеется, системная функция завершается неудачно. Если
ядру приходится приостанавливать свою работу при обращении к списку опера-
ций, оно возвращает семафорам их прежние значения и находится в состоянии
приостанова до наступления ожидаемого события, после чего систем-
ная функция запускается вновь. Поскольку ядро хранит коды операций над сема-
форами в глобальном списке, оно вновь считывает этот список из пространства
348
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і алгоритм semop /* операции над семафором */ і
і входная информация: (1) дескриптор семафора і
і (2) список операций над семафором і
і (3) количество элементов в списке і
і выходная информация: исходное значение семафора і
і { і
і проверить корректность дескриптора семафора; і
і start: считать список операций над семафором из простран- і
і ства задачи в пространство ядра; і
і проверить наличие разрешений на выполнение всех опера- і
і ций; і
і і
і для (каждой операции в списке) і
і { і
і если (код операции имеет положительное значение) і
і { і
і прибавить код операции к значению семафора; і
і если (для данной операции установлен флаг UNDO)і
і скорректировать структуру восстановления і
і для данного процесса; і
і вывести из состояния приостанова все процессы, і
і ожидающие увеличения значения семафора; і
і } і
і в противном случае если (код операции имеет отрица-і
і тельное значение) і
і { і
і если (код операции + значение семафора >= 0) і
і { і
і прибавить код операции к значению семафо- і
і ра; і
і если (флаг UNDO установлен) і
і скорректировать структуру восстанов- і
і ления для данного процесса; і
і если (значение семафора равно 0) і
і /* продолжение на следующей страни- і
і * це */ і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.15. Алгоритм выполнения операций над семафором
задачи, когда перезапускает системную функцию. Таким образом, операции вы-
полняются комплексно - или все за один сеанс или ни одной.
Ядро меняет значение семафора в зависимости от кода операции. Если код
операции имеет положительное значение, ядро увеличивает значение семафора и
выводит из состояния приостанова все процессы, ожидающие наступления этого
события. Если код операции равен 0, ядро проверяет значение семафора: если
оно равно 0, ядро переходит к выполнению других операций; в противном случае
ядро увеличивает число приостановленных процессов, ожидающих, когда значение
семафора станет нулевым, и "засыпает". Если код операции имеет отрицательное
значение и если его абсолютное значение не превышает значение семафора, ядро
прибавляет код операции (отрицательное число) к значению семафора. Если ре-
зультат равен 0, ядро выводит из состояния приостанова все процессы, ожидаю-
щие обнуления значения семафора. Если результат меньше абсолютного
значения кода операции, ядро приостанавливает процесс до тех пор, пока зна-
чение семафора не увеличится. Если процесс приостанавливается посреди опера-
ции, он имеет приоритет, допускающий прерывания; следовательно, получив сиг-
нал, он выходит из этого состояния.
349
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і вывести из состояния приостанова все і
і процессы, ожидающие обнуления значе-і
і ния семафора; і
і продолжить; і
і } і
і выполнить все произведенные над семафором в і
і данном сеансе операции в обратной последова- і
і тельности (восстановить старое значение сема- і
і фора); і
і если (флаги не велят приостанавливаться) і
і вернуться с ошибкой; і
і приостановиться (до тех пор, пока значение се- і
і мафора не увеличится); і
і перейти на start; /* повторить цикл с самого і
і * начала * / і
і } і
і в противном случае /* код операции равен нулю */і
і { і
і если (значение семафора отлично от нуля) і
і { і
і выполнить все произведенные над семафором і
і в данном сеансе операции в обратной по- і
і следовательности (восстановить старое і
і значение семафора); і
і если (флаги не велят приостанавливаться) і
і вернуться с ошибкой; і
і приостановиться (до тех пор, пока значениеі
і семафора не станет нулевым); і
і перейти на start; /* повторить цикл */ і
і } і
і } і
і } /* конец цикла */ і
і /* все операции над семафором выполнены */ і
і скорректировать значения полей, в которых хранится вре-і
і мя последнего выполнения операций и идентификаторы і
і процессов; і
і вернуть исходное значение семафора, существовавшее в і
і момент вызова функции semop; і
і } і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рисунок 11.15. Алгоритм выполнения операций над семафором (продолжение)
Перейдем к программе, представленной на Рисунке 11.14, и предположим,
что пользователь исполняет ее (под именем a.out) три раза в следующем поряд-
ке:
a.out &
a.out a &
a.out b &
Если программа вызывается без параметров, процесс создает набор семафо-
ров из двух элементов и присваивает каждому семафору значение, равное 1. За-
тем процесс вызывает функцию pause и приостанавливается для получения сигна-
ла, после чего удаляет семафор из системы (cleanup). При выполнении програм-
мы с параметром 'a' процесс (A) производит над семафорами в цикле четыре
операции: он уменьшает на единицу значение семафора 0, то же самое делает с
семафором 1, выполняет команду вывода на печать и вновь увеличивает значения
семафоров 0 и 1. Если бы процесс попытался уменьшить значение семафора, рав-
350
ное 0, ему пришлось бы приостановиться, следовательно, семафор можно считать
захваченным (недоступным для уменьшения). Поскольку исходные значения сема-
форов были равны 1 и поскольку к семафорам не было обращений со стороны дру-
гих процессов, процесс A никогда не приостановится, а значения семафоров бу-
дут изменяться только между 1 и 0. При выполнении программы с параметром 'b'
процесс (B) уменьшает значения семафоров 0 и 1 в порядке, обратном ходу вы-
полнения процесса A. Когда процессы A и B выполняются параллельно, может
сложиться ситуация, в которой процесс A захватил семафор 0 и хочет захватить
семафор 1, а процесс B захватил семафор 1 и хочет захватить семафор 0. Оба
процесса перейдут в состояние приостанова, не имея возможности продолжить
свое выполнение. Возникает взаимная блокировка, из которой процессы могут
выйти только по получении сигнала.
Чтобы предотвратить возникновение подобных проблем, процессы могут вы-
полнять одновременно несколько операций над семафорами. В последнем примере
желаемый эффект достигается благодаря использованию следующих операторов:
struct sembuf psembuf[2];
psembuf[0].sem_num = 0;
psembuf[1].sem_num = 1;
psembuf[0].sem_op = -1;
psembuf[1].sem_op = -1;
semop(semid,psembuf,2);
Psembuf - это список операций, выполняющих одновременное уменьшение значений
семафоров 0 и 1. Если какая-то операция не может выполняться, процесс приос-
танавливается. Так, например, если значение семафора 0 равно 1, а значение
семафора 1 равно 0, ядро оставит оба значения неизменными до тех пор, пока
не сможет уменьшить и то, и другое.
Установка флага IPC_NOWAIT в функции semop имеет следующий смысл: если
ядро попадает в такую ситуацию, когда процесс должен приостановить свое вы-
полнение в ожидании увеличения значения семафора выше определенного уровня
или, наоборот, снижения этого значения до 0, и если при этом флаг IPC_NOWAIT
установлен, ядро выходит из функции с извещением об ошибке. Таким образом,
если не приостанавливать процесс в случае невозможности выполнения отдельной
операции, можно реализовать условный тип семафора.
Если процесс выполняет операцию над семафором, захватывая при этом неко-
торые ресурсы, и завершает свою работу без приведения семафора в исходное
состояние, могут возникнуть опасные ситуации. Причинами возникновения таких
ситуаций могут быть как ошибки программирования, так и сигналы, приводящие к
внезапному завершению выполнения процесса. Если после того, как процесс
уменьшит значения семафоров, он получит сигнал kill, восстановить прежние
значения процессу уже не удастся, поскольку сигналы данного типа не анализи-
руются процессом. Следовательно, другие процессы, пытаясь обратиться к сема-
форам, обнаружат, что последние заблокированы, хотя сам заблокировавший их
процесс уже прекратил свое существование. Чтобы избежать возникновения по-
добных ситуаций, в функции semop процесс может установить флаг SEM_UNDO;
когда процесс завершится, ядро даст обратный ход всем операциям, выполненным
процессом. Для этого в распоряжении у ядра имеется таблица, в которой каждо-
му процессу в системе отведена отдельная запись. Запись таблицы содержит
указатель на группу структур восстановле-
ния, по одной структуре на каждый используемый процессом семафор (Рисунок
11.16). Каждая структура восстановления состоит из трех элементов - иденти-
фикатора семафора, его порядкового номера в наборе и установочного значения.
Ядро выделяет структуры восстановления динамически, во время первого вы-
полнения системной функции semop с установленным флагом SEM_UNDO. При после-
дующих обращениях к функции с тем же флагом ядро просматривает структуры
восстановления для процесса в поисках структуры с тем же самым идентификато-
351
Заголовки частных структур
восстановления Структуры восстановления
ЪДДДДДДї
і щ і
і щ і
і щ і
і щ і ЪДДДДДДДДДДї ЪДДДДДДДДДДї ЪДДДДДДДДДДї
ГДДДДДДґ іДескрипторі іДескрипторі іДескрипторі
і ГДД>і Номер ГДД>і Номер ГДД>і Номер і
ГДДДДДДґ і Значение і і Значение і і Значение і
і і АДДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДДДДДДДЩ
і і ЪДДДДДДДДДДї
ГДДДДДДґ іДескрипторі
і ГДД>і Номер і
ГДДДДДДґ і Значение і
і щ і АДДДДДДДДДДЩ
і щ і
і щ і
і щ і
АДДДДДДЩ
Рисунок 11.16. Структуры восстановления семафоров
ром и порядковым номером семафора, что и в формате вызова функции. Если
структура обнаружена, ядро вычитает значение произведенной над семафором
операции из установочного значения. Таким образом, в структуре восстановле-
ния хранится результат вычитания суммы значений всех операций, произведенных
над семафором, для которого установлен флаг SEM_UNDO. Если соответствующей
структуры нет, ядро создает ее, сортируя при этом список структур по иденти-
фикаторам и номерам семафоров. Если установочное значение становится равным
0, ядро удаляет структуру из списка. Когда процесс завершается, ядро вызыва-
ЪДДДДДДДДДДДДДДДВВДДДДДДДї ЪДДДДДДДДДДДДДДДВВДДДДДДДВДДДДДДДї
і идентификатор іі і і идентификатор іі і і
і семафора іі semid і і семафора іі semid і semid і
ГДДДДДДДДДДДДДДДЕЕДДДДДДДґ ГДДДДДДДДДДДДДДДЕЕДДДДДДДЕДДДДДДДґ
і номер семафораіі 0 і і номер семафораіі 0 і 1 і
ГДДДДДДДДДДДДДДДЕЕДДДДДДДґ ГДДДДДДДДДДДДДДДЕЕДДДДДДДЕДДДДДДДґ
і установочное іі і і установочное іі і і
і значение іі 1 і і значение іі 1 і 1 і
АДДДДДДДДДДДДДДДББДДДДДДДЩ АДДДДДДДДДДДДДДДББДДДДДДДБДДДДДДДЩ
(а) После первой операции (б) После второй операции
ЪДДДДДДДДДДДДДДДВВДДДДДДДї
і идентификатор іі і
і семафора іі semid і
ГДДДДДДДДДДДДДДДЕЕДДДДДДДґ
і номер семафораіі 0 і пусто
ГДДДДДДДДДДДДДДДЕЕДДДДДДДґ
і установочное іі і
і значение іі 1 і
АДДДДДДДДДДДДДДДББДДДДДДДЩ
(в) После третьей операции (г) После четвертой операции
Рисунок 11.17. Последовательность состояний списка структур восстановления
352
ет специальную процедуру, которая просматривает все связанные с процессом
структуры восстановления и выполняет над указанным семафором все обусловлен-
ные действия.
Ядро создает структуру восстановления всякий раз, когда процесс уменьша-
ет значение семафора, а удаляет ее, когда процесс увеличивает значение сема-
фора, поскольку установочное значение
структуры равно 0. На Рисунке 11.17 показана последовательность состояний
списка структур при выполнении программы с параметром 'a'. После первой опе-
рации процесс имеет одну структуру, состоящую из идентификатора semid, номе-
ра семафора, равного 0, и установочного значения, равного 1, а после второй
операции появляется вторая структура с номером семафора, равным 1, и устано-
вочным значением, равным 1. Если процесс неожиданно завершается, ядро прохо-
дит по всем структурам и прибавляет к каждому семафору по единице, восста-
навливая их значения в 0. В частном случае ядро уменьшает установочное зна-
чение для семафора 1 на третьей операции, в соответствии с увеличением зна-
чения самого семафора, и удаляет всю структуру целиком, поскольку установоч-
ное значение становится нулевым. После четвертой операции у процесса больше
нет структур восстановления, поскольку все установочные значения стали нуле-
выми.
Векторные операции над семафорами позволяют избежать взаимных блокиро-
вок, как было показано выше, однако они представляют известную трудность для
понимания и реализации, и в большинстве приложений полный набор их возмож-
ностей не является обязательным. Программы, испытывающие потребность в ис-
пользовании набора семафоров, сталкиваются с возникновением взаимных блоки-
ровок на пользовательском уровне, и ядру уже нет необходимости поддерживать
такие сложные формы системных функций.
Синтаксис вызова системной функции semctl:
semctl(id,number,cmd,arg);
Параметр arg объявлен как объединение типов данных:
union semunion {
int val;
struct semid_ds *semstat; /* описание типов см. в При-
* ложении */
unsigned short *array;
} arg;
Ядро интерпретирует параметр arg в зависимости от значения параметра
cmd, подобно тому, как интерпретирует команды ioctl (глава 10). Типы дейст-
вий, которые могут использоваться в параметре cmd: получить или установить
значения управляющих параметров (права доступа и др.), установить значения
одного или всех семафоров в наборе, прочитать значения семафоров. Подробнос-
ти по каждому действию содержатся в Приложении. Если указана команда удале-
ния, IPC_RMID, ядро ведет поиск всех процессов, содержащих структуры восста-
новления для данного семафора, и удаляет соответствующие структуры из систе-
мы. Затем ядро инициализирует используемые семафором структуры данных и вы-
водит из состояния приостанова все процессы, ожидающие наступления некоторо-
го связанного с семафором события: когда процессы возобновляют свое выполне-
ние, они обнаруживают, что идентификатор семафора больше не является коррек-
тным, и возвращают вызывающей программе ошибку.
E11.2.4 Общие замечанияF
Механизм функционирования файловой системы и механизмы взаимодействия
353
процессов имеют ряд общих черт. Системные функции типа "get" похожи на функ-
ции creat и open, функции типа "control" предоставляют возможность удалять
дескрипторы из системы, чем похожи на функцию unlink. Тем не менее, в меха-
низмах взаимодействия процессов отсутствуют операции, аналогичные операциям,
выполняемым системной функцией close. Следовательно, ядро не располагает
сведениями о том, какие процессы могут использовать механизм IPC, и, дейст-
вительно, процессы могут прибегать к услугам этого механизма, если правильно
угадывают соответствующий идентификатор и если у них имеются необходимые
права доступа, даже если они не выполнили предварительно функцию типа "get".
Ядро не может автоматически очищать неиспользуемые структуры механизма взаи-
модействия процессов, поскольку ядру неизвестно, какие из этих структур
больше не нужны. Таким образом, завершившиеся вследствие возникновения ошиб-
ки процессы могут оставить после себя ненужные и неиспользуемые структуры,
перегружающие и засоряющие систему. Несмотря на то, что в структурах меха-
низма взаимодействия после завершения существования процесса ядро может сох-
ранить информацию о состоянии и данные, лучше все-таки для этих целей ис-
пользовать файлы.
Вместо традиционных, получивших широкое распространение файлов механизмы
взаимодействия процессов используют новое пространство имен, состоящее из
ключей (keys). Расширить семантику ключей на всю сеть довольно трудно, пос-
кольку на разных машинах ключи могут описывать различные объекты. Короче го-
воря, ключи в основном предназначены для использования в одномашинных систе-
мах. Имена файлов в большей степени подходят для распределенных систем (см.
главу 13). Использование ключей вместо имен файлов также свидетельствует о
том, что средства взаимодействия процессов являются "вещью в себе", полезной
в специальных приложениях, но не имеющей тех возможностей, которыми облада-
ют, например, каналы и файлы. Большая часть функциональных возможностей,
предоставляемых данными средствами, может быть реализована с помощью других
системных средств, поэтому включать их в состав ядра вряд ли следовало бы.
Тем не менее, их использование в составе пакетов прикладных программ тесного
взаимодействия дает лучшие результаты по сравнению со стандартными файловыми
средствами (см. Упражнения).
E11.3 ВЗАИМОДЕЙСТВИЕ В СЕТИF
Программы, поддерживающие межмашинную связь, такие, как электронная поч-
та, программы дистанционной пересылки файлов и удаленной регистрации, издав-
на используются в качестве специальных средств организации подключений и ин-
формационного обмена. Так, например, стандартные программы, работающие в
составе электронной почты, сохраняют текст почтовых сообщений пользователя в
отдельном файле (для пользователя "mjb" этот файл имеет имя
"/usr/mail/mjb"). Когда один пользователь посылает другому почтовое сообще-
ние на ту же машину, программа mail (почта) добавляет сообщение в конец фай-
ла адресата, используя в целях сохранения целостности различные блокирующие
и временные файлы. Когда адресат получает почту, программа mail открывает
принадлежащий ему почтовый файл и читает сообщения. Для того, чтобы послать
сообщение на другую машину, программа mail должна в конечном итоге отыскать
на ней соответствующий почтовый файл. Поскольку программа не может работать
с удаленными файлами непосредственно, процесс, протекающий на другой машине,
должен действовать в качестве агента локального почтового процесса; следова-
тельно, локальному процессу необходим способ связи со своим удаленным аген-
том через межмашинные границы. Локальный процесс является клиентом удаленно-
го обслуживающего (серверного) процесса.
Поскольку в системе UNIX новые процессы создаются с помощью системной
функции fork, к тому моменту, когда клиент попытается выполнить подключение,
обслуживающий процесс уже должен существовать. Если бы в момент создания но-
вого процесса удаленное ядро получало запрос на подключение (по каналам меж-
машинной связи), возникла бы несогласованность с архитектурой системы. Чтобы
354
избежать этого, некий процесс, обычно init, порождает обслуживающий процесс,
который ведет чтение из канала связи, пока не получает запрос на обслужива-
ние, после чего в соответствии с некоторым протоколом выполняет установку
соединения. Выбор сетевых средств и протоколов обычно выполняют программы
клиента и сервера, основываясь на информации, хранящейся в прикладных базах
данных; с другой стороны, выбранные пользователем средства могут быть зако-
дированы в самих программах.
В качестве примера рассмотрим программу uucp, которая обслуживает пере-
сылку файлов в сети и исполнение команд на удалении (см. [Nowitz 80]). Про-
цесс-клиент запрашивает в базе данных адрес и другую маршрутную информацию
(например, номер телефона), открывает автокоммутатор, записывает или прове-
ряет информацию в дескрипторе открываемого файла и вызывает удаленную маши-
ну. Удаленная машина может иметь специальные линии, выделенные для использо-
вания программой uucp; выполняющийся на этой машине процесс init порождает
getty-процессы - серверы, которые управляют линиями и получают извещения о
подключениях. После выполнения аппаратного подключения процесс-клиент регис-
трируется в системе в соответствии с обычным протоколом регистрации:
getty-процесс запускает специальный интерпретатор команд, uucico, указанный
в файле "/etc/passwd", а процесс-клиент передает на удаленную машину после-
довательность команд, тем самым заставляя ее исполнять процессы от имени ло-
кальной машины.
Сетевое взаимодействие в системе UNIX представляет серьезную проблему,
поскольку сообщения должны включать в себя как информационную, так и управ-
ляющую части. В управляющей части сообщения может располагаться адрес назна-
чения сообщения. В свою очередь, структура адресных данных зависит от типа
сети и используемого протокола. Следовательно, процессам нужно знать тип се-
ти, а это идет вразрез с тем принципом, по которому пользователи не должны
обращать внимания на тип файла, ибо все устройства для пользователей выгля-
дят как файлы. Традиционные методы реализации сетевого взаимодействия при
установке управляющих параметров в сильной степени полагаются на помощь сис-
темной функции ioctl, однако в разных типах сетей этот момент воплощается
по-разному. Отсюда возникает нежелательный побочный эффект, связанный с тем,
что программы, разработанные для одной сети, в других сетях могут не зарабо-
тать.
Чтобы разработать сетевые интерфейсы для системы UNIX, были предприняты
значительные усилия. Реализация потоков в последних редакциях версии V рас-
полагает элегантным механизмом поддержки сетевого взаимодействия, обеспечи-
вающим гибкое сочетание отдельных модулей протоколов и их согласованное ис-
пользование на уровне задач. Следующий раздел посвящен краткому описанию ме-
тода решения данных проблем в системе BSD, основанного на использовании
гнезд.
E11.4 ГНЕЗДАF
В предыдущем разделе было показано, каким образом взаимодействуют между
собой процессы, протекающие на разных машинах, при этом обращалось внимание
на то, что способы реализации взаимодействия могут быть различаться в зави-
симости от используемых протоколов и сетевых средств. Более того, эти спосо-
бы не всегда применимы для обслуживания взаимодействия процессов, выполняю-
щихся на одной и той же машине, поскольку в них предполагается существование
обслуживающего (серверного) процесса, который при выполнении системных функ-
ций 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 определяет, с каким клиентом устанавливается связь в
данный момент.
E11.5 ВЫВОДЫF
Мы рассмотрели несколько форм взаимодействия процессов. Первой формой,
положившей начало обсуждению, явилась трассировка процессов - взаимодействие
двух процессов, выступающее в качестве полезного средства отладки программ.
При всех своих преимуществах трассировка процессов с помощью функции ptrace
все же достаточно дорогостоящее и примитивное мероприятие, поскольку за один
сеанс функция способна передать строго ограниченный объем данных, требуется
большое количество переключений контекста, взаимодействие ограничивается
только формой отношений родитель-потомок, и наконец, сама трассировка произ-
водится только по обоюдному согласию участвующих в ней процессов. В версии V
системы UNIX имеется пакет взаимодействия процессов (IPC), включающий в себя
механизмы обмена сообщениями, работы с семафорами и разделения памяти. К со-
жалению, все эти механизмы имеют узкоспециальное назначение, не имеют хоро-
шей стыковки с другими элементами операционной системы и не действуют в се-
ти. Тем не менее, они используются во многих приложениях и по сравнению с
другими схемами отличаются более высокой эффективностью.
Система UNIX поддерживает широкий спектр вычислительных сетей. Традици-
онные методы согласования протоколов в сильной степени полагаются на помощь
системной функции ioctl, однако в разных типах сетей они реализуются по-раз-
ному. В системе BSD имеются системные функции для работы с гнездами, поддер-
живающие более универсальную структуру сетевого взаимодействия. В будущем в
версию V предполагается включить описанный в главе 10 потоковый механизм,
повышающий согласованность работы в сети.
E11.6 УПРАЖНЕНИЯF
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
|
|