Окт 16

Объекты ядра

Современные процессоры позволяют выполнять программы в одном из двух режимов: Kernel {режим ядра) или User (режим пользователя). В режиме ядра приложению, кроме всего прочего, разрешено выделять память для других приложений. Поэтому изменить важные данные в памяти компьютера, например, управляющие распределением памяти, может только приложение, работающее в режиме ядра.
Архитектура операционной системы Windows NT/2000 состоит из двух основных частей: привилегированной подсистемы режима ядра (privileged kernel mode part) и непривилегированной подсистемы пользовательского режима (nonprivileged user mode part).
Подсистема ядра содержит следующие компоненты:
HAL (Hardware Abstraction Layer) — уровень, абстрагирующий другие компоненты от аппаратных различий, зависимых от платформы.
Микроядро (MicroKernel), которое отвечает за планирование потоков, переключение задач, обработку прерываний и исключительных ситуаций, многопроцессорную синхронизацию.
Драйверы устройств — драйверы оборудования, файловой системы и сетевой поддержки, реализующие пользовательские функции ввода-вывода.
Управление окнами и графическая подсистема реализуют функции графического интерфейса.
Исполнительная часть отвечает за управление памятью, управление процессами и потоками, безопасность, ввод-вывод данных и взаимодействия между процессами.
Объекты ядра используются системой и приложениями для управления самыми разнообразными ресурсами, например процессами, потоками, файлами, семафорами, событиями и многими другими.
Каждый объект ядра — это блок памяти, выделенный ядром и доступный только ему. В блоке размещается структура данных, поля которой содержат информацию об объекте. Некоторые поля (например, дескриптор защиты и счетчик количества пользователей) присутствуют во всех объектах, но большая их часть специфична для объектов конкретного типа. Приложение не имеет прямого доступа к структурам данных, представляющим собой объекты ядра. Оперировать объектами ядра приложение может только через специальные функции Windows.
Когда вы вызываете функцию, создающую объект ядра (например, CreateFile), она возвращает дескриптор созданного объекта. Этот дескриптор может быть использован любым потоком вашего процесса. Для большей надежности операционной системы Microsoft сделала так, что значения дескрипторов объектов ядра действительны только в адресном пространстве процесса, их создавшего. Поэтому все попытки передачи такого дескриптора другому процессу и его использования в другом процессе приводят к ошибкам.
Объекты принадлежат ядру, а не процессу. Другими словами, если процесс создает какой-либо объект ядра, а затем завершает свою работу, то объект ядра может быть и не разрушен. В большинстве случаев объект все же разрушается, но если этим объектом пользуется другой процесс, то ядро не позволит разрушить объект до тех пор, пока второй процесс не откажется от него.
Ядру известно, сколько процессов использует конкретный объект ядра, поскольку в каждом объекте есть счетчик пользователей. В момент создания объекта ядра счетчику присваивается единичное значение. Когда к этому объекту обращается другой процесс, значение счетчика увеличивается на единицу. А когда какой-то процесс завершается, счетчики всех используемых им объектов ядра уменьшаются на единицу. Как только счетчик пользователей объекта обнуляется, ядро уничтожает этот объект.
Многие объекты ядра находятся всегда в одном из двух состояний: свободном состоянии (signaled state) либо несвободном состоянии (nonsignaled state). Переход из одного состояния в другое осуществляется по правилам, определенным Microsoft для каждого из объектов ядра. Эти состояния могут использоваться так называемыми wait-функциями для решения проблем синхронизации выполнения потоков.
Объекты ядра можно защитить с помощью дескриптора защиты (security descriptor). Он содержит информацию о том, кто создал объект и кто имеет права на доступ к нему. Дескрипторы защиты обычно используют при написании серверных приложений. Создавая клиентское приложение, можно просто игнорировать это свойство объектов ядра.
Почти все функции, создающие объекты ядра, принимают в качестве аргумента указатель на структуру SECURITY_ATTRIBUTES. В большинстве случаев на месте соответствующего аргумента можно указать значение NULL, и тогда объект создается с защитой по умолчанию. В этом случае создатель объекта и любой член группы администраторов получают полный доступ к объекту, а все прочие процессы к объекту не допускаются.
Вне зависимости от того, как был создан объект ядра, после окончания работы с ним его нужно закрыть вызовом функции CloseHandle:
BOOL CloseHandle(HANDLE hObject);
Эта функция сначала проверяет таблицу дескрипторов данного процесса, чтобы убедиться, что процесс имеет доступ к объекту hObject. Если доступ разрешен,то система получает адрес структуры данных объекта hObject и уменьшает в ней счетчик количества пользователей. Как только счетчик обнулится, ядро удаляет объект из памяти.
Если же дескриптор неверен, то функция CloseHandle возвращает значение FALSE, а функция GetLastError - код ERROR_INVALID_HANDLE.
Перед самым возвратом управления функция CloseHandle удаляет соответствующую запись из таблицы дескрипторов. После этого дескриптор hObject считается недоступным для данного процесса, и его нельзя более использовать. Но если счетчик пользователей этого объекта не обнулен, то объект остается в памяти. Это означает, что объект используется другими процессами. Когда и остальные процессы завершат свою работу с этим объектом, тоже вызвав функцию CloseHandle, он будет разрушен.
А если вы забыли вызвать CloseHandle — будет ли утечка памяти? И да, и нет. Утечка ресурсов вполне вероятна, пока процесс еще выполняется. Однако по завершении процесса операционная система гарантированно освобождает все ресурсы, принадлежавшие этому процессу.
Ранее указывалось, что объекты ядра используются только в рамках процесса, их создавшего. Но все же иногда возникает необходимость в совместном использовании объектов ядра несколькими процессами, например, в следующих ситуациях:
объект ядра «проекция файла» позволяет двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки памяти;
почтовые ящики и именованные каналы дают возможность программам обмениваться данными с процессами, исполняемыми на других машинах в сети;
мьютексы, семафоры и события позволяют синхронизировать потоки, исполняемые в разных процессах, чтобы одно приложение могло уведомить другое приложение об окончании той или иной операции.
Win32 API предоставляет три механизма, позволяющие процессам использовать одни и те же объекты ядра: а) наследование дескриптора объекта в дочернем процессе; б) именованные объекты; в) дублирование дескрипторов объектов. В приводимых ниже примерах мы будем активно применять второй из указанных механизмов (совпадающие имена для разделяемых объектов).