Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 семестр, WinAPI, MFC.pdf
Скачиваний:
370
Добавлен:
15.06.2014
Размер:
6.17 Mб
Скачать

действительный описатель, проверяет тип защиты объекта. Если Вас, как зарегистрировавшегося пользователя, допускают к существующему объекту ядра "проекция файла", OpenFileMapping возвращает действительный описатель. Но если Вам отказывают в доступе, OpenFileMapping возвращает NULL, а вызов GetLastError дает код ошибки 5 (или ERROR_ACCESS_DENIED).

Таблица описателей объектов ядра

При инициализации процесса система создает в нем таблицу описателей,

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

В таблице 1 показано, как выглядит таблица описателей, принадлежащая процессу. Как видите, это просто массив структур данных. Каждая структура

содержит указатель на какой-нибудь объект ядра, маску доступа и некоторые флаги.

Индекс

Указатель

 

на

Маска

доступа

Флаги

(DWORD с

 

блок

памяти

(DWORD

с набором

набором

битовых

 

объекта ядра

 

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

флагов)

 

1

0х????????

 

 

0х????????

 

0x????????

 

 

 

 

 

 

 

2

0х????????

 

 

0x????????

 

0x????????

 

 

 

 

 

 

 

 

Таблица 1. Структура таблицы описателей, принадлежащей процессу

Создание объекта ядра

Когда процесс инициализируется в первый paз, таблица описателей еще пуста.

Но стоит одному из его потоков вызвать функцию, создающую объект ядра (например, CreateFileMapping), как ядро выделяет для этого объекта блок памяти и инициализирует его, далее ядро просматривает таблицу описателей, принадлежащую данному процессу, и отыскивает свободную запись. Поскольку таблица еще пуста, ядро обнаруживает структуру с индексом 1 и инициализирует ее. Указатель устанавливается на внутренний адрес структуры данных объекта, маска доступа — на доступ без ограничений и, наконец, определяется последний компонент — флаги (О флагах мы поговорим позже, в разделе о наследовании.)

Вот некоторые функции, создающие объекты ядра :

 

HANDLE

CreateThread(

PSECURITY_ATTRIBUTES

psa,

DWORD

dwStackSize,

PTHREAD_START_ROUTINE

pfnStartAddr,

PVOID

pvParam,

DWORD

dwCreationFlags,

PDWORD pdwThreadId);

 

HANDLE

CreateFile(

PCTSTR

pszFileName,

DWORD

dwDesiredAccess,

DWORD

dwShareMode,

PSECURITY_ATTRIBUTES

psa,

DWORD

dwCreationDistribution,

DWORD

dwFlagsAndAttributes,

HANDLE hTemplateFile);

 

HANDLE

CreateFileMapping(

HANDLE

hFile,

PSECURITY_ATTRIBUTES

psa,

DWORD

flProtect,

DWORD

dwMdximumSizeHigh,

DWORD

dwMaximumSizeLow,

PCTSTR pszName);

 

HANDLE

CreateSemaphore(

PSECURITY_ATTRIBUTES

psa,

LONG

lInitialCount,

LONG

lMaximumCount,

PCTSTR pszName);

 

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

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

ядра как аргумент, Вы передаете ей значение, возвращенное одной из Create-

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

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

Если вызов функции, создающей объект ядра, оказывается неудачен, то обычно возвращается 0 (NULL). Такая ситуация возможна только при острой нехватке памяти или при наличии проблем с защитой. К сожалению, отдельные функции возвращают в таких случаях не 0, а -1 (INVALID_HANDLE_VALUE). Например, если

CreateFile не сможет открыть указанный файл, она вернет именно

INVALID_HANDLE_VALUE.

Закрытие объекта ядра

Независимо от того, как именно Вы создали объект ядра, по окончании работы с ним его нужно закрыть вызовом CloseHandle:

BOOL CloseHandle(HANDLE hobj);

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

Если же описатель неверен, происходит одно из двух. В нормальном режиме выполнения процесса CloseHandle возвращает FALSE, a GetLastError — код ERROR_INVALID_HANDLE. Но при выполнении процесса в режиме отладки система просто уведомляет отладчик об ошибке.

Перед самым возвратом управления CloseHandle удаляет соответствующую запись из таблицы описателей: данный описатель теперь недействителен в Вашем

процессе и использовать его нельзя. При этом запись удаляется независимо от того,

разрушен объект ядра или нет! После вызова CloseHandle Вы больше не получите доступ к этому объекту ядра; но, если его счетчик не обнулен, объект остается в памяти. Это означает лишь то, что объект используется другим процессом (или процессами). Когда и остальные процессы завершат свою работу с этим объектом (тоже вызвав CloseHandle), он будет разрушен.

Синхронизация объектов

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

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

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

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

операций:

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

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

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

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

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

2.

ИндексУказатель на блок

Маска

доступа

Флаги (DWORD с

 

памяти

объекта

(DWORD с набором

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

 

ядра

 

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

флагов)

1

0xF0000000

0x????????

 

0x00000000

2

0x00000000

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

 

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

 

3

0xF0000010

0х????????

 

0x00000001

 

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

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

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

помощью функции CreateProcess:

BOOL

CreateProcess(

PCTSTR

pszApplicationName,

PTSTR

pszCommandLine,

PSECURITY_ATTRIBUTES

psaProcess,

PSECURITY_ATTRIBUTES

psaThread,

BOOL

bInheritHandles,

DWORD

fdwCreate,

PVOID

pvEnvironment,

PCTSTR

pszCurDir,

PSTARTUPINFO

psiStartInfo,

PPROCESS_INFORMATION ppiProcInfo);

 

Создавая процесс, в параметре bInheritHandles обычно передается FALSE, тем самым сообщается системе, что дочерний процесс не должен наследовать наследуемые описатели, зафиксированные в таблице родительского процесса. Если же Вы передаете TRUE, дочерний процесс наследует описатели родительского. Тогда

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

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

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

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

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

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

Индекс

Указатель на

Маска

доступа

Флаги (DWORD с

 

блок

(DWORD

 

набором

 

памяти

с набором

битовых

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

 

объекта ядра

флагов)

 

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

1

0x00000000

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

2

0x00000000

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

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

3

0xF0000010

0х????????

 

0x00000001

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

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

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

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

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