ГЛАВА 11. ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ Наличие механизмов взаимодействия дает произвольным процессам возмож- ность осуществлять обмен данными и синхронизировать свое выполнение с други- ми процессами. Мы уже рассмотрели несколько форм взаимодействия процессов, такие как канальная связь, использование поименованных каналов и посылка сигналов. Каналы (непоименованные) имеют недостаток, связанный с тем, что они известны только потомкам процесса, вызвавшего системную функцию pipe: не имеющие родственных связей процессы не могут взаимодействовать между собой с помощью непоименованных каналов. Несмотря на то, что поименованные каналы позволяют взаимодействовать между собой процессам, не имеющим родственных связей, они не могут использоваться ни в сети (см. главу 13), ни в организа- ции множественных связей между различными группами взаимодействующих процес- сов: поименованный канал не поддается такому мультиплексированию, при кото- ром у каждой пары взаимодействующих процессов имелся бы свой выделенный ка- нал. Произвольные процессы могут также связываться между собой благодаря по- сылке сигналов с помощью системной функции kill, однако такое "сообщение" состоит из одного только номера сигнала. В данной главе описываются другие формы взаимодействия процессов. В на- чале речь идет о трассировке процессов, о том, каким образом один процесс следит за ходом выполнения другого процесса, затем рассматривается пакет IPC: сообщения, разделяемая память и семафоры. Делается обзор традиционных методов сетевого взаимодействия процессов, выполняющихся на разных машинах, и, наконец, дается представление о "гнездах", применяющихся в системе BSD. Вопросы сетевого взаимодействия, имеющие специальный характер, такие как протоколы, адресация и др., не рассматриваются, поскольку они выходят за рамки настоящей работы. 11.1 ТРАССИРОВКА ПРОЦЕССОВ В системе 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-программ, предоставляемая только суперпользовате- лю, реализуется как составная часть традиционного механизма защиты файлов. 11.2 ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В ВЕРСИИ V СИСТЕМЫ Пакет 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", запрашиваю- щая информацию о состоянии записи, изменяющая эту информацию или удаляю- щая запись из системы. Когда процесс запрашивает информацию о состоянии записи, ядро проверяет, имеет ли процесс разрешение на чтение записи, после чего копирует данные из записи таблицы по адресу, указанному поль- зователем. При установке значений принадлежащих записи параметров ядро проверяет, совпадают ли между собой пользовательский код идентификации процесса и идентификатор пользователя (или создателя), указанный в запи- си, не запущен ли процесс под управлением суперпользователя; одного раз- решения на запись недостаточно для установки параметров. Ядро копирует сообщенную пользователем информацию в запись таблицы, устанавливая зна- чения пользовательского и группового кодов идентификации, режимы доступа и другие параметры (в зависимости от типа механизма). Ядро не изменяет значения полей, описывающих пользовательский и групповой коды идентифи- кации создателя записи, поэтому пользователь, создавший запись, сохраня- ет управляющие права на нее. Пользователь может удалить запись, либо ес- ли он является суперпользователем, либо если идентификатор процесса сов- падает с любым из идентификаторов, указанных в структуре записи. Ядро увеличивает номер дескриптора, чтобы при следующем назначении записи ей был присвоен новый дескриптор. Следовательно, как уже ранее говорилось, если процесс попытается обратиться к записи по старому дескриптору, выз- ванная им функция получит отказ. 11.2.1 Сообщения С сообщениями работают четыре системных функции: 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);