Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
348
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

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

Время от времени возникает необходимость в разделении объектов ядра между потоками, исполняемыми в разных процессах. Причин тому может быть несколько:

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

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

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

Но поскольку описатели объектов ядра имеют смысл только в конкретном процессе, разделение объектов ядра между несколькими процессами — задача весьма непростая. У Microsoft было несколько веских причин сделать описатели процессно-зависимыми", и самая главная — устойчивость операционной системы к сбоям. Если бы описатели объектов ядра были общесистемными, то один процесс мог бы запросто получить описатель объекта, используемого другим процессом, и устроить в нем (этом процессе) настоящий хаос. Другая причина — защита. Объекты ядра защищены, и процесс, прежде чсм оперировать с ними, должен запрашивать разрешение на доступ к ним.

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

Наследование описателя объекта

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

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

Чтобы создать наследуемый описатель, родительский процесс выделяет и инициализирует структуру SECURITY_ATTRIBUTES, а затем передает ее адрес требуемой Createфункции. Следующий код создаст объект-мьютекс и возвращает его описатель:

SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecuntyDescriptor = NULL;

sa.bInheritHandle =- TRUE; // делаем возвращаемый описатель наследуемым

HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);

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

WINDOWS 98:

Хотя Windows 98 не полностью поддерживает защиту, она все же поддерживает наследование и поэтому корректно обрабатывает элемент bInheritHandle.

А теперь перейдем к флагам, которые хранятся в таблице описателей, принадлежащей процессу В каждой ее записи присутствует битовый флаг, сообщающий, является данный описатель наследуемым или нет. Если Вы, создавая объект ядра, передадите в парамере типа PSECURITY_ATTRIBUTES значение NULL, то получите ненаследуемый описатель, и этот флаг будет нулевым. А если элемент bInheritHandle равен TRUE, флaгy

пpиcвaивaeтcя 1.

Допустим, какому-то процессу принадлежит таблица описателей, как в таблице 3-2.

Индекс

Указатель на блок

Маска доступа (DWORD с

Флаги (DWORD с

 

памяти объекта ядра

набором битовых флагов)

набором битовых

 

 

 

флагов)

1

0xF0000000

0x????????

0x00000000

 

 

 

 

2

0x00000000

(неприменим)

(неприменим)

 

 

 

 

3

0xF0000010

0х????????

0x00000001

 

 

 

 

Таблица 3-2. Таблица описателей с двумя действительными записями

Эта таблица свидетельствует, что данный процесс имеет доступ к двум объектам ядра: описатель 1 (ненаследуемый) и 3 (наследуемый)

Следующий этап — родительский процесс порождает дочерний. Это делается с помощью функции CreateProcess,

BOOL CreateProcess(

PCTSTR pszApplicationName,

PTSTR pszCommandLine,

PSECURITY_ATTRIBUTES psaProcess,

PSECURITY_ATTRIBUTES psaThread,

BOOL bInheritHandles,

DWORD fdwCreale,

PVOIO pvEnvironment,

PCTSTR pszCurDir,

PSTARTUPINFO psiStartInfo,

PPROCESS_INFORMATION ppiProcInfo);

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

как означает, что описатели будут идентичны в обоих процессах (родительском и дочернем).

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

В таблице 3-3 показано состояние таблицы описателей в дочернем процессе — перед самым началом его исполнения. Как видите, записи 1 и 2 не инициализированы, и поэтому данные описатели неприменимы в дочсрнсм процессе Однако индекс 3 действительно идентифицирует объект ядра по тому же (что и в родительском) адресу 0xF0000010. При этом маска доступа и флаги в родительском и дочернем процессах тоже идентичны. Так что, если дочерний процесс в свою очередь породит новый ("внука" по отношению к исходному родительскому), "внук" унаследует данный описатель объекта ядра с теми же значением, нравами доступа и флагами, а счетчик числа пользователей этого объекта ядра вновь увеличится на 1.

Индекс

Указатель на

Маска доступа (DWORD

Флаги (DWORD с набором

 

блок

с набором битовых

битовых флагов)

 

памяти объекта

флагов)

 

 

ядра

 

 

1

0x00000000

(неприменим)

(неприменим)

 

 

 

 

2

0x00000000

(неприменим)

(неприменим)

 

 

 

 

3

0xF0000010

0х????????

0x00000001

 

 

 

 

Таблица 3-3. Таблица описателей в дочернем процессе (после того как он унаследовал от родительского один наследуемый описатель)

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

ядра с наследуемыми описателями, то эти описатели будут уже недоступны дочернему процессу.

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

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

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

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

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

Изменение флагов описателя

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

вызовом SetHandleInformation

BOOL SetHandleInformation(

HANDLE hObject,

DWORD dwMask,

DWORD dwFlags);

Как видите, эта функция принимает три параметра. Первый (bObject) идентифицирует допустимый описатель. ВтороЙ (dwMask) сообщает функции, какой флаг (или флаги) Вы хотите изменить На сегодняшний день с каждым описателем связано два флага:

#define HANDLE FLAG_INHtRIT 0x00000001

#define HANDLE FLAG PROTECT FROM CLOSE 0x00000002

Чтобы изменить сразу все флаги объекта, нужно объединить их побитовой операцией OR.

И, наконец, третий параметр функции SetHandleInformation — dwFlags — указывает, в какое именно состояние следует перевести флаги. Например, чтобы установить флаг наследования для описателя объекта ядра

SetHandleInformation(hobj, HANDLE_FLAG_INHERIT,

HANDLE_FLAG_INHERIT);

а чтобы сбросить этот флаг:

SetHandleInformation(hobj, HANDLE_FLAG_INHERIT, 0);