Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

u_course

.pdf
Скачиваний:
39
Добавлен:
04.06.2015
Размер:
1.87 Mб
Скачать

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

101

1. Наследование. Наследование применимо, только когда процессы связаны «родственными» отношениями (родительский–дочерний). Например, родительскому процессу доступны один или несколько описателей объектов ядра, и он решает, породив дочерний процесс, передать ему по наследству доступ к своим объектам ядра.

При вызове функции создания объекта в качестве одного из ее параметров передается структура LPSECURITY_ATTRIBUTES, которая определяет среди прочего и наследуемость объекта. Если при создании дочернего процесса в параметрах функции CreateProcess() флаг наследования объектов blnheritHandles выставлен в значение TRUE, то этот дочерний процесс может наследовать объекты. ОС создает дочерний процесс, но не дает ему немедленно начать свою работу. Сформировав в нем, как обычно, новую (пустую) таблицу описателей, ОС считывает таблицу родительского процесса и копирует ее записи в таблицу дочернего — причем в те же позиции. Последний факт чрезвычайно важен, так как означает, что описатели будут идентичны в обоих процессах (родительском и дочернем).

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

Сразу после возврата управления функцией CreateProcess() родительский процесс может закрыть свой описатель объекта, и это никак не отразится на способности дочернего процесса манипулировать с этим объектом. Чтобы уничтожить какой-то объект ядра, его описатель должны закрыть (вызовом CloseHandle()) оба процесса – родительский и дочерний.

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

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

В качестве имени объекта передается строка, завершающаяся нулевым символом, длиной не более 260 символов и не содержащая символов «/». Все именованные объекты разделяют общее для системы пространство имен, поэтому нельзя создать два разнотипных объекта с одинаковым именем.

Пусть два разных процесса вызовут функцию вида CreateObject(), указав для объектов одного типа одинаковые имена. Для того, кто это сделал первым (например, процесс А) будет отработана обычная процедура создания объекта. Для второго процесса, соответственно Б, система проверит и убедится, что объект ядра с таким же именем и такого же типа существует. При

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

102

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

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

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

Отметим, что если тип объекта, создаваемого процессом Б, не совпадает с уже созданным объектом того же имени, то система сгенерирует ошибку и второй объект не будет создан.

Вместо Create-функций возможно использование одной из Open- функций. Все Open-функции имеют общий прототип:

BOOL OpenHandle(DWORD dwDesiredAccess, BOOL blnheritHandle,

LPCTSTR IpszName);

Последний параметр, IpszName, определяет имя объекта ядра. Он всегда должен содержать адрес строки с нулевым символом в конце (передавать NULL нельзя). Функции OpenHandle() просматривают единое пространство имен объектов ядра, пытаясь найти совпадение. Если объекта ядра с указанным именем нет или он другого типа, Open-функции возвращают NULL. Но если объект ядра с заданным именем существует и если его тип идентичен тому, что указан, система проверяет, разрешен ли к данному объекту доступ запрошенного (через параметр dwDesiredAccess) вида. Если доступ разрешен, таблица описателей в вызывающем процессе обновляется, и счетчик числа пользователей объекта возрастает на единицу. Если присвоить параметру blnheritHandle значение TRUE, то будет получен наследуемый описатель.

Главное отличие между вызовом Create- и Ореn-функций в том, что при отсутствии указанного объекта Create-функция создает его, а Ореn-функция просто сообщает об ошибке.

3. Дублирование описателей объектов. Последний механизм совмест-

ного использования объектов ядра несколькими процессами требует функции

DuplicateHandle():

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

103

BOOL DuplicateHandle (HANDLE hSourceProcessHandle, HANDLE hSourceHandle,

HANDLE hTargetProcessHandle, LPHANDLE IpTargetHandle, DWORD dwDesiredAccess,

BOOL PInheritHandle, DWORD dwOptions);

Эта функция берет запись в таблице описателей одного процесса и создает ее копию в таблице другого процесса, не обязательно вызвавшего эту функцию. Опишем параметры функции.

Параметр HANDLE hSourceProcessHandle – описатель объекта ядра типа «про- цесс»-владельца запрашиваемого объекта, специфичный для вызывающего процесса;

Параметр HANDLE hSourceHandle – описатель запрашиваемого объекта ядра любого типа, специфичного для процесса, на который указывает па-

раметр hSourceProcessHandle;

Параметр HANDLE hTargetProcessHandle – описатель объекта ядра типа «процесс», в таблицу которого копируется запись (процесс-приемник), специфичный для вызывающего процесса;

Параметр LPHANDLE IpTargetHandle – адрес переменной типа HANDLE, в которой возвращается индекс записи с копией описателя из процессаисточника, специфичного для процесса, идентифицируемого пара-

метром hTargetProcessHandle.

Параметр DWORD dwDesiredAccess – задает маску доступа, устанавливаемую для данного описателя объекта ядра в процессе-приемнике;

Параметр BOOL PInheritHandle – задает флаг наследования, устанавливаемый для данного описателя объекта ядра в процессе-приемнике;

Параметр DWORD dwOptions – может быть 0 или любой комбинацией двух

флагов – DUPLICATE_SAME_ACCESS и DUPLICATE_CLOSE_SOURCE.

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

DuplicateHandle() игнорировать параметр dwDesiredAccess. Флаг

DUPLICATE_CLOSE_SOURCE приводит к закрытию описателя в процессеисточнике. Он позволяет процессам обмениваться объектом ядра как эстафетной палочкой. При этом счетчик объекта не меняется.

BOOL CloseHandle(HANDLE hObj);

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

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

104

ПРОЦЕССЫ

Общие сведения

Процесс обычно определяют как экземпляр выполняемой программы.

ВWin32 процессу отводится 4 Гб адресного пространства. Win32процессы, в отличие от своих аналогов в MS-DOS и 16-разрядной Windows, инертны. Иными словами, Win32-npoцecc ничего не исполняет – просто владеет 4 Гб памяти, содержащей код и данные ЕХЕ-файла приложения. В это же пространство загружаются код и данные DLL-библиотек, если того требует ЕХЕ-файл. Кроме адресного пространства, процессу принадлежат такие ресурсы, как файлы, динамически выделяемые области памяти и потоки. Ресурсы, создаваемые при жизни процесса, обязательно уничтожаются при его завершении.

Чтобы процесс что-нибудь выполнил, в нем нужно создать поток. Каждый процесс должен содержать как минимум один поток. Именно потоки отвечают за исполнение кода, содержащегося в адресном пространстве процесса. В принципе, один процесс может владеть несколькими потоками, и тогда они «одновременно» исполняют код в адресном пространстве процесса. Для этого каждый поток должен располагать собственным набором регистров процессора и собственным стеком.

Чтобы все потоки работали, операционная система отводит каждому из них определенное процессорное время. Выделяя потокам отрезки времени (называемые квантами) по принципу карусели, она создает тем самым иллюзию одновременного выполнения потоков.

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

Создание процесса приложением

Процесс создается при вызове приложением функции CreateProcess():

BOOL CreateProcess (

LPCTSTR IpszApplicationName,

LPCTSTR IpszCommandLine,

LPSECURITY_ATTRIBUTES IpsaProcess,

LPSECURITY_ATTRIBUTES IpsaThread,

BOOL flnheritHandles,

DWORD fdwCreate,

LPVOID IpvEnvironment,

LPTSTR IpszCurDir,

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

105

LPSTARTUPINFO IpsiStartlnfo,

LPPROCESS_INFORMATION IppiProcInfo);

Когда поток в приложении вызывает CreateProcess(), система создает объект ядра «процесс» с начальным значением счетчика числа его пользователей, равным 1. Этот объект – не сам процесс, а компактная структура данных, через которую операционная система управляет процессом. Таким образом, объект ядра “процесс” следует рассматривать как структуру данных со статистической информацией о процессе.

Затем система создает для нового процесса виртуальное адресное пространство размером 4 Гб и загружает в него код и данные, как для исполняемого файла, так и всех DLL (если таковые требуются).

Далее система формирует объект ядра “поток” (со счетчиком, равным 1) для первичного потока нового процесса. Как и в первом случае, объект ядра “поток” – это компактная структура данных, через которую система управляет потоком.

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

Если системе удастся создать новый процесс и его первичный поток,

CreateProcess() вернет TRUE.

Рассмотрим параметры функции CreateProcess().

Параметр IpszApplicafionName определяет имя исполняемого файла, которым будет пользоваться новый процесс.

Параметр IpszCommandLine определяет полную командную строку, передаваемую системой создаваемому процессу.

Если параметр IpszApplicationName равен NULL, то CreateProcess() анали-

зирует строку по адресу IpszCommandLine, извлекая первый компонент, и полагая, что это имя исполняемого файла, который Вы хотите запустить. Если в имени файла не указано расширение, она считает его ЕХЕ. Далее функция приступает к поиску заданного файла и делает это в следующем порядке: каталог, содержащий ЕХЕ-файл вызывающего процесса – текущий каталог вызывающего процесса – системный каталог Windows – основной каталог Windows – каталоги, перечисленные в переменной окружения PATH.

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

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

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

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

106

командную строку процесса и передает WinMain() адрес первого (за именем исполняемого файла) аргумента как IpszCmdLine.

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

Но даже при указанном в IpszApplicationName имени файла CreateProcess()

все равно передает новому процессу содержимое параметра IpszCommandLine как командную строку.

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

Параметр IpsaProcess определяет атрибуты защиты для объекта «процесс». Параметр IpsaThread определяет атрибуты защиты для объекта «поток», который создается для процесса первым (первичный поток).

Параметр fInheritHandles определяет, будут ли переданы дочернему процессу все наследуемые описатели (TRUE).

Если параметры IpsaProcess и IpsaThread содержат NULL, то система закрепит за соответствующими объектами (процесс и поток) дескрипторы защиты, принятые по умолчанию. В качестве альтернативы можно объявить и инициализировать две структуры SECURITY_ATTRIBUTES, тем самым, создавая объекты «процесс» и «поток» с собственными атрибутами защиты.

Структуры SECURITY_ATTRIBUTES для параметров IpsaProcess и IpsaThread

используются и при объявлении соответствующих объектов наследуемых любым дочерним процессом. При этом параметр fInheritHandles должен быть равен TRUE, тогда дочернему процессу будут переданы все наследуемые описатели.

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

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

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

107

Флаг DEBUG_ONLY_THIS_PROCESS аналогичен флагу DEBUG_PROCESS

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

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

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

Флаг CREATE_NEW_CONSOLE приводит к созданию нового консольного окна для нового процесса. Одновременная установка флагов

CREATE_NEW_CONSOLE и DETACHED_PROCESS недопустима.

Флаг CREATE_NO_WINDOW не дает создавать никаких консольных окон для данного приложения и тем самым позволяет исполнять консольные программы без пользовательского интерфейса.

Флаг CREATE_NEW_PROCESS_GROUP служит для модификации списка процессов, уведомляемых о нажатии клавиш Ctrl+C и Ctrl+Break. Если в системе одновременно исполняется несколько процессов консольного типа, то при нажатии одной из упомянутых комбинаций клавиш система уведомляет об этом только процессы, включенные в группу. При указании этого флага при создании нового процесса консольного типа, предыдущий список процессов, входящих в группу обнуляется, и создается новая группа. Таким образом, на нажатие клавиш Ctrl+C и Ctrl+Break реагировать будут лишь этот процесс и процессы, им порожденные.

Флаг CREATE_DEFAULT_ERROR_MODE сообщает системе, что новый процесс не должен наследовать режимы обработки ошибок, установленные в родительском процессе.

Флаг CREATE_SEPARATE_WOW_VDM полезен только при запуске 16разрядного Windows-приложения в Windows NT. Если он установлен, система создает отдельную виртуальную DOS-машину (Virtual DOS-machine,

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

108

VDM) и запускает 16-разрядное Windows-приложение именно в ней. По умолчанию все 16-разрядные Windows-приложения выполняются в одной, общей VDM. Выполнение приложения в отдельной VDM дает большое преимущество: «рухнув», приложение уничтожит лишь эту VDM, а программы, выполняемые в других VDM, продолжат нормальную работу. Кроме того, 16разрядные Windows-приложения, выполняемые в раздельных VDM, имеют и раздельные очереди ввода. Это значит, что, если одно приложение вдруг «зависнет», приложения в других VDM продолжат прием ввода. Единственный недостаток работы с несколькими VDM в том, что каждая из них требует значительных объемов физической памяти.

Флаг CREATE_SHARED_WOW_VDM полезен только при запуске 16разрядного Windows-приложения в Windows NT. По умолчанию все 16разрядные Windows-приложения выполняются в одной VDM, если только не указан флаг CREATE_SEPARATE_WOW_VDM. Однако стандартное поведение Windows NT можно изменить, если присвоить значение “yes” параметру

DefaultSeparateVDM в разделе HKEY_LOCAL_MACHINE\System\CurrentControl-

Set\Control\WOW (после модификации этого параметра Windows NT нужно перезагрузить). Установив значение “yes”, но указав флаг CREATE_SHARED_WOW_VDM, можно снова заставить Windows NT выполнять все 16-разрядные Windows-приложения в одной VDM.

Флаг CREATE_UNICODE_ENVIRONMENT сообщает системе, что блок переменных окружения дочернего процесса должен содержать Unicodeсимволы. По умолчанию блок формируется на основе ANSI-символов.

При создании процесса можно задать и класс его приоритета. Однако это необязательно и даже, как правило, не рекомендуется; система присваивает новому процессу класс приоритета по умолчанию.

Существуют следующие классы приоритета:

Idle (простаивающий)

Normal (нормальный)

High (высокий)

Realtime (реального време-

ни)

IDLE_PRIORITY_CLASS NORMAL_PRIORITY_CLASS HIGH_PRIORITY_CLASS REALTIME PRIORITY CLASS

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

цию GetEnvironmentStrings():

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

109

LPVOID GetEnvironmentStrings(VOID)

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

CreateProcess(), если передать ей NULL вместо IpvEnvironment).

Параметр IpszCurDir позволяет родительскому процессу установить текущие диск и каталог для дочернего процесса. Если его значение – NULL, рабочий каталог нового процесса будет расположен там же, где и у приложения, его породившего. А если он отличен от NULL, то должен указывать на строку (с нулевым символом в конце), содержащую нужный диск и каталог. В путь надо включать и букву диска.

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

Большинство приложений порождают процессы с атрибутами по умолчанию. Но и в этом случае надо как минимум инициализировать все элементы структуры STARTUPINFO нулевыми значениями, а в элемент cb – занести размер этой структуры, что делается с помощью следующей строки:

STARTUPINFO si = sizeof (si);

Тогда при создании процесса передается адрес структуры STARTUPINFO:

CreateProcess(..., &si, ...);

Параметр IppiProcInfo указывает на структуру PROCESS_INFORMATION, которую следует предварительно создать. Структура имеет следующий вид:

typedef struct _PROCESS_INFORMATION{ HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadld;

} PROCESS_INFORMATION;

Ее элементы инициализируются самой функцией CreateProcess(). Создание нового процесса влечет за собой и создание объектов ядра

«процесс» и «поток». В момент создания система присваивает счетчику каждого объекта начальное значение «1».

Далее, перед самым возвратом управления, CreateProcess() открывает объекты «процесс» и «поток», заносит их описатели, специфичные для данного процесса, в элементы hProcess и hThread структуры

PROCESS_INFORMATION. Когда CreateProcess() открывает эти объекты, счетчики

каждого из них увеличиваются до двух.

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

110

Это означает, что, перед тем как система сможет высвободить из памяти объект «процесс», процесс должен быть завершен (счетчик уменьшается до 1), а родительский процесс – вызвать функцию CloseHandle() (и тем самым обнулить счетчик). То же самое относится и к объекту «поток»: поток должен быть завершен, а родительский процесс должен закрыть описатель объекта «поток».

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

Завершая свою работу, CreateProcess() заносит значения идентификато-

ров в элементы dwProcessId и dwThreadld структуры PROCESS_INFORMATION. Ис-

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

Система способна повторно использовать идентификаторы процессов и потоков. Например, при создании процесса система формирует объект «процесс», присваивая ему идентификатор со значением, допустим, 0x22222222. Создавая новый объект «процесс», система уже не присвоит ему данный идентификатор. Но после выгрузки из памяти первого объекта следующему создаваемому объекту «процесс» может быть присвоен тот же идентификатор 0x22222222. Это может привести к ошибкам в работе приложений, поскольку в некоторых случаях система может закрыть объект ядра, не смотря на то, что им кто-то пользуется. В этом случае описатель объекта ядра будет указывать на несуществующий процесс или другой процесс, имеющий тот же идентификатор.

Зная, что значение счетчика выше нуля, можно свободно оперировать идентификатором процесса. А когда необходимость в нем отпадет, следует вызвать CloseHandle(), чтобы уменьшить счетчик объекта «процесс». Затем стоит удостовериться, что этот идентификатор больше нигде не используется.

Завершение процесса

Процесс можно завершить тремя способами:

1.Один из потоков процесса вызывает функцию ExitProcess() (самый распространенный способ).

2.Поток другого процесса вызывает функцию TerminateProcess() (нежелательный способ).

3.Все потоки процесса завершаются самостоятельно.

Рассмотрим функции завершения подробнее.

VOID ExitProcess(UINT fuExitCode)

Функция завершает процесс и заносит в параметр fuExitCode код завершения процесса. Эта функция должна быть вызвана один из потоков процес-

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]