Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Вопросы и ответы по ОС.doc
Скачиваний:
37
Добавлен:
27.08.2019
Размер:
3.35 Mб
Скачать

11. Виды межпроцессного взаимодействия и их классификация. Виды ipc в стандартах posix. Использование сокетов tcp/ip при большом количестве соединений Методы межпроцессного взаимодействия.

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

  1. Использование передачи сообщений

  2. Синхронизация (семафоры, мьютексы, критические секции и т.д.)

  3. Разделяемая память

  4. Удаленный вызов процедур (RPC)

Виды ipc в стандартах posix

В стандарте POSIX определены следующие средства IPC:

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

  2. Семафоры

  3. Разделяемая память.

Однако большинство POSIX-систем используют так же каналы, именованные каналы, сигналы и сокеты.

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

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

С сообщениями работают четыре системных функции:

  • msgget, которая возвращает или создает дескриптор очереди сообщений

  • msgctl, которая устанавливает и возвращает связанные с дескриптором сообщений параметры или удаляет дескрипторы

  • msgsnd, которая посылает сообщение

  • msgrcv, которая получает сообщение

При вызове msgsnd ядро выделяет сообщению место, используя карту сообщений, и копирует в это место данные из пространства пользователя. К сообщению присоединяется заголовок, после чего оно помещается в конец связного списка заголовков сообщений. Затем ядро выводит из состояния приостанова все процессы, ожидающие пополнения очереди сообщений.

Структуры данных, используемые в организации сообщений

При вызове функции msgrcv ядро проверяет, имеет ли пользователь необходимые права доступа к очереди сообщений. Если тип считываемого сообщения имеет нулевое значение, ядро ищет первое по счету сообщение в связном списке. Если его размер меньше или равен размеру, указанному пользователем, ядро копирует текст сообщения в пользовательское пространство. Если какие-либо процессы, ожидавшие получения сообщения, находились в состоянии приостанова из-за отсутствия свободного места в списке, ядро выводит их из этого состояния. Если размер сообщения превышает значение maxcount, указанное пользователем, ядро посылает системной функции уведомление об ошибке и оставляет сообщение в очереди. Если, тем не менее, процесс игнорирует ограничения на размер (в поле flag установлен бит MSG_NOERROR), ядро обрезает сообщение, возвращает запрошенное количество байт и удаляет сообщение из списка целиком. Процесс может получать сообщения определенного типа, если присвоит параметру type соответствующее значение. Если это положительное целое число, функция возвращает первое значение данного типа, если отрицательное, ядро определяет минимальное значение типа сообщений в очереди, и если оно не превышает абсолютное значение параметра type, возвращает процессу первое сообщение этого типа.

Семафоры

Для семафоров определены две элементарные операции: P и V Операция P заключается в уменьшении значения семафора в том случае, если оно больше 0, операция V - в увеличении этого значения (и там, и там на единицу). Поскольку операции элементарные, в любой момент времени для каждого семафора выполняется не более одной операции P или V.

Семафор в POSIX-системе состоит из следующих элементов:

  • Значение семафора,

  • Идентификатор последнего из процессов, работавших с семафором,

  • Количество процессов, ожидающих увеличения значения семафора,

  • Количество процессов, ожидающих момента, когда значение семафора станет равным 0.

Для создания набора семафоров и получения доступа к ним используется системная функция semget, для выполнения различных управляющих операций над набором - функция semctl, для работы со значениями семафоров - функция semop.

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

Установка флага IPC_NOWAIT в функции semop позволяет семафору не переходить в состояние ожидания увеличения значения семафора выше определенного уровня или снижения до 0.В такой ситуации ядро сразу вернет сообщение об ошибке.

В функции semop так же процесс может установить флаг SEM_UNDO: когда процесс завершится, ядро даст обратный ход всем операциям, выполненным процессом. Для этого в распоряжении у ядра имеется таблица, в которой каждому процессу в системе отведен указатель на группу структур восстановления, по одной структуре на каждый используемый процессом семафор.

Структуры восстановления семафоров

В структуре восстановления хранится результат вычитания суммы значений всех операций, произведенных над семафором, для которого установлен флаг SEM_UNDO. Если установочное значение становится равным 0, ядро удаляет структуру из списка. Когда процесс завершается, ядро просматривает все связанные с процессом структуры восстановления и отменяет выполненные семафором действия.

Разделяемая память

Процессы могут взаимодействовать друг с другом непосредственно путем разделения участков виртуального адресного пространства и обмена данными через разделяемую память. Функция shmget создает новую область разделяемой памяти или возвращает адрес уже существующей области, функция shmat логически присоединяет область к виртуальному адресному пространству процесса, функция shmdt отсоединяет ее, а функция shmctl имеет дело с различными параметрами, связанными с разделяемой памятью.

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

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

Структуры данных, используемые при разделении памяти

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

[Средства, не входящие в состав POSIX, но включенные в ответ: каналы, сигналы, сокеты]

Неименованные каналы

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

Традиционная реализация каналов использует файловую систему для хранения данных. Канал создается вызовом функции pipe(). При создании канала ядро назначает ему индекс и пару пользовательских дескрипторов и соответствующие им записи в таблице файлов: один для чтения из канала, а другой для записи. Затем ядро выделяет в таблице файлов две записи, соответствующие дескрипторам для чтения и записи в канал, в каждой из которых хранится информация о том, сколько экземпляров канала открыто для чтения или записи. Наконец, в индексе записываются смещения в байтах внутри канала до места, где будет начинаться следующая операция записи или чтения. Благодаря сохранению этих смещений в индексе имеется возможность производить доступ к данным в канале в порядке их поступления в канал; этот момент является особенностью каналов, поскольку для обычных файлов смещения хранятся в таблице файлов. Процессы не могут менять эти смещения с помощью системной функции lseek и поэтому произвольный доступ к данным канала невозможен

Именованные каналы

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

Работа с каналами

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

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

Если процесс ведет запись в канал и в канале нет места для всех данных, ядро помечает индекс и приостанавливает выполнение процесса до тех пор, пока канал не начнет очищаться от данных.

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

Сигналы

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

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

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

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

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

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

  2. Сбрасывает в пространстве процесса прежнее значение поля функции обработки сигнала и присваивает ему значение по умолчанию.

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

  4. Вносит изменения в сохраненный регистровый контекст задачи: устанавливает значение счетчика команд равным адресу функции обработки сигнала, а значение указателя вершины стека равным глубине стека задачи.

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

Сокеты

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

С каждым сокет связываются три атрибута: домен, тип и протокол. Эти атрибуты задаются при создании сокета и остаются неизменными на протяжении всего времени его существования. Для создания сокета используется функция socket, имеющая следующий прототип:

int socket(int domain, int type, int protocol);

Домен определяет пространство адресов, в котором располагается сокет, и множество протоколов, которые используются для передачи данных. Тип сокета определяет способ передачи данных по сети. Последний атрибут определяет протокол, используемый для передачи данных. Часто протокол однозначно определяется по домену и типу сокета. В этом случае в качестве третьего параметра функции socket можно передать 0, что соответствует протоколу по умолчанию. Тем не менее, иногда требуется задать протокол явно.

Прежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене. Для явного связывания сокета с некоторым адресом используется функция bind.

Установка соединения на стороне сервера состоит из четырёх этапов. Сначала сокет создаётся и привязывается к локальному адресу. На следующем шаге создаётся очередь запросов на соединение. При этом сокет переводится в режим ожидания запросов со стороны клиентов. Всё это выполняет функция listen. Функция accept создаёт для общения с клиентом новый сокет и возвращает его дескриптор. Один из параметров, sockfd задаёт слушающий сокет. После вызова он остаётся в слушающем состоянии и может принимать другие соединения. На стороне клиента для установления соединения используется функция connect.

После того как соединение установлено, можно начинать обмен данными. Для этого используются функции send и recv. В Unix для работы с сокетами можно использовать также файловые функции read и write, но они обладают меньшими возможностями, а кроме того не будут работать на других платформах.

После окончания обмена данными, сокет закрывают с помощью функции close. Это приводит к разрыву соединения. Также можно запретить передачу данных в каком-то одном направлении, используя функцию shutdown. В зависимости от переданного параметра можно запретить запись в сокет, чтение из него или и то и другое.