Основы организации операционных систем Microsoft Windows

Межпроцессный обмен

Показывать лекцию целиком

Введение

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

Известно также, что в основе межпроцессного (Inter Process Communications, IPC) обмена обычно находится разделяемый ресурс (например, канал или сегмент разделяемой памяти), и, следовательно, ОС должна предоставить средства для генерации, именования, установки режима доступа и атрибутов защиты таких ресурсов. Обычно такой ресурс может быть доступен всем процессам, которые знают его имя и имеют необходимые привилегии.

Кроме того, организация связи между процессами всегда предполагает установления таких ее характеристик, как:

  • направление связи. Связь бывает однонаправленная (симплексная) и двунаправленная (полудуплексная для поочередной передачи информации и дуплексная с возможностью одновременной передачи данных в разных направлениях);
  • тип адресации. В случае прямой адресации информация посылается непосредственно получателю, например, процессу P-Send (P, message). В случае непрямой или косвенной адресации информация помещается в некоторый промежуточный объект, например, в почтовый ящик;
  • используемая модель передачи данных - потоковая или модель сообщений (см. ниже);
  • объем передаваемой информации и сведения о том, обладает ли канал буфером необходимого размера;
  • синхронность обмена данными. Если отправитель сообщения блокируется до получения этого сообщения адресатом, то обмен считается синхронным, в противном случае - асинхронным.
  • Кроме перечисленных у каждой связи есть еще ряд особенностей.

    Способы межпроцессного обмена.

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

    (рис 7.1) Основные способы межпроцессного обмена

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

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

    Ограниченный объем курса не позволяет рассмотреть другие механизмы межпроцессного обмена, реализованные в ОС Windows, например, сокеты, Clipboard или удаленный вызов процедуры (RPC). Исчерпывающая справочная информация на эту тему имеется в MSDN.

    Понятие о разделяемом ресурсе

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

    В качестве примера рассмотрим общение через разделяемую память (рис 7.2).

    (рис 7.2) Адресные пространства процессов, взаимодействующих через сегмент разделяемой памяти

    В ОС Windows сегмент разделяемой памяти создается с помощью Win32-функции ). В случае успешного выполнения данной функции создается ресурс - фрагмент памяти, доступный по имени (параметр lpname ), который базируется на соответствующем объекте ядра - "объекте-файле, отображаемом в память" с присущими любому объекту атрибутами. Процессу-создателю возвращается описатель (handle) ресурса. Другие процессы, желающие иметь доступ к ресурсу, также должны получить его описатель. В данном случае это можно сделать с помощью функции OpenFileMapping, указав имя ресурса в качестве одного из параметров.

    (рис 7.3) Создание сегмента разделяемой памяти базируется на разделяемом ресурсе, которому соответствует объект ядра

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

    (рис 7.4) Обмен через каналы связи осуществляется через буфер в адресном пространстве ядра системы

    Каналы связи

    Основной принцип работы канала состоит в буферизации вывода одного процесса и обеспечении возможности чтения содержимого программного канала другим процессом. При этом часто интерфейс программного канала совпадает с интерфейсом обычного файла и реализуется обычными файловыми операциями read и write. Для обмена могут использоваться потоковая модель и модель обмена сообщениями.

    Механизм генерации канала предполагает получение процессом-создателем (процессом-сервером) двух описателей (handles) для пользования этим каналом. Один из описателей применяется для чтения из канала, другой - для записи в канал.

    Один из вариантов использования канала - это его использование процессом для взаимодействия с самим собой. Рассмотрим следующее изображение системы, состоящей из процесса и ядра, после создания канала (рис 7.5):

    (рис 7.5) Общение процесса с самим собой через канал связи

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

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

    (рис 7.6) Общение процессов через канал связи

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

    (рис 7.7) Передача информации от клиента серверу через канал связи

    Организация каналов в ОC Windows

    Анонимные каналы

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

    Анонимные каналы создаются процессом сервером при помощи функции CreatePipe:

    BOOL CreatePipe(
      PHANDLE hReadPipe,                       // описатель для чтения
      PHANDLE hWritePipe,                      // описатель для записи
      LPSECURITY_ATTRIBUTES lpPipeAttributes,  // атрибуты безопасности
      DWORD nSize                              // размер канала
    );

    Функция CreatePipe возвращает два описателя (дескриптора) для чтения и записи в канал. После создания канала необходимо передать клиентскому процессу эти дескрипторы (или один из них), что обычно делается с помощью механизма наследования.

    Для наследования описателя нужно, чтобы дочерний процесс создавался функцией CreateProcess с флагом наследования TRUE. Предварительно нужно создать наследуемые описатели. Это можно, например, сделать путем явной спецификации параметра bInheritHandle структуры SECURITY_ATTRIBUTES при создании канала.

    Другим способом является создание наследуемого дубликата имеющегося описателя при помощи функции DuplicateHandle и последующая передача его создаваемому процессу через командную строку или каким-либо иным образом.

    Получив нужный описатель, клиентский процесс, так же как и сервер, может далее взаимодействовать с каналом при помощи функций ReadFile и WriteFile. По окончании работы с каналом оба процесса должны закрыть описатели при помощи функции CloseHandle.

    Прогон программы общения процесса через анонимный канал с самим собой

    #include <windows.h>
    #include <stdio.h>
    
    int main()
    {
    	HANDLE hRead, hWrite;
    	char BufIn[100], *BufOut = "0123456789";
    	int BufSize = 100;
    	int BytesOut = 10, BytesIn = 5, i;
    	
    	if(!CreatePipe(hRead, hWrite, NULL, BufSize))  
    		printf("Create pipe failed.\n");
    	
    	WriteFile(hWrite, BufOut, BytesOut, BytesOut, NULL);
    	 printf("Write into pipe %d bytes : ", BytesOut);
            for(i=0; i<BytesOut;i++) printf("%c",BufOut[i]);
    	 printf("\n");
    	
    	ReadFile(hRead, BufIn, BytesIn, BytesIn, NULL);
    	 printf("Read from pipe %d bytes : ", BytesIn);
    	
    	 for(i=0; i<5;i++) printf("%c",BufIn[i]);
    	 
    	return 0;
    }

    В приведенной программе создается анонимный канал, в него записывается строка цифр, затем часть этой строки читается и выводится на экран.

    Прогон программы общения через анонимный канал клиента и сервера

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

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

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

    Каналы должны иметь уникальные в рамках сети имена в соответствии с правилами именования ресурсов в сетях Windows (Universal Naming Convention, UNC), например, \\ServerName\pipe\PipeName. Для общения внутри одного компьютера имя записывается в форме \\.\pipe\PipeName, где "." обозначает локальную машину. Слово "pipe" в составе имени фиксировано, а PipeName - имя, задаваемое пользователем. Эти имена, подобно именам открытых файлов, не являются именами объектов. Они относятся к пространству имен под управлением драйверов файловых систем именованных каналов ( \Winnt\System32\Drivers\Npfs.sys ), привязанному к специальному объекту устройству \Device\NamedPipe, на которое есть ссылка в каталоге глобальных имен объектов \??\Pipe (эти последние имена "видит" утилита WinObj).

    Имена созданных именованных каналов можно перечислить с помощью свободно распространяемой утилиты pipelist с сайта http://www.sysinternals.com. Поскольку имена каналов интегрированы в общую структуру имен объектов, приложения могут открывать именованные каналы с помощью функции CreateFile и взаимодействовать с ними через функции ReadFile и WriteFile.

    Использование именованных каналов

    Сервер создает именованный канал при помощи функции CreateNamedPipe (см. MSDN).

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

    Повторно вызывая CreateNamedPipe, можно создавать дополнительные экземпляры этого же канала.

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

    После того как соединение по именованному каналу установлено, клиент и сервер могут использовать его для чтения и записи данных через Win32-функции ReadFile и WriteFile.

    Прогон программы общения двух процессов через именованный канал

    В качестве упражнения рекомендуется осуществить прогон программы общения клиента и сервера через именованный канал.

    Сервер

    #include <stdio.h>
    #include <windows.h>
    
    void main()
    {
    	PROCESS_INFORMATION piProcInfo;
    	STARTUPINFO SI;
    	char * ClientName = "client.exe";
    	HANDLE hPipe;
    	LPTSTR PipeName = TEXT("\\\\.\\pipe\\MyPipe");
    	char Buff[255];
    	DWORD iNumBytesToRead = 255, i;
    	
    	ZeroMemory(SI, sizeof(STARTUPINFO));
    	SI.cb = sizeof(STARTUPINFO); 
    	ZeroMemory(piProcInfo, sizeof(piProcInfo));
    
    	hPipe = CreateNamedPipe( 
              PipeName,			   // имя канала
              PIPE_ACCESS_DUPLEX,       // чтение и запись из канала
              PIPE_TYPE_MESSAGE |       // передача сообщений по каналу
              PIPE_READMODE_MESSAGE |   // режим чтения сообщений 
              PIPE_WAIT,                // синхронная передача сообщений 
              PIPE_UNLIMITED_INSTANCES, // число экземпляров канала 
              4096,			   // размер выходного буфера
              4096,		          // размер входного буфера  
              NMPWAIT_USE_DEFAULT_WAIT, // тайм-аут клиента 
              NULL);                    // защита по умолчанию
    	
    	if (hPipe == INVALID_HANDLE_VALUE) 
        {
    	 printf("CreatePipe failed: error code %d\n", (int)GetLastError());
            return;
        }
    	
    	if((CreateProcess(NULL, ClientName, NULL, NULL, FALSE, 0, NULL, NULL, SI, piProcInfo))==0)
    	{
    	printf("create client process: error code %d\n", (int)GetLastError());
    	return;
    	}
    	
    	if((ConnectNamedPipe(hPipe, NULL))==0)
    	{
    		printf("client could not connect\n");
    		return;
    	}
    	
    	ReadFile(hPipe, Buff, iNumBytesToRead, iNumBytesToRead, NULL);
    	for(i=0; i<iNumBytesToRead; i++) printf("%c",Buff[i]);
     }

    Клиент

    #include <stdio.h>
    #include <windows.h>
    
    void main()
    {
    	HANDLE hPipe;
    	LPTSTR PipeName = TEXT("\\\\.\\pipe\\MyPipe");
    	DWORD NumBytesToWrite;
    	char Buff[] = "Message from Client";
    
    	hPipe = CreateFile( 
             PipeName,	      // имя канала
             GENERIC_READ |  // чтение и запись в канал
             GENERIC_WRITE, 
             0,              // нет разделяемых операций 
             NULL,           // защита по умолчанию
             OPEN_EXISTING,  // открытие существующего канала 
             0,              // атрибуты по умолчанию
             NULL);          // нет дополнительных атрибутов 
    
    	WriteFile(hPipe, Buff, strlen(Buff), NumBytesToWrite, NULL);
    	
    }

    В данном примере сервер создает канал, затем запускает процесс-клиент и ждет соединения. Далее он читает сообщение, посланное клиентом.

    Помимо перечисленных выше система представляет еще ряд полезных функций для работы с именованными каналами. Для копирования данных из именованного канала без удаления их из канала используется функция PeekNamedPipe. Функция TransactNamedPipe применяется для объединения операций чтения и записи в канал в одну операцию, которая называется транзакцией. Имеются информационные функции для определения состояния канала, например, GetNamedPipeHandleState или GetNamedPipeInfo. Полный перечень находится в MSDN.

    Заключение

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

    Вернуться к учебному плану