Межпроцессные коммуникации

Если задуматься над вопросом, что в наши дни является ключевой компонентой в области передовых компьютерных технологий, то без сомнения можно было бы ответить, что такой технологией в наши дни является технология "клиент-сервер". Действительно, эта универсальная модель служит основой построения любых сколь угодно сложных систем, в том числе и сетевых. Пользуясь известным выражением, можно было бы сказать, что технология "клиент-сервер" является ключевым звеном в цепи, ухватившись за которое можно вытащить всю цепь. Разработчики СУБД, коммуникационных систем, систем электронной почты, банковских систем и т.д. во всем мире используют эту технологию. В этом смысле ОС UNIX является для разработчиков системного и прикладного программного обеспечения идеальным средством, потому что в своей основе наиболее полно отвечает требованиям технологии "клиент-сервер", далеко опережая при этом своих основных конкурентов. Для построения моделей типа "клиент-сервер" в UNIX существуют великолепные возможности:

сигналы;
семафоры;
программные каналы;
именованные программные каналы;
очереди сообщений;
сегменты разделяемой памяти;
специальные команды (write, cu, mail);
средства межмашинного взаимодействия (uucp, tcp/ip, nfs, rfs).

В первоначальных версиях UNIX в момент ее зарождения этих средств либо вообще не существовало, либо они существовали в зачаточном состоянии. Не этим ли объясняется некий скептицизм в отношении UNIX со стороны программистов старшего поколения даже и в наши дни, что уж совсем необъяснимо, особенно после того, как System V, начиная с версии 3.2, явилась перед нами во всем богатстве своего могущественного арсенала. Рассмотрим эти средства более подробно.

Сигналы

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

обработка исключительных ситуаций;
средства обработки внешних и внутренних прерываний;
средства управления системой прерываний (маскирование и демаскирование).

Всем этим требованиям в UNIX отвечает механика сигналов, которая может не только воспринимать и обрабатывать сигналы, но и порождать их и посылать на другие машины (процессы). Сигналы могут быть синхронными, когда инициатор сигнала - сам процесс, и асинхронными, когда инициатор возникновения сигнала - интерактивный пользователь за терминалом. Источником асинхронных сигналов может быть также ядро, когда оно контролирует определенные состояния аппаратуры рассматриваемые как ошибочные. Для удобства использования каждый сигнал закодирован и цифровое значение сигнала с использованием мнемонической формы записи хранится в файле signal.h

Нижеследующий перечень сигналов представлен в порядке возрастания значений сигналов (целое число в скобках).

   ИМЯ				СОДЕРЖАНИЕ			РЕАКЦИЯ

SIGHUP(1)	Вырабатывается при отключении связи с	Закончить процесс,
		терминалом. Рассылается всем процессам	принявший сигнал.
		TGID
SIGINT(2)	При нажатии определенной клавиши.	Закончить процесс
		Всем процессам TGID
SIGQUIT(3)	При нажатии клавиши quit. Всем           Здесь и далее
		процессам TGID				закончить процесс
SIGILL(4)	При возникновении аппаратных некон-
		тролируемых состояний. Текущему PID
SIGTRAP(5)	При трассировке. Текущему PID
SIGIOT(6)	При неисправности в аппаратуре.
		Текущему PID
SIGTEMT(7)	При выполнении команды EMT. Текущему
		PID
SIGFPE(8)	При обработке чисел с плавающей
		точкой. Текущему PID
SIGKILL(9)	При выдаче системного вызова kill.
		Адресуемому PID
SIGBUS(10)	При ошибке в косвенной адресации.
		Текущему PID
SIGSEGV(11)	При выходе за пределы сегмента.
		Текущему PID
SIGSYS(12)	При неверном системном вызове.
		Текущему PID
SIGPIPE(13)	При записи в Pipe, когда нет читающего
		процесса. Текущему PID записи в канал
SIGALARM(14)	При окончании временной установки.
		Посылается к PID установки
SIGTERM(15)	При выполнении команды kill в команд-
		ном режиме. К адресуемому PID
SIGUSR1(16)	При системном вызове kill.
SIGUSR2(17)	К адресуемому PID
SIGCLD(18)	При окончании "процесса-сына".		Сброс сигнала
		К "процессу-отцу"
SIGPWR(19)	При уменьшении напряжения в сети.	В зависимости от
		В зависимости от реализации		реализации.
SIGPOOL(22)	При регистрации события в Stream-	Закончить процесс.
		устройстве. К процессу, работающему
		с потоком.
---------------------------------------------------------------------------

Средством посылки и восприятия сигналов в ОС UNIX служат два системных вызова kill и signal. Системный вызов kill посылает выбранному процессу сигнал с определенным номером:


int kill (int pid, int sig);


Системный вызов signal воспринимает и идентифицирует сигнал:


int (* signal (int sig, void (* func (int))) (int));


Возможности системного вызова signal позволяют обрабатывать сигналы следующими способами:


  1. Реакция по умолчанию
  2. Замаскировать сигнал (т.е. проигнорировать его). Для указания указания возможности маскирования второму аргументу присваивается специальное значение SIG_IGN.
  3. Перехватить посланный процессу сигнал и написать собственную программу обработки прерывания по асинхронно посланному сигналу.

Используя технику сигналов, можно решать самые разнообразные задачи, в том числе можно даже предотвратить такую неприятную особенность, как появление в системе "процессов-зомби".

Семафоры

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

Семафоры - это достаточно мощное средство синхронизации процессов.

Зачем нужны семафоры?

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

Всегда семафоры - это средства задержки процессов, которые тормозятся или "засыпают" на светофорах и "пробуждаются" и приходят в движение при изменении состояния семафоров. Таким образом, дескриптор семафора является информационной управляющей структурой ipc-perm и хранится в файле ipc.h.

Процесс может создать семафор с помощью системного вызова semget.

В результате успешного выполнения semget возвращается semid - идентификатор созданного или уже имеющегося семафора. После создания семафора любой процесс, который знает его идентификатор semid, может на него воздействовать с помощью системного вызова semctl, который осуществляет настройку и проверку состояния множественного семафора.

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

struct sembuf {
  ushort sem_num; /* номер элемента семафора */
  short sem_op;   /* конкретная операция над элементом семафора */
  short sem_flg;  /* флаги */
	      }
Различают три типа операций:
  1. sem_op > 0 - освобождение процессом, выполнившим semop, ресурса, контролируемого данным элементом семафора. Текущее значение элемента семафора semval инкрементируется на величину sem_op.
  2. sem_op = 0 - процесс, исполняющий такой вызов semop, будет переведен в ожидание до тех пор, пока не освободится ресурс, т. е. semval не станет равным нулю.
  3. semop < 0 - процесс, вызвавший выполнение этой операции, ждет когда sem_op станет больше или равно semval. В данном случае происходит декремент текущего значения семафора на абсолютную величину sem_op. Эта операция используется при захвате ресурса.

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

В этом случае необходимо использовать флаг SEM_UNDO.

Программные каналы (Pipes)

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

Вообще, pipe - это роскошная штука! Вспомните хотя бы директиву RCVD из доброй и старой RSX11M на PDP-11, которая принимала всего-то, кажется, 17 слов из другой задачи, и сравните это с возможностью pipes в UNIX, которые существовали в ней всегда, с момента ее зарождения. Однако в новых версиях System V появилось много новых замечательных особенностей.

Во-первых, снята проблема взаимодействия (как по сигналам, так и по программным каналам) только "процессов-родственников", что принципиально ограничивало общность средств взаимодействия процессов.

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

По сути pipe - это некоторый специальный файл, в который можно писать информацию или читать из него. Это укладывается в общую концепцию ввода/вывода в UNIX. Выборка и помещение информации в такой особый файл должна происходить по дисциплине FIFO (First In/First Out).

При вводе/выводе всегда используется потоковая модель данных. Это означает, что передаваемые через pipe данные никак не интерпретируются. Длина передаваемых сообщений не контролируется. Сразу несколько "процессов-клиентов" могут писать в канал, а один процесс-сервер может читать из него и обмениваться с "клиентами" сообщениями. При этом сообщения никак не отделяются друг от друга и сервер сам должен определять, кто из "клиентов" и какое сообщение прислал ему.

Очереди сообщений

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

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

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

Ядро никак не интерпретирует содержания сообщений, а только лишь обеспечивает размещение и выборку в соответствии с указанным типом. Шаблон сообщения хранится в файле sys/msg.h.

	struct msgbuf {
		long mtype;     /*тип сообщения*/
		char mtext[1]   /*данные*/
	}

Для работы с очередями в UNIX System V существуют следующие системные вызовы:

Очереди имеют свои преимущества по сравнению, скажем, с программными каналами. Например, чтобы осуществлять полнодуплексную связь между двумя процессами, очевидно, что нужно создать два канала: один для записи, а другой для чтения. В случае использования очередей можно обойтись одной очередью, если различать сообщения по типу: от клиента к серверу и наоборот. Кроме того, имеется возможность извлекать сообщения из очереди не только по дисциплине FIFO, но и в произвольном порядке. Эта особенность открывает перспективу построения систем с приоритетным обслуживанием клиентов.