Sources.RU Magazine Поиск по журналу
 

TopList

Win32: Обзор объектов ядра

Автор: Eiden

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

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

Приложение не имеет прямого доступа к объектам ядра, для этого существуют специальные функции Windows. Доступ к объектам ядра программа может получить только через эти функции. Каждая функция, создающая объект ядра, возвращает хэндл созданного объекта (в русскоязычной литературе используется также слово «описатель»). Хэндл - это всего лишь некое значение, уникально идентифицирующее объект ядра в пределах данного процесса. Следует заметить, что значения описателей зависят от конкретного процесса. То есть, если с помощью какого-либо способа Вы передадите значение хэндла потоку другого процесса, то использование этого значения во втором процессе приведет к ошибке.

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

Объекты ядра можно защитить с помощью дескриптора защиты. Он описывает, кто создал процесс, и кто имеет доступ к нему. Почти все функции создания объекта ядра принимают указатель на структуру SECURITY_ATTRIBUTES.

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

typedef struct _SECURITY_ATTRIBUTES { 
DWORD nLength, 
LPVOID lpSecurityDescriptor; 
BOOL bInheritHandle; 
} SECURITY_ATTRIBUTES;

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

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); // используется для выяснения версий 
sa.lpSecurityDescriptor = pSD; // адрес инициализированного 
//Security Descriptor 
sa.bInheritHandle = FALSE;

При инициализации процесса система создает в нем таблицу хэндлов, используемую только для объектов ядра. Официально эта таблица недокументирована, но Джеффри Рихтер в своей книге «Создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows» в общих чертах ее описывает, однако, предупреждая при этом, что реализация этой таблицы сильно различается в разных версиях Windows. Таблица представляет собой массив структур данных. Каждая структура содержит указатель на объект ядра, маску доступа и некоторые флаги.

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

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

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

HANDLE hMutex = CreateMutex(...); 
if (hMutex == INVALID_HANDLE_VALUE) 
{ 
 // этот код никогда не будет выполнен, так как 
 // при ошибке CreateMutex() возвращает NULL 
}

Точно так же бессмыслен и следующий код:

HANDLE hFile = CreateFile(.. ); 
if (hFile == NULL} 
{ 
 // и этот код никогда не будет выполнен, так как 
 // при ошибке CreateFile() возвращает INVALID_HANDLE_VALUE (-1) 
}

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

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

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

BOOL CloseHandle(HANDLE hobj);

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

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

Стоит заметить, что не все хэндлы закрываются функцией CloseHandle(). Существуют особые хэндлы, создаваемые функцией FindFirstFile(), для их закрытия служит функция CloseFind().

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



 Design by Шишкин Алексей (Лёха)  ©2004-2008 by sources.ru