Обновлено: 06.02.2023
Межпроцессное взаимодействие (англ. inter-process communication, IPC ) — обмен данными между потоками одного или разных процессов. Реализуется посредством механизмов, предоставляемых ядром ОС или процессом, использующим механизмы ОС и реализующим новые возможности IPC. Может осуществляться как на одном компьютере, так и между несколькими компьютерами сети [1] .
Содержание
Общие положения
Операционные системы реального времени, поддерживающие многозадачность, должны использовать такие средства межпроцессного взаимодействия, которые гарантируют синхронизацию без взаимоисключений и с минимальными задержками.
В идеальной многозадачной системе все процессы, выполняющиеся параллельно, независимы друг от друга (т.е., асинхронны). На практике такая ситуация маловероятна, поскольку рано или поздно возникают ситуации, когда параллельным процессам необходим доступ к некоторым общим ресурсам. Для этого необходимо введение на уровне ОС средств, предоставляющих такую возможность.
При выполнении параллельных процессов может возникать проблема, когда каждый процесс, обращающийся к разделяемым данным, исключает для всех других процессов возможность одновременного с ним обращения к этим данным — это называется взаимоисключением (mutual exclusion).
Ресурс, который допускает обслуживание только одного пользователя за один раз, называетсякритическим ресурсом. Если несколько процессов хотят пользоваться критическим ресурсом в режиме разделения времени, им следует синхронизировать свои действия таким образом, чтобы этот ресурс всегда находился в распоряжении не более чем одного их них.
Участки процесса, в которых происходит обращение к кри¬тическим ресурсам, называются критическими участками. Для организации коммуникации между одновременно работающими процессами применяются средства межпроцессного взаимодействия (Interprocess Communication — IPC).
Выделяются три уровня средств IPC:
- локальный;
- удаленный;
- высокоуровневый.
Удаленные IPC предоставляют механизмы, которые обеспечивают взаимодействие как в пределах одного процессора, так и между программами на различных процессорах, соединенных через сеть. Сюда относятся удаленные вызовы процедур (Remote Procedure Calls — RPC), сокеты Unix, а также TLI (Transport Layer Interface — интерфейс транспортного уровня) фирмы Sun [2] .
Наряду с обеспечением взаимодействия процессов средства IPC призваны решать проблемы, возникающие при организации параллельных вычислений. Сюда относятся:
- Синхронный доступ. Если все процессы считывают данные из файла, то в большинстве случае проблем не возникает. Однако, при попытке одним из процессов изменить этот файл, могут возникнуть так называемые конкурентные ситуации (race conditions).
- Дисциплина доступа. Если нужно, чтобы как можно большее количество пользователей могли записывать данные, организуется так называемая очередь (по правилу один пишет, несколько читают). Практически организуется две очереди: одна — для чтения, другая — для записи. Такую дисциплину доступа можно организовать с помощью барьеров (блокировок). При этом создается общий барьер для считывателей, так как несколько процессов могут одновременно считывать данные, а также отдельный барьер для процесса-писателя. Такая организация предотвращает взаимные помехи в работе.
- Голодание процессов. Организация дисциплины доступа может привести к ситуации, когда процесс будет длительно ждать своей очереди для записи данных. Поэтому иногда нужно организовывать очереди с приоритетами.
- Управление потоком. Если нельзя точно определить, какой из процессов запрашивает или возвращает свои данные в нужный компьютер первым, используется так называемое взаимодействие по модели «клиент-сервер». При этом используются один или несколько клиентов и один сервер. Клиент посылает запрос серверу, а сервер отвечает на него. После этого клиент должен дождаться ответа сервера, чтобы продолжать дальнейшее взаимодействие. Такое поведение называется управлением потоком. При одновременном доступе здесь также могут использоваться очереди с приоритетами [3] .
- Тупик (deadlock). Классический тупик возникает, если процесс A получает доступ к файлу A и ждет освобождения файла B. Одновременно процесс B, получив доступ к файлу B, ждет освобождения файла A. Оба процесса теперь ждут освобождения ресурсов другого процесса и не освобождают при этом собственный файл.
Тупика можно избежать, если ресурсы будут пронумерованы и предоставляться только в восходящем порядке номеров. Кроме того, все ресурсы должны освобождаться сразу после окончания использования.
Каналы
Канал (pipe) представляет собой средство связи стандартного вывода одного процесса со стандартным вводом другого. Каналы старейший из инструментов IPC, существующий приблизительно со времени появления самых ранних версий оперативной системы UNIX.
Для реализации IPC возможно использование полудуплексных и/или именованных каналов (FIFO).
Полудуплексные каналы
Когда процесс создает канал, ядро устанавливает два файловых дескриптора для пользования этим каналом. Один такой дескриптор используется, чтобы открыть путь ввода в канал (запись), в то время как другой применяется для получения данных из канала (чтение). В этом смысле, канал мало применим практически, так как создающий его процесс может использовать канал только для взаимодействия с самим собой. Рассмотрим следующее изображение процесса и ядра после создания канала:
Из этого рисунка легко увидеть, как файловые дескрипторы связаны друг с другом. Если процесс посылает данные через канал (fd0), он имеет возможность получить эту информацию из fd1. Однако этот простенький рисунок отображает и более глобальную задачу. Хотя канал первоначально связывает процесс с самим собой, данные, идущие через канал, проходят через ядро. В частности, в Linux каналы внутренне представлены корректным inode. Конечно, этот inode существует в пределах самого ядра, а не в какой-либо физической файловой системе. Эта особенность откроет нам некоторые привелекательные возможности для ввода/вывода, как мы увидим немного позже. Зачем же нам неприятности с созданием канала, если мы всего-навсего собираемся поговорить сами с собой? На самом деле, процесс, создающий канал, обычно порождает дочерний процесс. Как только дочерний процесс унаследует какой-нибудь открытый файловый дескриптор от родителя, мы получаем базу для мультипроцессовой коммуникации (между родителем и потомком). Рассмотрим эту измененную версию нашего рисунка:
Теперь мы видим, что оба процесса имеют доступ к файловым дескрипторам, которые основывают канал. На этой стадии должно быть принято критическое решение. В каком направлении мы хотим запустить данные? Потомок посылает информацию к родителю или наоборот? Два процесса взаимно согласовываются и «закрывают» неиспользуемый конец канала. Пусть потомок выполняет несколько действий и посылает информацию родителю обратно через канал. Наш новый рисунок выглядел бы примерно так:
Конструкция канала теперь полная. Чтобы использовать его, можно применять системные вызовы, подобные тем, которые нужны для ввода/вывода в файл или из файла на низком уровне (вспомним, что в действительности каналы внутренне представлены как корректный inode). Это означает, что для чтения из канала можно использовать вызов read(), а для записи — write(). Примечания:
- Двусторонние каналы могут быть созданы посредством открывания двух каналов и правильным переопределением файловых дескрипторов в процессе-потомке.
- Вызов pipe() должен быть произведен ПЕРЕД вызовом fork(), или дескрипторы не будут унаследованы процессом-потомком! (то же для popen()).
- С полудуплексными каналами любые связанные процессы должны разделять происхождение. Поскольку канал находится в пределах ядра, любой процесс, не состоящий в родстве с создателем канала, не имеет способа адресовать его. Это не относится к случаю с именованными каналами (FIFOS).
Именованные каналы (FIFO: First In First Out)
Именованные каналы во многом работают так же, как и обычные каналы, но все же имеют несколько заметных отличий:
- Именованные каналы существуют в виде специального файла устройства в файловой системе.
- Процессы различного происхождения могут разделять данные через такой канал.
- Именованный канал остается в файловой системе для дальнейшего использования и после того, как весь ввод/вывод сделан.
Операции ввода/вывода с FIFO, по существу, такие же, как для обычных каналов, за одним исключением. Чтобы физически открыть проход к каналу, должен быть использован системный вызов «open» или библиотечная функция. С полудуплексными каналами это невозможно, поскольку канал находится в ядре, а не в физической файловой системе.
Если FIFO открыт для чтения, процесс его блокирует до тех пор, пока какой-нибудь другой процесс не откроет FIFO для записи.
Сигналы
Сигналы являются программными прерываниями, которые посылаются процессу, когда случается некоторое событие. Сигналы могут возникать синхронно с ошибкой в приложении, например SIGFPE (ошибка вычислений с плавающей запятой) и SIGSEGV (ошибка адресации), но большинство сигналов является асинхронными. Сигналы могут посылаться процессу, если система обнаруживает программное событие, например, когда пользователь дает команду прервать или остановить выполнение, или получен сигнал на завершение от другого процесса. Сигналы могут прийти непосредственно от ядра ОС, когда возникает сбой аппаратных средств ЭВМ. Система определяет набор сигналов, которые могут быть отправлены процессу. При этом каждый сигнал имеет целочисленное значение и приводит к строго определенным действиям.
Механизм передачи сигналов состоит из следующих частей:
- установление и обозначение сигналов в форме целочисленных значений;
- маркер в строке таблицы процессов для прибывших сигналов;
- таблица с адресами функций, которые определяют реакцию на прибывающие сигналы.
Отдельные сигналы подразделяются на три класса:
- системные сигналы (ошибка аппаратуры, системная ошибка и т.д.);
- сигналы от устройств;
- сигналы, определенные пользователем.
Как только сигнал приходит, он отмечается записью в таблице процессов. Если этот сигнал предназначен для процесса, то по таблице указателей функций в структуре описания процесса выясняется, как нужно реагировать на этот сигнал. При этом номер сигнала служит индексом таблицы.
Известно три варианта реакции на сигналы:
- вызов собственной функции обработки;
- игнорирование сигнала (не работает для SIGKILL);
- использование предварительно установленной функции обработки по умолчанию.
Чтобы реагировать на разные сигналы, необходимо знать концепции их обработки. Процесс должен организовать так называемый обработчик сигнала в случае его прихода.
Процесс, выполняющий операцию с блокировкой, может быть приостановлен до тех пор, пока не будет удовлетворено одно из условий:
Семафоры
Семафоры являются одним из классических примитивов синхронизации. Семафор (semaphore) — это целая переменная, значение которой можно опрашивать и менять только при помощи неделимых (атомарных) операций. Двоичный семафор может принимать только значения 0 или 1. Считающий семафор может принимать целые неотрицательные значения. В приложениях как правило требуется использование более одного семафора, ОС должна представлять возможность создавать множества семафоров.
Над каждым семафором, принадлежащим некоторому множеству, можно выполнить любую из трех операций:
- увеличить значение;
- уменьшить значение;
- дождаться обнуления.
Для выполнения первых двух операций у процесса должно быть право на изменение, для выполнения третьей достаточно права на чтение. Чтобы увеличить значение семафора, системному вызову semop следует передать требуемое число. Чтобы уменьшить значение семафора, нужно передать требуемое число, взятое с обратным знаком; если результат получается отрицательным, операция не может быть успешно выполнена. Для третьей операции нужно передать 0; если текущее значение семафора отлично от нуля, операция не может быть успешно выполнена.
Разделяемая память
Сокеты
Сокеты обеспечивают двухстороннюю связь типа «точка-точка между двумя процессами. Они являются основными компонентами межсистемной и межпроцессной связи. Каждый сокет представляет собой конечную точку связи, с которой может быть совмещено некоторое имя. Он имеет определенный тип, и один процесс или несколько, связанных с ним процессов.
Основные типы сокетов
Поточный — обеспечивает двухсторонний, последовательный, надежный, и недублированный поток данных без определенных границ. Тип сокета — SOCK_STREAM, в домене Интернета он использует протокол TCP.
Сокет последовательных пакетов — обеспечивает двухсторонний, последовательный, надежный обмен датаграммами фиксированной максимальной длины. Тип сокета — SOCK_SEQPACKET. Для этого типа сокета не существует специального протокола.
IPC: основы межпроцессного взаимодействия
Любая операционная система была бы весьма ущербна, если бы замыкала выполняющееся приложение в собственном темном мирке без окон и дверей, без какой-либо возможности сообщить другим программам какую-либо информацию. Если посмотреть внимательно, можно заметить, что далеко не все приложения являются самодостаточными. Очень многим, если не большей части, требуется информация от других приложений, либо они должны эту информацию сообщать. Именно поэтому в операционную систему встраивается множество механизмов, которые обеспечивают т.н. Interproccess Communication (IPC) — то есть межпроцессное взаимодействие.
В историческом плане сначала появилась необходимость в общении процессов, выполняющихся на одном компьютере. В дальнейшем с бурным развитием сетевых технологий все острее стала чувствоваться потребность в средствах для взаимодействия процессов, выполняющихся на разных компьютерах в сети. Особенно трудна такая задача, если это компьютеры на базе разных платформ и/или с разными операционными системами.
Рассмотрим подробнее несколько ключевых примеров, демонстрирующих важность IPC. Вам, возможно это покажется неправдоподобным, но зачатки IPC существовали еще в MS-DOS — и это несмотря на то, что MS-DOS при всем желании трудно назвать многозадачной средой. В самом деле, когда вы в командной строке вводили подобную инструкцию :
происходило следующее: выполнялась команда DIR и ее вывод записывался во временный текстовый файл. После этого содержимое файла подавалось на вход команды MORE. В результате вы получали листинг каталогов, который в случае большого количества каталогов не уезжал мгновенно за экран, а мог скроллироваться с помощью клавиши Enter. Конечно же это очень примитивный IPC, но его наличие показывает, что уже тогда такой механизм был востребован и в какой-то мере реализован.
Примеры использования IPC охватывают гораздо большее количество программ и приложений, чем вы скорее всего думаете. Когда вы выходите в интернет, ваш браузер — одна программа (процесс) — взаимодействует с web-сервером — другой программой (процессом). Эти программы выполняются на разных компьютерах; браузер на вашем, сервер — где-то еще. И вас не волнует, какая ОС установлена на сервере и какая там платформа.
Или, например, вы работаете с удаленной базой данных. Ваше клиентское приложение — это один процесс, на сервере базы данных запущен другой процесс. Процесс на сервере выполняет запросы к БД, поступающие от вашего процесса.
ПРИМЕЧАНИЕ
Вообще, для сетевых форм IPC (но не обязательно только для них) очень часто используется концепция «клиент-сервер». Как вы понимаете, «клиент» — это приложение, которому требуются данные, «сервер» — приложение, предоставляющее данные.
А если брать только взаимодействие программ, выполняющихся на одном компьютере, самым банальным примером будет следующий: текст из вашего текcтового редактора передается в электронную таблицу или программу для верстки. Да-да, наш старый знакомый буфер обмена — это тоже один из механизмов IPC!
И еще можно было бы привести очень много примеров.
Средств, обеспечивающих взаимодействие между процессами, создано достаточно много. Огромное их количество реализовано в Windows 9x, еще больше — в Windows NT/2000. Теперь нужно приличное количество времени, чтобы хотя бы познакомиться со всеми! Замечу, что нет, и наверное в принципе не может быть универсального способа обмена данными, который годился бы на все случаи жизни — все равно в некоторых случаях использование другого способа будет предпочтительнее. Но я надеюсь, что после прочтения этой статьи вы сможете достаточно уверенно ориентироваться в мире IPC и обоснованно выбирать тот или иной метод.
Тот факт, что механизмы IPC работают на уровне операционной системы, положительно сказывается на скорости и надежности программ и программных комплексов, построенных с их использованием. Эффективность приложений соответственно возрастает.
Вообще, правильнее было бы называть эти механизмы «Interthread Communication» — межпотоковое взаимодействие. Если вы помните, выполняются именно потоки, они же и обмениваются данными. Однако, смысл для отдельных механизмов взаимодействия появляется только в том случае, если эти потоки принадлежат разным процессам. Ведь потоки, выполняющиеся в рамках одного процесса, вовсе не нуждаются в дополнительных средствах для общения между собой. Так как они разделяют одно адресное пространство, обмен данными могут обеспечить обычные переменные. Таким образом, IPC становится необходим в том случае, если поток одного процесса должен передать данные потоку другого процесса.
Теперь давайте рассмотрим основные виды IPC и случаи, в которых они используются.
Буфер обмена (clipboard)
Это одна из самых примитивных и хорошо известных форм IPC. Он появился еще в самых ранних версиях Windows. Основная его задача — обеспечивать обмен данными между программами по желанию и под контролем пользователя. Впрочем, вы наверняка сами неплохо знаете, как используется буфер обмена. Не рекомендуется использовать его для внутренних нужд приложения, и не стоит помещать туда то, что не предназначено для прямого просмотра пользователем.
Разделяемая память (shared memory)
Этот способ взаимодействия реализуется не совсем напрямую, а через технологию File Mapping — отображения файлов на оперативную память. Вкраце, этот механизм позволяет осуществлять доступ к файлу таким образом, как будто это обыкновенный массив, хранящийся в памяти (не загружая файл в память явно). «Побочным эффектом» этой технологии является возможность работать с таким отображенным файлом сразу нескольким процессам. Таким образом, можно создать объект file mapping, но не ассоциировать его с каким-то конкретным файлом. Получаемая область памяти как раз и будет общей между процессами. Работая с этой памятью, потоки обязательно должны согласовывать свои действия с помощью объектов синхронизации.
Библиотеки динамической компоновки (DLL)
Библиотеки динамической компоновки также имеют способность обеспечивать обмен данными между процессами. Когда в рамках DLL объявляется переменная, ее можно сделать разделяемой (shared). Все процессы, обращающиеся к библиотеке, для таких переменных будут использовать одно и то же место в физической памяти. (Здесь также важно не забыть о синхронизации.)
Протокол динамического обмена данными (Dynamic Data Exchange, DDE)
Этот протокол выполняет все основные функции для обмена данными между приложениями. Он очень широко использовался до тех пор, пока для этих целей не стали применять OLE (впоследствии ActiveX). На данный момент DDE используется достаточно редко, в основном для обратной совместимости.
Больше всего этот протокол подходит для задач, не требующих продолжительного взаимодействия с пользователем. Пользователю в некоторых случаях нужно только установить соединение между программами, а обмен данными происходит без его участия. Замечу, что все это в равной степени относится и к технологии OLE/ActiveX.
OLE/ActiveX
Это действительно универсальная технология, и одно из многих ее применений — межпроцессный обмен данными. Хотя cтоит думаю отметить, что OLE как раз для этой цели и создавалась (на смену DDE), и только потом была расширена настолько, что пришлось поменять название ;-). Специально для обмена данными существует интерфейс IDataObject. А для обмена данными по сети используется DCOM, которую под некоторым углом можно рассматривать как объединение ActiveX и RPC.
Каналы (pipes)
Каналы — это очень мощная технология обмена данными. Наверное, именно поэтому в полной мере они поддерживаются только в Windows NT/2000. В общем случае канал можно представить в виде трубы, соединяющей два процесса. Что попадает в трубу на одном конце, мгновенно появляется на другом. Чаще всего каналы используются для передачи непрерывного потока данных.
Каналы делятся на анонимные (anonymous pipes) и именованные (named pipes). Анонимные каналы используются достаточно редко, они просто передают поток вывода одного процесса на поток ввода другого. Именованные каналы передают произвольные данные и могут работать через сеть. (Именованные каналы поддерживаются только в WinNT/2000.)
Сокеты (sockets)
Это очень важная технология, т.к. именно она отвечает за обмен данными в Интернет. Сокеты также часто используются в крупных ЛВС. Взаимодействие происходит через т.н. разъемы-«сокеты», которые представляют собой абстракцию конечных точек коммуникационной линии, соединяющей два приложения. С этими объектами программа и должна работать, например, ждать соединения, посылать данные и т.д. В Windows входит достаточно мощный API для работы с сокетами.
Почтовые слоты (mailslots)
Объекты синхронизации
Как ни странно, объекты синхронизации тоже можно отнести к механизмам IPC. Конечно, объем передаваемых данных в данном случае очень невелик Но именно эти объекты следует использовать, если одному процессу нужно передать другому что-то вроде «я закончил работу» или «я начинаю работать с общей памятью».
Microsoft Message Queue (MSMQ)
Удаленный вызов процедур (Remote Procedure Call, RPC)
Строго говоря, это не совсем технология IPC, а скорее способ значительно расширить возможности других механизмов IPC. С помощью этой технологии общение через сеть становится совешенно прозрачным как для сервера, так и для клиента. Им обоим начинает казаться, что их «собеседник» расположен локально по отношению к ним .
Резюме
Конечно, я перечислил далеко не все способы обмена данными. Если бы это было так, то это было бы не так интересно За рамками данной статьи остались такие вещи, как глобальная таблица атомов, хуки и некоторые другие технологии, которые с некоторой натяжкой можно признать механизмами IPC. Но главное, как я считаю, сделано: теперь вы знаете, что это за непонятные аббревиатуры и как из всего многообразия методов IPC выбрать наиболее подходящий.
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
в этом разделе объясняются различные способы выполнения межпроцессного взаимодействия (IPC) между приложениями универсальная платформа Windows (UWP) и приложениями Win32.
Услуги для приложений
Службы приложений позволяют приложениям предоставлять службы, которые принимают и возвращают контейнеры свойств примитивов (наборзначений) в фоновом режиме. Объекты с широкими возможностями могут передаваться при сериализации.
Службы приложений могут выполнять как фоновые задачи , так ипроцессы в рамках приложения переднего плана.
Службы приложений лучше использовать для совместного использования небольших объемов данных, где задержка почти в реальном времени не требуется.
Com — это распределенная объектно-ориентированная система для создания двоичных программных компонентов, которые могут взаимодействовать и взаимодействовать. Как разработчик вы используете COM для создания многократно используемых программных компонентов и слоев автоматизации для приложения. COM-компоненты могут быть в процессе или вне процесса и могут взаимодействовать через клиентскую и серверную модель. Необработанные серверы COM долго использовались в качестве средства для обмена данными между объектами.
Упакованные приложения с возможностью рунфуллтруст могут регистрировать необработанные COM-серверы для IPC с помощью манифеста пакета. Это называется упакованным com.
Файловая система
BroadFileSystemAccess
Упакованные приложения могут выполнять IPC с помощью обширной файловой системы, объявляя ограниченную возможность broadFileSystemAccess . эта возможность предоставляет Windows. служба хранилища Интерфейсы API и ксксксфромапп Win32 API обращаются к широкой файловой системе.
По умолчанию IPC через файловую систему для упакованных приложений ограничена другими механизмами, описанными в этом разделе.
PublisherCacheFolder
PublisherCacheFolder позволяет упакованным приложениям объявлять папки в своем манифесте, которые могут использоваться совместно с другими пакетами одним и тем же издателем.
Папка общего хранилища имеет следующие требования и ограничения.
- Данные в папке общего хранилища не архивируются и не перемещаются.
- Пользователь может очистить содержимое папки общего хранилища.
- Нельзя использовать папку общего хранилища для обмена данными между приложениями разных издателей.
- Нельзя использовать папку общего хранилища для обмена данными между разными пользователями.
- В папке общего хранилища нет управления версиями.
Если вы публикуете несколько приложений и ищете простой механизм обмена данными между ними, PublisherCacheFolder — подходящий простой вариант на основе файловой системы.
шаредакцесссторажеманажер
Шаредакцесссторажеманажер используется совместно со службами приложений, активацией протоколов (например, лаунчурифорресултсасинк) и т. д., чтобы совместно использовать сторажефилес через маркеры.
фуллтрустпроцесслаунчер
Благодаря возможности рунфуллтруст Упакованные приложения могут запускать процессы с полным доверием в одном пакете.
В сценариях, где ограничения пакета являются косвенными, или отсутствуют параметры IPC, приложение может использовать процесс полного доверия в качестве прокси-сервера для взаимодействия с системой, а затем IPC с полным уровнем доверия через службы приложений или другой хорошо поддерживаемый механизм IPC.
LaunchUriForResultsAsync
Лаунчурифорресултсасинк используется для простого обмена данными с другими упакованными приложениями, реализующими контракт активации протоколфорресултс . В отличие от служб приложений, которые обычно выполняются в фоновом режиме, целевое приложение запускается на переднем плане.
Для совместного использования файлов можно передавать маркеры шаредсторажеакцессманажер в приложение через его значение.
Замыкание на себя
Замыкание на себя — это процесс связи с сетевым сервером, который прослушивает localhost (адрес замыкания на себя).
Для обеспечения безопасности и сетевой изоляции подключения по обратной связи для IPC по умолчанию блокируются для упакованных приложений. Можно включить замыкание соединений между доверенным упакованным приложением с помощью возможностей и свойств манифеста.
- Все Упакованные приложения, участвующие в подключениях замыкания на себя, должны объявлять privateNetworkClientServer возможности в privateNetworkClientServer .
- Два упакованных приложения могут обмениваться данными через замыкание на себя, объявляя лупбаккакцессрулес в манифестах пакетов.
- Каждое приложение должно перечислить в лупбаккакцессрулес. Клиент объявляет правило «out» для сервера, а сервер объявляет правила «in» для поддерживаемых клиентов.
имя семейства пакетов, необходимое для распознавания приложения в этих правилах, можно найти с помощью редактора манифеста пакета во Visual Studio во время разработки, через центр партнеров для приложений, опубликованных с помощью Microsoft Store, или с помощью команды PowerShell Get-AppxPackage для уже установленных приложений.
Неупакованные приложения и службы не имеют удостоверения пакета, поэтому их нельзя объявлять в лупбаккакцессрулес. Вы можете настроить упакованное приложение для подключения через замыкание с помощью неупакованных приложений и служб с помощью CheckNetIsolation.exe, однако это возможно только в сценариях загружать неопубликованные или отладки, где у вас есть локальный доступ к компьютеру и у вас есть права администратора.
- Все Упакованные приложения, участвующие в подключениях замыкания на себя, privateNetworkClientServer должны объявлять возможности в privateNetworkClientServer .
- Если упакованное приложение подключается к неупакованному приложению или службе, выполните команду CheckNetIsolation.exe LoopbackExempt -a -n=
-
должны выполняться постоянно, пока упакованное приложение прослушивает подключения.
- -is флаг был введен в Windows 10 версии 1607 (10,0; Сборка 14393).
имя семейства пакетов, необходимое для -n флага -n , можно найти в редакторе манифеста пакета в Visual Studio во время разработки, через центр партнеров для приложений, опубликованных с помощью Microsoft Store, или с помощью команды PowerShell Get-AppxPackage для уже установленных приложений.
Каналы
Каналы обеспечивают простое взаимодействие между сервером канала и одним или несколькими клиентами канала.
Анонимные каналы и именованные каналы поддерживают следующие ограничения:
- По умолчанию именованные каналы в упакованных приложениях поддерживаются только между процессами в одном пакете, если только процесс не имеет полного доверия.
- Именованные каналы можно совместно использовать в пакетах, следуя рекомендациям по предоставлению общего доступа к именованным объектам.
- Именованные каналы в упакованных приложениях должны использовать синтаксис \.pipeLOCAL для имени канала.
Реестр
Использование реестра для IPC, как правило, не рекомендуется, но поддерживается для существующего кода. Упакованные приложения имеют доступ только к тем разделам реестра, для доступа к которым у них есть разрешение.
Классические приложения, Упакованные в MSIX, используют виртуализацию реестра , так что глобальные записи реестра содержатся в частном кусте в пакете MSIX. Это обеспечивает совместимость исходного кода при минимизации влияния на глобальные реестры и может использоваться для IPC между процессами в одном пакете. Если необходимо использовать реестр, то эта модель является предпочтительной, а управление глобальным реестром — с помощью.
RPC можно использовать для подключения упакованного приложения к КОНЕЧНОЙ точке RPC Win32 при условии, что Пакетное приложение имеет правильные возможности для сопоставления ACL в КОНЕЧНОЙ точке RPC.
Пользовательские возможности позволяют производителям оборудования и независимым поставщикам программно определять произвольные возможности, предоставлять им конечные точки RPC, а затем предоставить эти возможности полномочным клиентским приложениям. Полный пример приложения см. в примере кустомкапабилити .
Конечные точки RPC также могут быть доступен для конкретных упакованных приложений, чтобы ограничить доступ к конечной точке только этими приложениями без необходимости управления дополнительными возможностями. Вы можете использовать API деривеаппконтаинерсидфромаппконтаинернаме для получения идентификатора безопасности из имени семейства пакетов, а затем в списке ACL КОНЕЧНОЙ точки RPC с ИД безопасности, как показано в примере кустомкапабилити .
Общая память
Сопоставление файлов можно использовать для совместного использования файла или памяти между двумя или более процессами со следующими ограничениями:
- По умолчанию сопоставления файлов в упакованных приложениях поддерживаются только между процессами в одном пакете, если только процесс не имеет полного доверия.
- Сопоставления файлов можно совместно использовать в пакетах, следуя рекомендациям по предоставлению общего доступа к именованным объектам.
Для эффективного совместного использования больших объемов данных и управления ими рекомендуется использовать общую память.
TCP и UDP сокеты. Адресные пространства портов. Понятие encapsulation. Encapsulation для UDP-протокола на сети Ethernet. Использование модели клиент-сервер для взаимодействия удаленных процессов. Организация связи между процессами с помощью датаграмм.
Рубрика Программирование, компьютеры и кибернетика Вид контрольная работа Язык русский Дата добавления 07.05.2012 Размер файла 40,6 K Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Межпроцессное взаимодействие
порт сервер датаграмма encapsulation
Мы не будем вдаваться в детали реализации протоколов транспортного уровня, а лишь кратко рассмотрим их основные характеристики. К протоколам транспортного уровня относятся протоколы TCP и UDP.
Полный адрес удаленного процесса или промежуточного объекта для конкретного способа связи с точки зрения операционных систем определяется парой адресов: .
Такая пара получила название socket (гнездо, панель), так как по сути дела является виртуальным коммуникационным узлом (можно представить себе виртуальный разъем или ящик для приема / отправки писем), ведущим от объекта во внешний мир и наоборот. При непрямой адресации сами промежуточные объекты для организации взаимодействия процессов также именуются сокетами.
Поскольку уровень Internetсемейства протоколов TCP/IP умеет доставлять информацию только от компьютера к компьютеру, данные, полученные с его помощью, должны содержать тип использованного протокола транспортного уровня и локальные адреса отправителя и получателя. И протокол TCP, и протокол UDP используют непрямую адресацию.
Итак, мы описали иерархическую систему адресации, используемую в семействе протоколов TCP/IP, которая включает в себя несколько уровней:
· Физический пакет данных, передаваемый по сети, содержит физические адреса узлов сети (MAC-адреса) с указанием на то, какой протокол уровня Internet должен использоваться для обработки передаваемых данных (поскольку пользователя интересуют только данные, доставляемые затем на уровень приложений / процессов, то для него это всегда IP).
· IP-пакет данных содержит 32-битовые IP-адреса компьютера-отправителя и компьютера-получателя, и указание на то, какой вышележащий протокол (TCP, UDP или еще что-нибудь) должен использоваться для их дальнейшей обработки.
· Служебная информация транспортных протоколов (UDP-заголовок к данным и TCP-заголовок к данным) должна содержать 16-битовые номера портов для сокета отправителя и сокета получателя.
Добавление необходимой информации к данным при переходе от верхних уровней семейства протоколов к нижним принято называть английским словом encapsulation (дословно: герметизация). На рисунке 1. приведена схема encapsulation при использовании протокола UDP на сети Ethernet.
Рис. 1. Encapsulation для UDP-протокола на сети Ethernet
Поскольку между MAC-адресами и IP-адресами существует взаимно однозначное соответствие, известное семейству протоколов TCP/IP, то фактически для полного задания адреса доставки и адреса отправления, необходимых для установления двусторонней связи, нужно указать пять параметров: транспортный протокол, IP-адрес отправителя, порт отправителя, IP-адрес получателя, порт получателя.
Использование модели клиент-сервер для взаимодействия удаленных процессов
Модель клиент-сервер может использоваться для организации взаимодействия локальных процессов. Эта же модель, изначально предполагающая неравноправность взаимодействующих процессов, наиболее часто используется для организации сетевых приложений. Напомним основные отличия процессов клиента и сервера применительно к удаленному взаимодействию:
· Сервер, как правило, работает постоянно, на всем протяжении жизни приложения, а клиенты могут работать эпизодически.
· Сервер ждет запроса от клиентов, инициатором же взаимодействия выступает клиент.
· Как правило, клиент обращается к одному серверу за раз, в то время как к серверу могут одновременно поступить запросы от нескольких клиентов.
· Клиент должен знать полный адрес сервера (его локальную и удаленную части) перед началом организации запроса (до начала общения), в то время как сервер может получить информацию о полном адресе клиента из пришедшего запроса (после начала общения).
· И клиент, и сервер должны использовать один и тот же протокол транспортного уровня.
Неравноправность процессов в модели клиент-сервер, как мы увидим далее, накладывает свой отпечаток на программный интерфейс, используемый между уровнем приложений / процессов и транспортным уровнем.
Поступающие запросы сервер может обрабатывать последовательно — запрос за запросом — или параллельно, запуская для обработки каждого из них свой процесс или thread. Как правило, серверы, ориентированные на связь клиент-сервер с помощью установки логического соединения (TCP-протокол), ведут обработку запросов параллельно, а серверы, ориентированные на связь клиент-сервер без установления соединения (UDP-протокол), обрабатывают запросы последовательно.
Рассмотрим основные действия, которые нам необходимы в терминах абстракции socket для того, чтобы организовать взаимодействие между клиентом и сервером, используя транспортные протоколы стека TCP/IP.
Организация связи между удаленными процессами с помощью датаграмм
Более простой для взаимодействия удаленных процессов является схема организации общения клиента и сервера с помощью датаграмм, т.е. использование протокола UDP.
Все эти модельные действия имеют аналоги при общении удаленных процессов по протоколу UDP.
Схематично эти действия выглядят так, как показано на рисунке 2. Каждому из них соответствует определенный системный вызов. Названия вызовов написаны справа от блоков соответствующих действий.
Создание сокета производится с помощью системного вызова socket(). Для привязки созданного сокета к IP-адресу и номеру порта (настройка адреса) служит системный вызов bind(). Ожиданию получения информации, ее чтению и, при необходимости, определению адреса отправителя соответствует системный вызов recvfrom(). За отправку датаграммы отвечает системный вызов sendto().
Прежде чем приступить к подробному рассмотрению этих системных вызовов и примеров программ нам придется остановиться на нескольких вспомогательных функциях, которые мы должны будем использовать при программировании.
Сетевой порядок байт. Функции htons(), htonl(), ntohs(), ntohl()
Передача от одного вычислительного комплекса к другому символьной информации, как правило (когда один символ занимает один байт), не вызывает проблем. Однако для числовой информации ситуация усложняется.
Как известно, порядок байт в целых числах, представление которых занимает более одного байта, может быть для различных компьютеров неодинаковым. Есть вычислительные системы, в которых старший байт числа имеет меньший адрес, чем младший байт (big-endianbyteorder), а есть вычислительные системы, в которых старший байт числа имеет больший адрес, чем младший байт (little-endianbyteorder). При передаче целой числовой информации от машины, имеющей один порядок байт, к машине с другим порядком байт мы можем неправильно истолковать принятую информацию. Для того чтобы этого не произошло, было введено понятие сетевого порядка байт, т.е. порядка байт, в котором должна представляться целая числовая информация в процессе передачи ее по сети (на самом деле — это big-endianbyteorder). Целые числовые данные из представления, принятого на компьютере-отправителе, переводятся пользовательским процессом в сетевой порядок байт, в таком виде путешествуют по сети и переводятся в нужный порядок байт на машине-получателе процессом, которому они предназначены. Для перевода целых чисел из машинного представления в сетевое и обратно используется четыре функции: htons(), htonl(), ntohs(), ntohl().
Функции преобразования порядка байт
Прототипы функций
unsigned long inthtonl (
unsigned long inthostlong);
unsigned short inthtons (
unsigned short inthostshort);
unsigned long intntohl (
unsigned long intnetlong);
unsigned short intntohs (
unsigned short intnetshort);
Описание функций
Функция htonl осуществляет перевод целого длинного числа из порядка байт, принятого на компьютере, в сетевой порядок байт.
Функция htons осуществляет перевод целого короткого числа из порядка байт, принятого на компьютере, в сетевой порядок байт.
Функция ntohl осуществляет перевод целого длинного числа из сетевого порядка байт в порядок байт, принятый на компьютере.
Функция ntohs осуществляет перевод целого короткого числа из сетевого порядка байт в порядок байт, принятый на компьютере.
В архитектуре компьютеров i80x86 принят порядок байт, при котором младшие байты целого числа имеют младшие адреса. При сетевом порядке байт, принятом в Internet, младшие адреса имеют старшие байты числа.
Параметр у них — значение, которое мы собираемся конвертировать. Возвращаемое значение-то, что получается в результате конвертации. Направление конвертации определяется порядком букв h (host) и n (network) в названии функции, размер числа — последней буквой названия, то есть htons — это hosttonetworkshort, ntohl — networktohostlong.
Для чисел с плавающей точкой все обстоит гораздо хуже. На разных машинах могут различаться не только порядок байт, но и форма представления такого числа. Простых функций для их корректной передачи по сети не существует. Если требуется обмениваться действительными данными, то либо это нужно делать на гомогенной сети, состоящей из одинаковых компьютеров, либо использовать символьные и целые данные для передачи действительных значений.
Функции преобразования IP-адресов inet_ntoa(), inet_aton()
Нам также понадобятся функции, осуществляющие перевод IP-адресов из символьного представления (в виде четверки чисел, разделенных точками) в числовое представление и обратно. Функция inet_aton() переводит символьный IP-адрес в числовое представление в сетевом порядке байт.
Функция возвращает 1, если в символьном виде записан правильный IP-адрес, и 0 в противном случае — для большинства системных вызовов и функций это нетипичная ситуация. Обратите внимание на использование указателя на структуру structin_addr в качестве одного из параметров данной функции. Эта структура используется для хранения IP-адресов в сетевом порядке байт. То, что используется структура, состоящая из одной переменной, а не сама 32-битовая переменная, сложилось исторически, и авторы в этом не виноваты.
Для обратного преобразования применяется функция inet_ntoa().
Функции преобразования IP-адресов
Прототипы функций
intinet_aton (const char *strptr,
char *inet_ntoa (structin_addr *addrptr);
Описание функций
Функция inet_aton переводит символьный IP-адрес, расположенный по указателю strptr, в числовое представление в сетевом порядке байт и заносит его в структуру, расположенную по адресу addrptr. Функция возвращает значение 1, если в строке записан правильный IP-адрес, и значение 0 в противном случае. Структура типа structin_addr используется для хранения IP-адресов в сетевом порядке байт и выглядит так:
То, что используется адрес такой структуры, а не просто адрес переменной типа in_addr_t, сложилось исторически.
Функция inet_ntoa применяется для обратного преобразования. Числовое представление адреса в сетевом порядке байт должно быть занесено в структуру типа structin_addr, адрес которой addrptr передается функции как аргумент. Функция возвращает указатель на строку, содержащую символьное представление адреса. Эта строка располагается в статическом буфере, при последующих вызовах ее новое содержимое заменяет старое содержимое.
Функция bzero()
Функция 2 настолько проста, что про нее нечего рассказывать. Все видно из описания.
Функция bzero
Прототип функции
void bzero (void *addr, int n);
Описание функции
Функция bzero заполняет первые n байт, начиная с адреса addr, нулевыми значениями. Функция ничего не возвращает.
Теперь мы можем перейти к системным вызовам, образующим интерфейс между пользовательским уровнем стека протоколов TCP/IP и транспортным протоколом UDP.
Создание сокета. Системный вызов socket()
Для создания сокета в операционной системе служит системный вызов socket(). Для транспортных протоколов семейства TCP/IP существует два вида сокетов: UDP-сокет — сокет для работы с датаграммами, и TCP сокет — потоковыйсокет. Однако понятие сокета не ограничивается рамками только этого семейства протоколов. Рассматриваемый интерфейс сетевых системных вызовов (socket(), bind(), recvfrom(), sendto() и т.д.) в операционной системе UNIX может применяться и для других стеков протоколов (и для протоколов, лежащих ниже транспортного уровня).
При создании сокета необходимо точно специфицировать его тип. Эта спецификация производится с помощью трех параметров вызова socket(). Первый параметр указывает, к какому семейству протоколов относится создаваемый сокет, а второй и третий параметры определяют конкретный протокол внутри данного семейства.
Второй параметр служит для задания вида интерфейса работы с сокетом — будет это потоковый сокет, сокет для работы с датаграммами или какой-либо иной. Третий параметр указывает протокол для заданного типа интерфейса. В стеке протоколов TCP/IP существует только один протокол для потоковых сокетов — TCP и только один протокол для датаграммных сокетов — UDP, поэтому для транспортных протоколов TCP/IP третий параметр игнорируется.
В других стеках протоколов может быть несколько протоколов с одинаковым видом интерфейса, например, датаграммных, различающихся по степени надежности.
Для транспортных протоколов TCP/IP мы всегда в качестве первого параметра будем указывать предопределенную константу AF_INET (Addressfamily — Internet) или ее синоним PF_INET (Protocolfamily — Internet).
Второй параметр будет принимать предопределенные значения SOCK_STREAM для потоковых сокетов и SOCK_DGRAM — для датаграммных.
Подобные документы
Характеристика модели клиент-сервер как технологии взаимодействия в информационной сети. Разработка и описание алгоритмов работы приложений на платформе Win32 в среде Microsoft Visual Studio, использующих для межпроцессного взаимодействия сокеты.
курсовая работа [544,6 K], добавлен 02.06.2014
Изучение сущности и основных функций программного интерфейса для обеспечения обмена данными между процессами, который называется сокет. Сокеты и UNIX. Атрибуты и именование сокета. Установка соединения (сервер, клиент). Обмен данными. Закрытие сокета.
презентация [99,1 K], добавлен 12.05.2013
Варианты топологии одноранговой вычислительной сети, принцип работы распределенных пиринговых сетей. Использование в крупных сетях модели «клиент-сервер». Характеристика операционных систем с сетевыми функциями, многопроцессорная обработка информации.
творческая работа [51,8 K], добавлен 26.12.2011
Организация связи между электронными устройствами. Коммуникационный протокол, основанный на архитектуре «клиент-сервер». Чтение флагов, дискретных входов, регистров хранения и регистров ввода. Запись регистра хранения. Обработка прерываний и запроса.
курсовая работа [1,4 M], добавлен 07.07.2011
Разработка приложений на платформе Win32 для исследования взаимодействия между процессами через отображение файла в память. Модель приложений «клиент — сервер». Описание алгоритма работы программы-клиента и программы-сервера. Результаты работы приложений.
курсовая работа [869,3 K], добавлен 18.05.2014
Особенности программной архитектуры клиент-сервер, взаимодействие серверов и пользователей сети Интернет согласно сетевым протоколам. Классификация служб по выполняемым функциям: доступ к гипертекстовому контенту, файлам, проведение телеконференций.
реферат [31,5 K], добавлен 12.07.2015
Общее понятие о пакете «java.net». Логическая структура соединений через сокеты. Создание объекта Socket, соединение между узлами Internet. Способы создания потока. Алгоритм работы системы клиент-сервер. Листинг ServerForm.java, запуск подпроцесса.
лабораторная работа [174,6 K], добавлен 27.11.2013
Читайте также:
- Прекрасное в жизни и искусстве реферат
- Медициналық интроскопияның негізгі реферат
- Эпидемиологическое значение почвы реферат
- Образ собаки в литературе xx века реферат
- Заболевание нервной системы у пожилых людей реферат
Предложите, как улучшить StudyLib
(Для жалоб на нарушения авторских прав, используйте
другую форму
)
Ваш е-мэйл
Заполните, если хотите получить ответ
Оцените наш проект
1
2
3
4
5
Цель работы: Изучение механизмов межпроцессного взаимодействия (InterProcess Communication) в Windows NT. Получение практических навыков по использованию Win32 API для программирования механизмов IPC.
Отображение файлов (File Mapping)
Механизм отображения файлов позволяет процессу трактовать содержимое файла как блок памяти в адресном пространстве этого процесса. Процесс может использовать обычные операции с указателями для того, чтобы считывать и изменять содержимое файла. Когда два или более процесса получают доступ к одинаковым отображениям файлов, то каждый процесс получает указатель на память в собственном адресном пространстве, которое он может использовать для модифицирования содержимого файла. Отсюда следует, что процессы также должны использовать объект синхронизации, например семафор, чтобы предотвратить разрушение данных в многозадачной среде.
Программист может использовать специальный вид отображения файла для обеспечения именованной разделенной памяти между процессами. Если при создании объекта-отображения файла указывается файл подкачки системы (swapping file), то отображение файла создается как блок совместно используемой памяти. Другие процессы также могут получить доступ к этому блоку, открыв такое же отображение файла.
Отображение файлов вполне эффективно, и, кроме того, предоставляет поддерживаемые операционной системой атрибуты безопасности для того, чтобы помочь предотвратить несанкционированный доступ к данным.
Механизм отображения файлов может быть использован процессами, работающими только на локальном компьютере; он не используется для передачи данных по сети.
Рассмотрим последовательно все операции, необходимые для создания отображения файлов, чтения/записи отображений и корректного завершения работы.
1. Создание именованной совместно используемой памяти.
Первый процесс вызывает функцию CreateFileMapping для того, чтобы создать объект-отображение файла с именем MyFileMappingObject. Используя флаг PAGE_READWRITE, процесс назначает права на чтение и запись данных при работе с файловым отображением.
Функция CreateFileMapping создает или открывает именованный или неименованный объект-отображение файла и имеет следующее определение (по MSDN 2001):
HANDLE CreateFileMapping(
HANDLE HFile, // дескриптор файла для отображения
LPSECURITY_ATTRIBUTES lpAttributes, // атрибуты безопасности
DWORD flProtect, // флаги защиты
DWORD dwMaximumSizeHigh, // старшие DWORD максимального размера
DWORD DwMaximumSizeLow, // младшие DWORD максимального размера
LPCTSTR lpName // имя объекта-отображения файла
);
HANDLE hMapFile;
HMapFile = CreateFileMapping(hFile, // Дескриптор открытого файла
NULL, // Безопасность по умолчанию
PAGE_READWRITE, // Разрешение чтения/записи
0, // Максимальный размер объекта
0, // Текущий размер hFile
«MyFileMappingObject»); // Имя отображения файла
// Проверяем корректность создания отображения
If (hMapFile == NULL)
ErrorHandler(«Не могу создать объект file-mapping.»);
Затем процесс использует дескриптор hMapFile при вызове функции MapViewOfFile, чтобы создать представление содержимого файла в адресном пространстве процесса. Функция возвращает указатель на представление файла в памяти.
Синтаксис функции MapViewOfFile по MDSN 2001:
LPVOID MapViewOfFile(
HANDLE HFileMappingObject, // дескриптор объекта-отображения файла
DWORD DwDesiredAccess, // режим доступа
DWORD DwFileOffsetHigh, // старший DWORD смещения
DWORD DwFileOffsetLow, // младший DWORD смещения
SIZE_T DwNumberOfBytesToMap // количество байт для сопоставления
);
LPVOID lpMapAddress;
LpMapAddress = MapViewOfFile(hMapFile, // Дескриптор объекта-отображения файла
FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи
0, // Максимальный размер объекта
0, // Размер hFile
0); // Отображать файл целиком
If (lpMapAddress == NULL)
ErrorHandler(«Не могу создать представление файла в памяти процесса.»);
Второй процесс вызывает функцию OpenFileMapping с именем MyFileMappingObject, чтобы использовать тот же объект-отображение файла, что и первый процесс. Также как и первый процесс, второй использует функцию MapViewOfFile для сопоставления области памяти процесса отображаемому файлу.
Синтаксис функции OpenFileMapping по MDSN 2001:
HANDLE OpenFileMapping(
DWORD DwDesiredAccess, // режим доступа
BOOL BInheritHandle, // флаг наследования
LPCTSTR LpName // имя объекта
);
HANDLE hMapFile;
LPVOID lpMapAddress;
HMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи
FALSE, // Не наследовать имя
«MyFileMappingObject»); // объекта-отображения файла.
If (hMapFile == NULL)
ErrorHandler(«Не могу открыть объект-отображение файла.»);
LpMapAddress = MapViewOfFile(hMapFile, // Дескриптор объекта-отображения файла.
FILE_MAP_ALL_ACCESS, // Разрешение чтения/записи
0, // Максимальный размер объекта
0, // Размер hFile.
0); // Отображать файл целиком
If (lpMapAddress == NULL)
ErrorHandler(«Не могу создать представление файла в памяти процесса.»);
2. Чтение/запись отображенных данных.
Для чтения из представления файла в памяти разыменуем указатель, полученный с помощью функции MapViewOfFile:
DWORD dwLength;
DwLength = *((LPDWORD) lpMapAddress);
Тот же указатель используется и для записи данных в отображенный файл:
*((LPDWORD) lpMapAddress) = dwLength;
Функция FlushViewOfFile копирует указанное количество байт представления файла в памяти в физический файл не ожидая пока произойдет операция кэшированной записи:
If (!FlushViewOfFile(lpMapAddress, dwBytesToFlush))
ErrorHandler(«Не могу сохранить изменения на диск.»);
3. Завершение работы с отображениями.
Каждый процесс вызывает функцию UnmapViewOfFile чтобы сделать недействительным указатель на отображенную память. Этим уничтожается сопоставление адресного пространства процесса объекту-отображению файла. Если это необходимо, функция UnmapViewOfFile также копирует измененные страницы памяти на диск.
If (!UnmapViewOfFile(lpMapAddress))
ErrorHandler(«Не могу уничтожить сопоставление.»);
Когда все процессы завершат использование объекта-отображения файла (вызвав предыдущую функцию), они должны закрыть дескрипторы объектов-отображений с помощью функции CloseHandle:
CloseHandle(hMapFile);
Замечание. В данном примере оставлены на самостоятельное рассмотрение вопросы, связанные с синхронизацией работы нескольких процессов, передающих данные через отображения файлов. Для обеспечения синхронизации см. лабораторную работу ОС-5.
Почтовые ящики (Mailslot)
Почтовые ящики обеспечивают только однонаправленные соединения. Каждый процесс, который создает почтовый ящик, является «сервером почтовых ящиков» (mailslot server). Другие процессы, называемые «клиентами почтовых ящиков» (mailslot clients), посылают сообщения серверу, записывая их в почтовый ящик. Входящие сообщения всегда дописываются в почтовый ящик и сохраняются до тех пор, пока сервер их не прочтет. Каждый процесс может одновременно быть и сервером и клиентом почтовых ящиков, создавая, таким образом, двунаправленные коммуникации между процессами.
Клиент может посылать сообщения на почтовый ящик, расположенный на том же компьютере, на компьютере в сети, или на все почтовые ящики с одним именем всем компьютерам выбранного домена. При этом широковещательное сообщение, транслируемое по домену, не может быть более 400 байт. В остальных случаях размер сообщения ограничивается только при создании почтового ящика сервером.
Почтовые ящики предлагают легкий путь для обмена короткими сообщениями, позволяя при этом вести передачу и по локальной сети, в том числе и по всему домену.
Mailslot является псевдофайлом находящимся в памяти и вы должны использовать стандартные функции для работы с файлами, чтобы получить доступ к нему. Данные в почтовом ящике могут быть в любой форме – их интерпретацией занимается прикладная программа, но их общий объем не должен превышать 64 Кб. Однако, в отличии от дисковых файлов, mailslot’ы являются временными — когда все дескрипторы почтового ящика закрыты, он и все его данные удаляются. Заметим, что все почтовые ящики являются локальными по отношению к создавшему их процессу; процесс не может создать удаленный mailslot.
Сообщения меньше, чем 425 байт, передаются с использованием дейтаграмм. Сообщения больше чем 426 байт используют передачу с установлением логического соединения на основе SMB-сеансов. Передачи с установлением соединения допускают только индивидуальную передачу от одного клиента к одному серверу. Следовательно, вы теряете возможность широковещательной трансляции сообщений от одного клиента ко многим серверам. Учтите, что Windows не поддерживает сообщения размером в 425 или 426 байт.
Когда процесс создает почтовый ящик, имя последнего должно иметь следующую форму:
.mailslot[Path]Name
Например:
.mailslotTaxesbobs_comments
.mailslotTaxespetes_comments
.mailslotTaxessues_comments
Если вы хотите отправить сообщение в почтовый ящик на удаленный компьютер, то воспользуйтесь NETBIOS-именем:
ComputerNamemailslot[path]name
Если же ваша цель передать сообщение всем mailslot’ам с указанным именем внутри домена, вам понадобится NETBIOS-имя домена:
DomainNamemailslot[path]name
Или для главного домена операционной системы (домен в котором находится рабочая станция):
*mailslot[path]name
Клиенты и серверы, использующие почтовые ящики, при работе с ними должны пользоваться следующими функциями:
Функции серверов почтовых ящиков
Функция |
Описание |
CreateMailslot |
Создает почтовый ящик и возвращает его дескриптор. |
GetMailslotInfo |
Извлекает максимальный размер сообщения, размер почтового ящика, размер следующего сообщения в ящике, количество сообщений и время ожидания сообщения при выполнении операции чтения. |
SetMailslotInfo |
Изменение таймаута при чтении из почтового ящика. |
Функция |
Описание |
DuplicateHandle |
Дублирование дескриптора почтового ящика. |
ReadFile, ReadFileEx |
Считывание сообщений из почтового ящика. |
GetFileTime |
Получение даты и времени создания mailslot’а. |
SetFileTime |
Установка даты и времени создания mailslot’а. |
GetHandleInformation |
Получение свойств дескриптора почтового ящика. |
SetHandleInformation |
Установка свойств дескриптора почтового ящика. |
Функции клиентов почтовых ящиков
Функция |
Описание |
CloseHandle |
Закрывает дескриптор почтового ящика для клиентского процесса. |
CreateFile |
Создает дескриптор почтового ящика для клиентского процесса. |
DuplicateHandle |
Дублирование дескриптора почтового ящика. |
WriteFile, WriteFileEx |
Запись сообщений в почтовый ящик. |
Рассмотрим последовательно все операции, необходимые для корректной работы с почтовыми ящиками.
1. Создание почтового ящика.
Эта операция выполняется процессом сервера с использованием функции CreateMailslot:
HANDLE CreateMailslot(
LPCTSTR lpName, // имя почтового ящика
DWORD nMaxMessageSize, // максимальный размер сообщения
DWORD lReadTimeout, // таймаут операции чтения
LPSECURITY_ATTRIBUTES lpSecurityAttributes // опции наследования и
); // безопасности
BOOL FAR PASCAL Makeslot(HWND hwnd, HDC hdc)
{
LPSTR lpszSlotName = «\.mailslotsample_mailslot»;
// Дескриптор почтового ящика «hSlot1» определен глобально.
hSlot1 = CreateMailslot(lpszSlotName,
0, // без максимального размера сообщения
MAILSLOT_WAIT_FOREVER, // без таймаута при операциях
(LPSECURITY_ATTRIBUTES) NULL); // без атрибутов безопасности
if (hSlot1 == INVALID_HANDLE_VALUE)
{
ErrorHandler(hwnd, «CreateMailslot»); // обработка ошибки
return FALSE;
}
TextOut(hdc, 10, 10, «CreateMailslot вызвана удачно.», 26);
return TRUE;
}
2. Запись сообщений в почтовый ящик.
Запись в mailslot производится аналогично записи в стандартный дисковый файл. Следующий код иллюстрирует как с помощью функций CreateFile, WriteFile и CloseHandle можно поместить сообщение в почтовый ящик.
LPSTR lpszMessage = «Сообщение для sample_mailslot в текущем домене.»;
BOOL fResult;
HANDLE hFile;
DWORD cbWritten;
// С помощью функции CreateFile клиент открывает mailslot для записи сообщений
HFile = CreateFile(«\*mailslotsample_mailslot»,
GENERIC_WRITE,
FILE_SHARE_READ, // Требуется для записи в mailslot
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
If (hFile == INVALID_HANDLE_VALUE)
{
ErrorHandler(hwnd, «Ошибка открытия почтового ящика»);
return FALSE;
}
// Запись сообщения в почтовый ящик
FResult = WriteFile(hFile,
lpszMessage,
(DWORD) lstrlen(lpszMessage) + 1, // включая признак конца строки
&cbWritten,
(LPOVERLAPPED) NULL);
If (!fResult)
{
ErrorHandler(hwnd, «Ошибка при записи сообщения»);
return FALSE;
}
TextOut(hdc, 10, 10, «Сообщение отправлено успешно.», 21);
FResult = CloseHandle(hFile);
If (!fResult)
{
ErrorHandler(hwnd, «Ошибка при закрытии дескриптора»);
return FALSE;
}
TextOut(hdc, 10, 30, «Дескриптор закрыт успешно.», 23);
Return TRUE;
3. Чтение сообщений из почтового ящика.
Создавший почтовый ящик процесс получает право считывания сообщений из него используя дескриптор mailslot’а в вызове функции ReadFile. Следующий пример использует функцию GetMailslotInfo чтобы определить сколько сообщений находится в почтовом ящике. Если есть непрочитанные сообщения, то они отображаются в окне сообщения вместе с количеством оставшихся сообщений.
Почтовый ящик существует до тех пор, пока не вызвана функция CloseHandle на сервере или пока существует сам процесс сервера. В обоих случаях все непрочитанные сообщения удаляются из почтового ящика, уничтожаются все клиентские дескрипторы и mailslot удаляется из памяти.
Функция считывает параметры почтового ящика:
BOOL GetMailslotInfo(
HANDLE hMailslot, // дескриптор почтового ящика
LPDWORD lpMaxMessageSize, // максимальный размер сообщения
LPDWORD lpNextSize, // размер следующего непрочитанного сообщения
LPDWORD lpMessageCount, // количество сообщений
LPDWORD lpReadTimeout // таймаут операции чтения
);
Функция устанавливает таймаут операции чтения:
BOOL SetMailslotInfo(
HANDLE hMailslot, // дескриптор почтового ящика
DWORD lReadTimeout // новый таймаут операции чтения
);
BOOL WINAPI Readslot(HWND hwnd, HDC hdc)
{
DWORD cbMessage, cMessage, cbRead;
BOOL fResult;
LPSTR lpszBuffer;
CHAR achID[80];
DWORD cAllMessages;
HANDLE hEvent;
OVERLAPPED ov;
cbMessage = cMessage = cbRead = 0;
hEvent = CreateEvent(NULL, FALSE, FALSE, «ExampleSlot»);
ov. Offset = 0;
ov. OffsetHigh = 0;
ov. hEvent = hEvent;
// Дескриптор почтового ящика «hSlot1» определен глобально.
fResult = GetMailslotInfo(hSlot1, // дескриптор mailslot’а
(LPDWORD) NULL, // без ограничения размера сообщения
&cbMessage, // размер следующего сообщения
&cMessage, // количество сообщений в ящике
(LPDWORD) NULL); // без таймаута чтения
if (!fResult)
{
ErrorHandler(hwnd, «Ошибка при получении информации о почтовом ящике»);
return FALSE;
}
if (cbMessage == MAILSLOT_NO_MESSAGE)
{
TextOut(hdc, 10, 10, «Нет непрочитанных сообщений.», 20);
return TRUE;
}
cAllMessages = cMessage;
while (cMessage!= 0) // Считываем все сообщения
{
// Создаем строку с номером сообщения.
wsprintf((LPSTR) achID,
» Message #%d of %d «, cAllMessages — cMessage + 1,
cAllMessages);
// Выделяем память для сообщения.
lpszBuffer = (LPSTR) GlobalAlloc(GPTR,
lstrlen((LPSTR) achID) + cbMessage);
lpszBuffer[0] = »;
// Считываем сообщение из почтового ящика
fResult = ReadFile(hSlot1,
lpszBuffer,
cbMessage,
&cbRead,
&ov);
if (!fResult)
{
ErrorHandler(hwnd, «Ошибка чтения сообщения»);
GlobalFree((HGLOBAL) lpszBuffer);
return FALSE;
}
// Формируем строку с номером и текстом сообщения.
lstrcat(lpszBuffer, (LPSTR) achID);
// Выводим сообщение на экран.
MessageBox(hwnd,
lpszBuffer,
«Содержимое почтового ящика»,
MB_OK);
GlobalFree((HGLOBAL) lpszBuffer);
fResult = GetMailslotInfo(hSlot1, // дексриптор почтового ящика
(LPDWORD) NULL, // размер сообщения не ограничен
&cbMessage, // размер следующего сообщения
&cMessage, // количество сообщения
(LPDWORD) NULL); // без таймаута чтения
if (!fResult)
{
ErrorHandler(hwnd, «Ошибка при получении информации о mailslot’е»);
return FALSE;
}
}
return TRUE;
}
Каналы (pipe)
Существует два способа организовать двунаправленное соединение с помощью каналов: безымянные и именованные каналы.
Безымянные (или анонимные) каналы позволяют связанным процессам передавать информацию друг другу. Обычно, безымянные каналы используются для перенаправления стандартного ввода/вывода дочернего процесса так, чтобы он мог обмениваться данными с родительским процессом. Чтобы производить обмен данными в обоих направлениях, вы должны создать два безымянных канала. Родительский процесс записывает данные в первый канал, используя его дескриптор записи, в то время как дочерний процесс считывает данные из канала, используя дескриптор чтения. Аналогично, дочерний процесс записывает данные во второй канал и родительский процесс считывает из него данные. Безымянные каналы не могут быть использованы для передачи данных по сети и для обмена между несвязанными процессами.
Именованные каналы используются для передачи данных между независимыми процессами или между процессами, работающими на разных компьютерах. Обычно, процесс сервера именованных каналов создает именованный канал с известным именем или с именем, которое будет передано клиентам. Процесс клиента именованных каналов, зная имя созданного канала, открывает его на своей стороне с учетом ограничений, указанных процессом сервера. После этого между сервером и клиентом создается соединение, по которому может производиться обмен данными в обоих направлениях. В дальнейшем наибольший интерес для нас будут представлять именованные каналы.
При создании и получении доступа к существующему каналу необходимо придерживаться следующего стандарта имен каналов:
.PipePipename
Если канал находится на удаленном компьютере, то вам потребуется NETBIOS-имя компьютера:
ComputerNamePipePipename
Клиентам и серверам для работы с каналами допускается использовать функции из следующего списка:
Функция |
Описание |
CallNamedPipe |
Выполняет подключение к каналу, записывает в канал сообщение, считывает из канала сообщение и затем закрывает канал. |
ConnectNamedPipe |
Позволяет серверу именованных каналов ожидать подключения одного или нескольких клиентских процессов к экземпляру именованного канала. |
CreateNamedPipe |
Создает экземпляр именованного канала и возвращает дескриптор для последующих операций с каналом. |
CreatePipe |
Создает безымянный канал. |
DisconnectNamedPipe |
Отсоединяет серверную сторону экземпляра именованного канала от клиентского процесса. |
GetNamedPipeHandleState |
Получает информацию о работе указанного именованного канала. |
GetNamedPipeInfo |
Извлекает свойства указанного именованного канала. |
PeekNamedPipe |
Копирует данные их именованного или безымянного канала в буфер без удаления их из канала. |
SetNamedPipeHandleState |
Устанавливает режим чтения и режим блокировки вызова функций (синхронный или асинхронный) для указанного именованного канала. |
TransactNamedPipe |
Комбинирует операции записи сообщения в канал и чтения сообщения из канала в одну сетевую транзакцию. |
WaitNamedPipe |
Ожидает, пока истечет время ожидания или пока экземпляр указанного именованного канала не будет доступен для подключения к нему. |
Кроме того, для работы с каналами используется функция CreateFile (для подключения к каналу со стороны клиента) и функции WriteFile и ReadFile для записи и чтения данных в/из канала соответственно.
Рассмотрим пример синхронной работы с каналами (т. е. с использованием блокирующих вызовов функций работы с каналами).
1. Многопоточный сервер каналов. Синхронный режим работы.
В данном примере главный поток состоит из цикла, в котором создается экземпляр канала и ожидается подключение клиента. Когда клиент успешно подсоединяется к каналу, сервер создает новый поток, обслуживающий этого клиента, и продолжает ожидание новых подключений.
Поток, обслуживающий каждый экземпляр канала считывает запросы из него и отправляет ответы по этому же каналу до тех пор пока не произойдет отсоединение от клиента. Когда клиент закрывает дескриптор канала, поток сервера также выполняет отсоединение, закрывает дескриптор канала и завершает свою работу.
#include <stdio. h>
#include <stdlib. h>
#include <string. h>
#include <windows. h>
VOID InstanceThread(LPVOID);
VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD);
Int xx = 0;
DWORD main(VOID)
{
BOOL fConnected;
DWORD dwThreadId;
HANDLE hPipe, hThread;
LPTSTR lpszPipename = «\.pipemynamedpipe»;
// Главный цикл создает экземпляр именованного канала и
// затем ожидает подключение клиентов к нему. Когда происходит
// подключение, создается поток, производящий обмен данными
// с клиентом, а выполнение главного цикла продолжается.
for (;;)
{
hPipe = CreateNamedPipe(
lpszPipename, // Имя канала
PIPE_ACCESS_DUPLEX, // Дуплексный доступ к каналу
PIPE_TYPE_MESSAGE | // Установка режима работы канала
PIPE_READMODE_MESSAGE | // для передачи по нему отдельных сообщений
PIPE_WAIT, // Синхронное выполнение операций с каналом
PIPE_UNLIMITED_INSTANCES, // Неограниченное количество экземпляров
BUFSIZE, // Размер буфера отправки
BUFSIZE, // Размер буфера приема
PIPE_TIMEOUT, // Время ожидания клиента
NULL); // Без дополнительных атрибутов безопасности
if (hPipe == INVALID_HANDLE_VALUE)
MyErrExit(«Экземпляр именованного канала не создан»);
// Ждем, пока не подсоединится клиент; в случае успешного подключения,
// Функция возвращает ненулевое значение. Если функция вернет ноль,
// то GetLastError вернет значение ERROR_PIPE_CONNECTED.
fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (fConnected)
{
// Создаем поток для обслуживания клиента.
hThread = CreateThread(
NULL, // без атрибутов безопасности
0, // размер стека по умолчанию
(LPTHREAD_START_ROUTINE) InstanceThread,
(LPVOID) hPipe, // параметр потока – дескриптор канала
0, // без отложенного запуска
&dwThreadId); // возвращает дескриптор потока
if (hThread == NULL)
MyErrExit(«Создание потока произошло с ошибками»);
else
CloseHandle(hThread);
}
else
// Если клиент не смог подсоединиться, то уничтожаем экземпляр канала.
CloseHandle(hPipe);
}
return 1;
}
// Главная функция потока, обслуживающего клиентские подключения
VOID InstanceThread(LPVOID lpvParam)
{
CHAR chRequest[BUFSIZE];
CHAR chReply[BUFSIZE];
DWORD cbBytesRead, cbReplyBytes, cbWritten;
BOOL fSuccess;
HANDLE hPipe;
// Переданный потоку параметр интерпретируем как дескриптор канала.
hPipe = (HANDLE) lpvParam;
while (1)
{
// Считываем из канала запросы клиентов.
fSuccess = ReadFile(
hPipe, // дескриптор канала
chRequest, // буфер для получения данных
BUFSIZE, // указываем размер буфера
&cbBytesRead, // запоминаем количество считанных байт
NULL); // синхронный режим ввода-вывода
// Обрабатываем запрос если он корректен
if (! fSuccess || cbBytesRead == 0)
break;
GetAnswerToRequest(chRequest, chReply, &cbReplyBytes);
// Записываем в канал результат обработки клиентского запроса.
fSuccess = WriteFile(
hPipe, // дескриптор канала
chReply, // буфер с данными для передачи
cbReplyBytes, // количество байт для передачи
&cbWritten, // запоминаем количество записанных в канал байт
NULL); // синхронный режим ввода-вывода
if (! fSuccess || cbReplyBytes!= cbWritten) break;
}
// Записываем содержимое буферов в канал, чтобы позволить клиенту считать
// остаток информации перед отключением. Затем выполним отсоединение от
// канала и уничтожаем дескриптор этого экземпляра канала.
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
2. Клиент каналов. Синхронный режим работы.
В данном примере клиент открывает именованный канал с помощью функции CreateFile и устанавливает канал в режим чтения/записи сообщений с помощью функции SetNamedPipeHandleState. Затем использует функции WriteFile и ReadFile для отправки запросов серверу и чтения ответов сервера соответственно.
#include <windows. h>
DWORD main(int argc, char *argv[])
{
HANDLE hPipe;
LPVOID lpvMessage;
CHAR chBuf[512];
BOOL fSuccess;
DWORD cbRead, cbWritten, dwMode;
LPTSTR lpszPipename = «\.pipemynamedpipe»;
// Пытаемся открыть именованный канал; если необходимо, то подождем.
while (1)
{
hPipe = CreateFile(
lpszPipename, // имя канала
GENERIC_READ | // доступ на чтение и запись данных
GENERIC_WRITE,
0, // без разделения доступа
NULL, // без дополнительных атрибутов безопасности
OPEN_EXISTING, // открываем существующий канал
0, // задаем атрибуты по умолчанию
NULL); // без файла шаблона
// Если подключение успешно, то выходим из цикла ожидания.
if (hPipe!= INVALID_HANDLE_VALUE)
break;
// Если возникает ошибка отличная от ERROR_PIPE_BUSY, то прекращаем работу.
if (GetLastError() != ERROR_PIPE_BUSY)
MyErrExit(«Не могу открыть канал»);
// Ждем 20 секунд до повторного подключения.
if (! WaitNamedPipe(lpszPipename, 20000) )
MyErrExit(«Не могу открыть канал»);
}
// Канал подключен успешно; сменим режим работы канала на чтение/запись сообщений
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe, // дескриптор канала
&dwMode, // новый режим работы
NULL, // не устанавливаем максимальный размер
NULL); // не устанавливаем максимальное время
if (!fSuccess)
MyErrExit(«Невозможно сменить режим работы канала»);
// Отправляем сообщения серверу.
lpvMessage = (argc > 1) ? argv[1] : «default message»;
fSuccess = WriteFile(
hPipe, // дескриптор канала
lpvMessage, // сообщение
strlen(lpvMessage) + 1, // длина сообщения
&cbWritten, // количество записанных в канал байт
NULL); // синхронный ввод/вывод
if (! fSuccess)
MyErrExit(«Запись сообщения в канал не удалась»);
do
{
// Считываем сообщения за канала.
fSuccess = ReadFile(
hPipe, // дескриптор канала
chBuf, // буфер для получения ответа
512, // размер буфера
&cbRead, // количество считанных из канала байт
NULL); // синхронный ввод/вывод
if (! fSuccess && GetLastError() != ERROR_MORE_DATA)
break;
// Следующий код – код обработки ответа сервера.
// В данном случае просто выводим сообщение на STDOUT
if (! WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
chBuf, cbRead, &cbWritten, NULL))
break;
} while (! fSuccess); // если ERROR_MORE_DATA (т. е. еще остались данные),
// то повторяем считывание из канала
// Закрываем канал
CloseHandle(hPipe);
return 0;
}
3. Совмещенное чтение/запись данных при работе с каналом.
Транзакции в именованных каналах — это клиент–серверные коммуникации, объединяющие операции записи и чтения в одну сетевую операцию. Такие транзакции погут быть использованы только на дуплексных, ориентированных на сообщения, каналах. Совмещенное чтение/запись данных позволяет увеличить производительность канала между клиентом и удаленным сервером. Процессы могут использовать функции TransactNamedPipe и CallNamedPipe для организации транзакций.
Функция TransactNamedPipe обычно используется клиентами каналов, но при необходимости может быть использована и серверами. Следующий код показывает пример вызова данной функции клиентом.
FSuccess = TransactNamedPipe(
hPipe, // дескриптор канала
lpszWrite, // сообщение серверу
strlen(lpszWrite)+1, // длина сообщения серверу
chReadBuf, // буфер для получения ответа
512, // размер буфера ответа
&cbRead, // количество считанных байт
NULL); // синхронный вызов функции
// Если возникла ошибка, то выходим из функции иначе выводим ответ сервера
// и, если необходимо, считываем оставшиеся данные из канала
If (!fSuccess && (GetLastError() != ERROR_MORE_DATA))
{
MyErrExit(«Ошибка при выполнении транзакции»);
}
While(1)
{
// Направляем на STDOUT считанные из канала данные.
if (! WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
chReadBuf, cbRead, &cbWritten, NULL) )
break;
// Если все операции прошли успешно, то выходим из цикла.
if (fSuccess)
break;
// Если в канале еще остались данные, то считываем их.
fSuccess = ReadFile(
hPipe, // дескриптор канала
chReadBuf, // буфер для получения ответа
512, // размер буфера
&cbRead, // количество считанных байт
NULL); // синхронный вызов функции
// Если возникла ошибка отличная от ERROR_MORE_DATA, то прекращаем работу.
if (! fSuccess && (GetLastError() != ERROR_MORE_DATA))
break;
}
Следующий код показывает вызов функции CallNamedPipe клиентом каналов.
// Комбинирует операции соединения, ожидания, записи, чтения и закрытия канала.
FSuccess = CallNamedPipe(
lpszPipename, // дескриптор канала
lpszWrite, // сообщение серверу
strlen(lpszWrite)+1, // длина сообщения серверу
chReadBuf, // буфер для получения ответа
512, // размер буфера для получения ответа
&cbRead, // количество считанных байт
20000); // ждем 20 секунд
If (fSuccess || GetLastError() == ERROR_MORE_DATA)
{
// В случае успеха выводим данные на STDOUT.
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
chReadBuf, cbRead, &cbWritten, NULL);
// Канал уже закрыт, следовательно оставшиеся в нем данные
// прочесть уже невозможно – они безвозвратно потеряны.
if (! fSuccess)
printf(» …дополнительные данные в сообщении потеряны «);
}
СОДЕРЖАНИЕ ОТЧЕТА 14. Наименование лабораторной работы, ее цель. 15. Программу, выполняющую с помощью механизмов межпроцессного взаимодействия (отображение файлов, почтовые ящики, каналы) одну из следующих задач (в соответствии с № по журналу): § Реализовать вычисление определителя квадратной матрицы с помощью разложения ее на определители меньшего порядка. При этом «ведущий» процесс рассылает задания «ведомым» процессам, последние выполняют вычисление определителей, а затем главный процесс вычисляет окончательный результат. Взаимодействие выполнить с помощью: |
||
1 вариант |
Отображения файлов |
|
2 вариант |
Почтовых ящиков |
|
3 вариант |
Каналов |
|
§ Реализовать нахождение обратной матрицы методом Гаусса, при этом задания по решению систем линейных уравнений распределяются поровну для каждого процесса. Взаимодействие выполнить с помощью: |
||
4 вариант |
Отображения файлов |
|
5 вариант |
Почтовых ящиков |
|
6 вариант |
Каналов |
|
§ Реализовать перемножение двух матриц с помощью нескольких процессов: каждый процесс выполняет перемножение строки первой матрицы на столбец второй (в соответствии с правилом умножения матриц). При необходимости процессам, выполняющим умножение, может быть отправлено несколько заданий. Взаимодействие выполнить с помощью: |
||
7 вариант |
Отображения файлов |
|
8 вариант |
Почтовых ящиков |
|
9 вариант |
Каналов |
|
§ Реализовать алгоритм блочной сортировки файла целых чисел. Каждый процесс, выполняющий сортировку, получает свою часть файла от ведущего процесса и сортирует его. Ведущий процесс выполняет упорядочивание уже отсортированных блоков. При необходимости ведомым процессам может быть выделено более одного задания на сортировку. Взаимодействие выполнить с помощью: |
||
10 вариант |
Отображения файлов |
|
11 вариант |
Почтовых ящиков |
|
12 вариант |
Каналов |
|
§ Реализовать обмен текстовыми сообщениями между несколькими процессами. Обеспечить возможность отправки сообщения сразу нескольким адресатам. Реализовать подтверждение приема сообщения адресатом или, в случае потери сообщения, повторную его передачу. Взаимодействие выполнить с помощью: |
||
13 |
Отображения файлов |
|
14 |
Почтовых ящиков |
|
15 |
Каналов |
|
16. Примеры разработанных приложений (программы и результаты).
СОДЕРЖАНИЕ ОТЧЕТА 17. Наименование лабораторной работы, ее цель. 18. Составить и изучить следующие программы: 19. Реализовать с помощью механизмов межпроцессного взаимодействия (отображение файлов, почтовые ящики, каналы) следующие задачи: Реализовать вычисление определителя квадратной матрицы с помощью разложения ее на определители меньшего порядка. При этом «ведущий» процесс рассылает задания «ведомым» процессам, последние выполняют вычисление определителей, а затем главный процесс вычисляет окончательный результат. Взаимодействие выполнить с помощью: |
|
1 |
Отображения файлов |
2 |
Почтовых ящиков |
3 |
Каналов |
Реализовать нахождение обратной матрицы методом Гаусса, при этом задания по решению систем линейных уравнений распределяются поровну для каждого процесса. Взаимодействие выполнить с помощью: |
|
4 |
Отображения файлов |
5 |
Почтовых ящиков |
6 |
Каналов |
Реализовать перемножение двух матриц с помощью нескольких процессов: каждый процесс выполняет перемножение строки первой матрицы на столбец второй (в соответствии с правилом умножения матриц). При необходимости процессам, выполняющим умножение, может быть отправлено несколько заданий. Взаимодействие выполнить с помощью: |
|
7 |
Отображения файлов |
8 |
Почтовых ящиков |
9 |
Каналов |
Реализовать алгоритм блочной сортировки файла целых чисел. Каждый процесс, выполняющий сортировку, получает свою часть файла от ведущего процесса и сортирует его. Ведущий процесс выполняет упорядочивание уже отсортированных блоков. При необходимости ведомым процессам может быть выделено более одного задания на сортировку. Взаимодействие выполнить с помощью: |
|
10 |
Отображения файлов |
11 |
Почтовых ящиков |
12 |
Каналов |
Реализовать обмен текстовыми сообщениями между несколькими процессами. Обеспечить возможность отправки сообщения сразу нескольким адресатам. Реализовать подтверждение приема сообщения адресатом или, в случае потери сообщения, повторную его передачу. Взаимодействие выполнить с помощью: |
|
13 |
Отображения файлов |
14 |
Почтовых ящиков |
15 |
Каналов |
20. Примеры разработанных приложений (программы и результаты).
Слайд 1
Описание слайда:
Операционные системы
Межпроцессное взаимодействие
Слайд 2
Описание слайда:
Межпроцессное взаимодействие
Синхронизация потоков с использованием объектов ядра Windows 2000+
Слайд 3
Описание слайда:
Синхронизация потоков
Простейшей формой коммуникации потоков является синхронизация (synchronization).
Синхронизация означает способность потока добровольно приостанавливать свое исполнение и ожидать, пока не завершится выполнение некоторой операции другим потоком.
Все операционные системы, поддерживающие многозадачность или мультипроцессорную обработку, должны предоставлять потокам способ ожидания того, что другой поток что–либо сделает. Пример – асинхронный ввод-вывод.
Слайд 4
Описание слайда:
Объекты синхронизации и их состояния
Объекты синхронизации (synchronization objects) – это объекты ядра, при помощи которых поток синхронизирует свое выполнение.
В любой момент времени синхронизационный объект находится в одном из двух состояний: свободен (signaled state) или занят.
Правила, по которым объект переходит в свободное или занятое состояние, зависят от типа этого объекта.
Слайд 5
Описание слайда:
Объекты синхронизации MS Windows 2000+ и их состояния
процессы
потоки
задания
файлы
консольный ввод
Слайд 6
Описание слайда:
Примеры функционирования объектов синхронизации
Объект-поток находится в состоянии «занят» все время существования, но устанавливается системой в состояние «свободен», когда его выполнение завершается.
Аналогично, ядро устанавливает процесс в состояние «свободен», когда завершился его последний поток.
В противоположность этому, объект – таймер «срабатывает» через заданное время (по истечении этого времени ядро устанавливает объект – таймер в состояние «свободен»).
Слайд 7
Описание слайда:
Спящие потоки
Слайд 8
Описание слайда:
Функции ожидания
DWORD WaitForSingleObject(
HANDLE hObject,
DWORD dwMilliseconds
);
Слайд 9
Описание слайда:
Функция одиночного ожидания
Первый параметр, hObject, идентифицирует объект ядра, поддерживающий синхронизацию.
Второй параметр, dwMilliseconds, указывает, сколько времени (в миллисекундах) поток готов ждать освобождения объекта.
Представленный ниже вызов сообщает системе, что поток будет ждать до тех пор, пока не завершится процесс, идентифицируемый дескриптором hProcess.
Слайд 10
Описание слайда:
Функция множественного ожидания
Функция WaitForMultipleObjects позволяет ждать освобождения сразу нескольких объектов или какого-то одного из списка объектов.
Параметр dwCount определяет количество интересующих Вас объектов ядра Его значение должно быть в пределах от 1 до MAXIMUM_WAIT_OBJECTS (в заголовочных файлах Windows оно определено как 64).
Параметр phObject – это указатель на массив дескрипторов объектов синхронизации.
Параметр fWaitAll – флаг, который указывает режим ожидания всех заданных объектов ядра (TRUE), либо режим ожидания освобождения любого первого из них (FALSE).
Слайд 11
Описание слайда:
Специализированные объекты синхронизации
Событие
Таймер ожидания
Семафор
Мьютекс
Слайд 12
Описание слайда:
События
Событие – самая примитивная разновидность объектов синхронизации, которая просто уведомляют об окончании какой-либо операции.
События содержат счетчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сообщает тип данного объекта-события, другая – его состояние (свободен или занят).
Объекты-события бывают двух типов: со сбросом вручную (manual-reset events) и с автосбросом (auto-reset events).
События с «ручным» сбросом нужно применять, если событие ждут несколько потоков. Только этот тип события позволяет выполнить синхронизацию нескольких потоков от одного события.
Слайд 13
Описание слайда:
Сравнение работы события с ручным сбросом и автосбросом
Слайд 14
Описание слайда:
Применение «событий»
Объекты-события обычно используют в том случае, когда какой-то поток выполняет инициализацию, а затем сигнализирует другому потоку, что тот может продолжить работу.
Инициализирующий поток переводит объект «событие» в занятое состояние и приступает к своим операциям.
Закончив, он сбрасывает событие в свободное состояние.
Тогда другой поток, который ждал перехода события в свободное состояние, пробуждается и вновь становится планируемым.
Слайд 15
Описание слайда:
Создание объекта типа «событие»
HANDLE CreateEvent (
LPSECURITY_ATTRIBUTES lpEventAttributes, // атрибуты // защиты
BOOL bManualReset, // тип сброса
BOOL bInitialState, // начальное состояние
LPCTSTR lpName // имя объекта
);
Параметр bManualReset сообщает системе, хотите Вы создать событие со сбросом вручную (TRUE) или с автосбросом (FALSE).
Параметр bInitialState определяет начальное состояние события – свободное (TRUE) или занятое (FALSE).
Слайд 16
Описание слайда:
Совместное использование объекта «события» процессами
Если объект «событие» успешно создан, то CreateEvent () возвращает дескриптор объекта специфичный для конкретного процесса.
Потоки из других процессов могут получить доступ к этому объекту:
наследованием описателя с применением функции DuplicateHandle () (универсальный способ);
вызовом OpenEvent () c передачей в параметре lpName имени, совпадающего с указанным в аналогичном параметре функции CreateEvent ().
Слайд 17
Описание слайда:
Открытие объекта типа «событие»
HANDLE OpenEvent(
DWORD dwDesiredAccess, // режим доступа
BOOL bInheritHandle, // флаг наследования
LPCTSTR lpName // имя обьекта
);
Функция OpenEvent () возвращает дескриптор существующего именованного объекта события.
Вызывающий процесс может использовать возвращаемый данной функцией дескриптор в качестве аргумента любой функции, использующей дескриптор объекта события, при условии соблюдения ограничений, налагаемых значением аргумента dwDesiredAccess.
Слайд 18
Описание слайда:
Режимы доступа к «событиям»
EVENT_ALL_ACCESS – полный доступ (разрешены все возможные виды доступа)
EVENT_MODIFY_STATE – обеспечивает возможность использования дескриптора объекта события в функциях SetEvent () и ResetEvent (), изменяющих состояние объекта события
SYNCHRONIZE – позволяет использовать дескриптор объекта события в любой из функций ожидания
Слайд 19
Описание слайда:
Управление «событиями»
Перевод события в свободное состояние:
BOOL SetEvent (HANDLE hEvent);
Перевод события в занятое состояние:
BOOL ResetEvent (HANDLE hEvent);
Освобождение события и перевод его обратно в занятое состояние:
BOOL PulseEvent (HANDLE hEvent);
Слайд 20
Описание слайда:
Особенности PulseEvent
Функция PulseEvent () устанавливает событие и тут же переводит его обратно в сброшенное состояние, это равнозначно последовательному вызову SetEvent () и ResetEvent ().
Если PulseEvent () вызывается для события со сбросом вручную, то все потоки, ожидающие этот объект, получают управление.
При вызове PulseEvent () для события с автосбросом пробуждается только один из ждущих потоков. А если ни один из потоков не ждет объект-событие, вызов функции не дает никакого эффекта.
Слайд 21
Описание слайда:
Пример использования «события» для синхронизации потоков
Слайд 22
Описание слайда:
Таймеры ожидания
Таймеры ожидания (waitable timers) – это объекты ядра, которые самостоятельно переходят в свободное состояние в определенное время или через регулярные промежутки времени.
Объекты «таймеры ожидания» всегда создаются в занятом состоянии («0»).
Слайд 23
Описание слайда:
Создание и открытие таймера ожидания
HANDLE CreateWaitableTimer (
LPSECURITY_ATTRIBUTES lpMutexAttributes,
// атрибуты защиты
BOOL bManualReset,// тип сброса таймера,TRUE – ручной
LPCTSTR lpName // имя объекта
);
HANDLE OpenWaitableTimer (
DWORD fdwAccess, // режим доступа
BOOL fInherit, // флаг наследования
LPCTSTR lpName // имя объекта
);
Слайд 24
Описание слайда:
Режимы доступа к таймеру
TIMER_ALL_ACCESS – полный доступ (разрешены все возможные виды доступа)
TIMER_MODIFY_STATE – определяет возможность изменения состояния таймера функциями SetWaitableTimer () и CancelWaitableTimer ()
SYNCHRONIZE – позволяет использовать дескриптор объекта таймера в любой из функций ожидания
Слайд 25
Описание слайда:
Управление таймером ожидания
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime,
LONG lPeriod,
LPTIMERAPCROUTINE pfnCompletionRoutine,
LPVOID pvArgToCompletionRoutine,
BOOI fResume
);
BOOL CancelWaitableTimer (HANDLE hTimer);
Слайд 26
Описание слайда:
Запуск таймера ожидания
Параметры pDuеТimе и lPeriod функции SetWaitableTimer () задают соответственно, время когда таймер должен сработать в первый раз, и интервал, с которым должны происходить дальнейшие срабатывания.
Если таймер должен сработать только 1 раз, то достаточно передать 0 в параметре lPeriod.
При необходимости Вы можете не выполнять синхронизацию с объектом таймера с помощью Wait-функций, а сразу вызвать некоторую функцию. Адрес этой функции и аргументы для ее вызова указываются в параметрах pfnCompletionRoutine и pvArgToCompletionRoutine.
Параметр fResume полезен на компьютерах с поддержкой режима сна. Если этот параметр был установлен в состояние TRUE, то когда компьютер выйдет из режима сна, то пробудятся потоки, ожидавшие этот таймер.
Слайд 27
Описание слайда:
Отмена действия таймера
Для отмены действия таймера ожидания следует использовать функцию CancelWaitableTimer (), после ее применения таймер не сработает до следующего вызова SetWaitableTimer ().
Для переустановки текущих параметров таймера ожидания нет необходимости вызывать CancelWaitableTimer (), каждый вызов SetWaitableTimer () автоматически отменяет предыдущие настройки перед установкой новых.
Слайд 28
Описание слайда:
Создание объекта типа «семафор»
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
// атрибуты защиты
LONG lInitialCount, // начальное значение
// счетчика семафора
LONG lMaximumCount, // максимальное значение // счетчика
LPCTSTR lpName // имя объекта
);
Слайд 29
Описание слайда:
Открытие объекта типа «семафор»
HANDLE OpenSemaphore(
DWORD fdwAccess, // режим доступа
BOOL fInherit, // флаг наследования
LPCTSTR lpName // имя объекта
);
Слайд 30
Описание слайда:
Режимы доступа к семафору
SEMAPHORE_ALL_ACCESS – полный доступ (разрешены все возможные виды доступа)
SEMAPHORE_MODIFY_STATE – определяет возможность изменения значение счетчика семафора функцией ReleaseSemaphore ()
SYNCHRONIZE – позволяет использовать дескриптор объекта семафора в любой из функций ожидания
Слайд 31
Описание слайда:
Захват семафора
Для прохождения через семафор (захвата семафора) необходимо использовать функции WaitForSingleObject () или WaitForMultipleObject ().
Если Wait-функция определяет, что счетчик семафора равен «0» (семафор занят), то система переводит вызывающий поток в состояние ожидания. Когда другой поток увеличит значение этого счетчика, система вспомнит о ждущем потоке и снова начнет выделять ему процессорное время (а он, захватив ресурс, уменьшит значение счетчика на 1).
Слайд 32
Описание слайда:
Освобождение семафора
Для увеличения значения счетчика семафора приложение должно использовать функцию ReleaseSemaphore ().
Функция ReleaseSemaphore () увеличивает значение счетчика семафора, дескриптор которого передается ей через параметр hSemaphore, на значение, указанное в параметре lReleaseCount.
Предыдущее значение счетчика, которое было до использования функции ReleaseSemaphore (), записывается в переменную типа LONG. Адрес этой переменной передается функции через параметр lplPreviousCount.
Слайд 33
Описание слайда:
Функция ReleaseSemaphore
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // дескриптор семафора LONG lReleaseCount, // значение инкремента LPLONG lplPreviousCount // адрес переменной для // записи предыдущего // значения семафора
);
Слайд 34
Описание слайда:
Определение текущего состояния семафора
Заметим, что в операционной системе Windows не предусмотрено средств, с помощью которых можно было бы определить текущее значение семафора, не изменяя его.
Нельзя передавать 0 параметр инкремента в функцию ReleaseSemaphore ().
Можно только узнать открыт или закрыт семафор, для этого следует воспользоваться Wait-функцией с нулевым тайм-аутом ожидания.
Слайд 35
Описание слайда:
Создание и открытие мьютекса
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
// атрибуты защиты
BOOL bInitialOwner, // начальное состояние
LPCTSTR lpName // имя объекта
);
HANDLE OpenMutex(
DWORD fdwAccess, // режим доступа
BOOL fInherit, // флаг наследования
LPCTSTR lpName // имя объекта
);
Слайд 36
Описание слайда:
Режимы доступа к мьютексу
MUTEX _ALL_ACCESS – полный доступ (разрешены все возможные виды доступа)
MUTEX _MODIFY_STATE – определяет возможность изменения значение счетчика мьютекса функцией ReleaseMutex ()
SYNCHRONIZE – позволяет использовать дескриптор объекта мьютекса в любой из функций ожидания
Слайд 37
Описание слайда:
Управление мьютексом
Для захвата мьютекса необходимо использовать одну из Wait-функций.
Для освобождения мьютекса используется функция ReleaseMutex ().
BOOL ReleaseMutex (HANDLE hMutex);
Слайд 38
Описание слайда:
Межпроцессное взаимодействие
Критические секции в Win32 API
Слайд 39
Описание слайда:
Критические секции
В составе API ОС Windows имеются специальные и эффективные функции для организации входа в критическую секцию (Critical Section) и выхода из нее потоков одного процесса в режиме пользователя.
Критическая секция позволяет добиться решения задачи взаимного исключения – в случае невозможности входа в критический участок поток переходит в состояние ожидания. Впоследствии, когда такая возможность появится, поток будет «разбужен» и сможет сделать попытку входа в критическую секцию.
Слайд 40
Описание слайда:
Функции Win32 API для работы с критическими секциями
Для работы с критическими секциями используют функции:
InitializeCriticalSection – создание КС;
DeleteCriticalSection – освобождение ресурсов КС;
EnterCriticalSection – вход в КС;
LeaveCriticalSection – освобождение КС;
TryEnterCriticalSection – проверка КС.
Эти функции имеют в качестве параметра предварительно проинициализированную структуру типа CRITICAL_SECTION.
Слайд 41
Описание слайда:
Структура типа CRITICAL_SECTION
typedef struct _RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // исп. ОС
LONG LockCount; // счетчик блокировок
LONG RecursionCount; // счетчик повторного захвата
HANDLE OwningThread; // ID потока,
// владеющего секцией
HANDLE LockSemaphore; // дескриптор события
ULONG_PTR SpinCount; // кол-во холостых циклов
// перед вызовом ядра
}
RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
Слайд 42
Описание слайда:
Основные поля структуры CRITICAL_SECTION
Поле LockCount увеличивается на единицу при каждом вызове EnterCriticalSection() и уменьшается при каждом вызове LeaveCriticalSection().
Поле OwningThread содержит ID потока-владельца или «0» для незанятых секций.
Поле RecursionCount хранит количество повторных входов в критическую секцию одним и тем же потоком-владельцем. Для освобождения секции необходимо вызывать функцию LeaveCriticalSection () столько же раз, сколько раз была вызвана функция EnterCriticalSection ().
Поле LockSemaphore содержит дескриптор объекта синхронизации типа «событие», функционирующий в режиме автосброса, и реализующий конкурентный доступ к секции со стороны нескольких потоков.
Слайд 43
Описание слайда:
Вход в свободную секцию
Если при попытке входа в критическую секцию, LockCount уже равен 0, т.е. секция свободна:
поле LockCount увеличивается на 1;
в поле OwningThread записывает ID текущего потока, который может быть получен с помощью функции GetCurrentThreadId ().
текущий поток продолжает выполнение – функция EnterCriticalSection() немедленно возвращает управление.
Слайд 44
Описание слайда:
Вход в занятую секцию
При попытке входа в занятую критическую секцию, (LockCount > 0) проверяется поле OwningThread:
если OwningThread совпадает с ID текущего потока, то имеет место рекурсивный захват, и RecursionCount просто увеличивается на единицу, а EnterCriticalSection() возвращает управление немедленно.
иначе текущий поток проверяет поле LockSemaphore. Если поле LockSemaphore равно 0, то поток создает событие LockSemaphore в сброшенном состоянии. Далее поток вызывает WaitForSingleObject (LockSemaphore) и ожидает пока поток, захвативший критическую секцию, не вызовет LeaveCriticalSection() количество раз равное значению поля RecursionCount.
Слайд 45
Описание слайда:
Освобождение секции
Поток-владелец при вызове LeaveCriticalSection() уменьшает поле RecursionCount на единицу и проверяет его.
Если значение этого поля стало равным 0, а LockCount > 0, то это значит, что есть как минимум один поток, ожидающий готовности события LockSemaphore.
В этом случае поток-владелец вызывает SetEvent (LockSemaphore), что дает возможность пробудиться первому по очереди из ожидающих потоков и выполнить вход в уже свободную критическую секцию.
Слайд 46
Описание слайда:
Иллюстрация использования критической секции
Слайд 47
Описание слайда:
Критические секции в многопроцессорных системах
Поле SpinCount используется только многопроцессорными системами.
В однопроцессорных системах, если критическая секция занята другим потоком, можно только переключить управление на него и подождать наступления события.
В многопроцессорных системах есть альтернатива: прогнать некоторое количество раз холостой цикл, проверяя каждый раз, не освободилась ли наша критическая секция. Если за SpinCount раз это не получилось, то выполняется переход к ожиданию. Это гораздо эффективнее, чем переключение на планировщик ядра и обратно.
Слайд 48
Описание слайда:
Пример использования критической секции
CRITICAL_SECTION cs;
DWORD WINAPI SecondThread()
{
InitializeCriticalSection(&cs); EnterCriticalSection(&cs);
//…критический участок кода
LeaveCriticalSection(&cs);
}
Слайд 49
Описание слайда:
Использование нескольких критических секций
В том случае, когда процесс работает с двумя ресурсами, доступ к которым должен выполняться последовательно, он может создать несколько критических секций.
Однако в этом случае все потоки должны использовать одинаковую последовательность входа в эти критические секции и выхода из них, иначе возможны взаимные блокировки потоков.
Слайд 50
Описание слайда:
Опасный код
Поток 1.
EnterCriticalSection(&cs1);
EnterCriticalSection(&cs2);
//…критический участок кода
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
Слайд 51
Описание слайда:
Правила хорошего тона
Критические секции должны быть короткими.
Критических секций не должно быть много.
Критическая секция не должна быть одна на весь код.
Слайд 52
Описание слайда:
Реализация критических секций
Функции EnterCriticalSection () и LeaveCriticalSection () реализованы на основе атомарных Interlocked-функций, поэтому они более производительны, чем мьютексы!!!
Слайд 53
Описание слайда:
Сравнение инструментов синхронизации Windows 2000+
Слайд 54
Описание слайда:
Межпроцессное взаимодействие
Атомарные операции и lockless программирование
Слайд 55
Описание слайда:
Lockless программирование
Lockless программирование – разработка неблокирующих многопоточных приложений.
Отказ от использования блокирующих примитивов типа мьютексов и даже критических секций для доступа к разделяемым данным.
Достоинство – повышенная производительность многопоточных приложений на многоядерных процессорах.
Слайд 56
Описание слайда:
Атомарные операции как lockless-инструмент
Простейшим способом lockless-программирования является активное использованием атомарных операций при конкурентном доступе нескольких потоков к общим переменным.
В достаточно частых случаях необходимо обеспечить конкурентный доступ к какой-либо целочисленной переменной, являющейся счетчиком. Тогда бывает достаточно просто обеспечить атомарность выполнения операций увеличения, уменьшения или изменения значения переменной.
Слайд 57
Описание слайда:
Виды атомарных операций
Все инструкции вида Операция Регистр-Регистр можно считать атомарными так как регистры за пределами вычислительного процессорного ядра не видны.
Загрузка данных из памяти по выровненному адресу в регистр общего назначения.
Сохранение данных из регистра общего назначения в память по выровненному адресу.
Специальные операции для атомарной работы (например, cmpxchg).
Многие команды вида Чтение-Модификация-Запись могут быть сделаны искусственно атомарными с помощью операции блокировки шины (префикс lock).
Слайд 58
Описание слайда:
Реализация атомарных операций в Windows 2000+
Для увеличения значения целочисленных переменных –InterlockedIncrement, InterlockedIncrement64.
Для уменьшения значения целочисленных переменных – InterlockedDecrement, InterlockedDecrement64.
Для изменения значений целочисленных переменных – InterlockedExchange, InterlockedExchange64, InterlockedExchangeAdd, InterlockedExchangePointer.
Для изменения значений целочисленных переменных со сравнением – InterlockedCompareExchange, InterlockedCompareExchangePointer.
Слайд 59
Описание слайда:
Пример кода с использованием атомарной операции
static DWORD array [100];
…
for (int i = 0; i < 100; i++)
InterlockedIncrement(array+i);
Слайд 60
Описание слайда:
Эффект переупорядочивания
Поток 1:
result = 7;
flag = TRUE;
Слайд 61
Описание слайда:
Переупорядочивание и модель памяти
Чтение или запись не всегда будет происходить в том порядке, который указан в вашем коде.
Переупорядочивать операции могут компилятор, исполняемая среда и процессор.
Говоря о переупорядочивании, мы приходим к термину модели памяти (memory consistency model или просто memory model).
Слайд 62
Описание слайда:
«Сильная» и «слабая» модели памяти
Модель памяти, в которой нет переупорядочиваний чтения и записи будет считаться сильной (strong), а модель памяти, где возможны любые переупорядочивания чтения и записи принято считать слабой (weak).
При этом слабые модели обеспечивают наибольшую производительность за счет возможных оптимизаций, но и порождают большое количество проблем при программировании.
Слайд 63
Описание слайда:
Сводная таблица для некоторых архитектур
Слайд 64
Описание слайда:
Барьеры памяти и оптимизации
Для борьбы с переупорядочиванием применяются так называемые барьеры памяти (memory barrier или memory fence).
В случае барьера для компилятора применяют еще и термин барьер оптимизации (optimization barrier).
Барьеры могут быть как явными, так и не явными (с точки зрения программиста).
Кроме того барьеры могут быть полными, двухсторонними и односторонними.
Слайд 65
Описание слайда:
Полные барьеры
Полный барьер (full fence) предотвращает любые переупорядочивания операций чтения или записи через него.
Можно говорить о том, что все операции чтения и записи до полного барьера будут завершены, по отношению к операциям, расположенным после барьера.
Данный барьер предлагает использование инструкции mfence для x86/x64 архитектуры.
Слайд 66
Описание слайда:
Двухсторонние барьеры
Двусторонние барьеры (Store fence и Load fence) предотвращают переупорядочивание лишь одного вида операций.
Барьер записи (store fence) не позволяет переупорядочивать через себя операции записи.
Барьер чтения (load fence) не позволяет переупорядочивать через себя операции чтения соответственно.
На x86/x64 архитектурах данные барьеры реализованы в инструкциях lfence и sfence.
Слайд 67
Описание слайда:
Односторонние барьеры
Односторонние барьеры обычно реализуют одну из двух семантик – write release (store release) или read acquire (load acquire).
Write release семантика предотвращает любое переупорядочивание чтения и записи через барьер до барьера (запрет ↓), но не предотвращает переупорядочивания после него.
Read acquire семантика предотвращает переупорядочивание чтения и записи через барьер после барьера (запрет ↑), но не предотвращает переупорядочивание до него.
Слайд 68
Описание слайда:
Управление переупорядочиванием в MS VC
MS VC для предотвращения переупорядочивания инструкций со стороны компилятора предлагает использовать _ReadBarrier(), _WriteBarrier(), _ReadWriteBarrier().
Эти функции не предотвращают переупорядочивание на уровне процессора, они являются лишь инструментом более тонкого контроля над оптимизациями со стороны компилятора.
Для предотвращения переупорядочивания инструкций со стороны процессора предлагает макрос MemoryBarrier(), который является полным барьером и предотвращает переупорядочивание как чтения, так и записи. Исходный код этого макроса можно увидеть в MSDN.
Слайд 69
Описание слайда:
Неявные барьеры в MS VC 2005
В случае MS VC 2005+ ключевое слово volatile приобретает значение, выходящее за рамки С++ стандарта.
Запись в volatile переменную всегда реализует семантику одностороннего барьера write release.
Чтение из volatile переменной всегда реализует семантику одностороннего барьера read acquire.
Причем оба эти неявных барьера реализуются как для компилятора, так и для процессора.
http://msdn.microsoft.com/en-us/library/windows/desktop/ee418650%28v=vs.85%29.aspx
Слайд 70
Описание слайда:
Пример решения проблемы переупорядочивания
Поток 1:
result = 7; // store
_WriteBarrier();
flag = TRUE; // store
Слайд 71
Описание слайда:
Производительность lockless приложений
MemoryBarrier () занимает 20-90 циклов.
InterlockedIncrement () занимает 36-90 циклов.
Вхождение или освобождение критической секции может занимать 40-100 циклов.
Захват или освобождение мьютекса может занимать 750-2500 циклов.