Программирование драйверов Windows

         

Мьютексы


Мьютекс является синхронизационным примитивом (объектом), которым может владеть только один поток в данный конкретный момент времени. Термин mutex является сокращением от 'mutual exclusion', совместное исключение. Объект этого типа имеет несигнальное состояние, когда поток им владеет, и сигнальное &#8212 когда объект свободен. Мьютексы обеспечивают несложный механизм координации исключительного доступа к совместно используемым ресурсам, обычно &#8212 областям памяти.

Предположим, потоки t1 и t2 ожидают освобождения мьютекса, которым владеет поток t0. В момент, когда поток t0 освободит мьютекс, один из ожидающих потоков "пробудится" и станет его владельцем.

Для использования мьютекса необходимо получить блок памяти размером sizeof(KMUTEX) в области нестраничной памяти (например, вызовом ExAllocatePool), после чего следует выполнить его инициализацию вызовом KeInitializeMutex

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

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

Таблица 10.34. Прототип вызова KeInitializeMutex



VOID KeInitializeMutex IRQL == PASSIVE_LEVEL
Параметры Инициализирует объект мьютекса и устанавливает его начальное состояние &#8212 сигнальное.
IN PKMUTEX pMutex Указатель на область, подготовленную для объекта мьютекса
IN LONG Level Уровень, присвоенный мьютексу разработчиком
Возвращаемое значение void
<
Интересен параметр Level, который мало где описан, включая DDK, но может улучшить защищенность кода от ситуаций взаимоблокировок, если драйвер использует несколько мьютексов сразу из нескольких программных потоков. При инициализации объекта мьютекса устанавливается номер уровня (параметр Level). Позднее, когда поток пытается получить очередной мьютекс, ядро не разрешает владение этим мьютексом в случае, если во владении данного потока уже находится любой другой мьютекс с более низким значением уровня. При умелом использовании этого механизма, ядро автоматически предотвращает взаимоблокировки, возникающие в результате использования в драйвере нескольких объектов мьютексов.

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

Драйвер должен освобождать все мьютексы, которые находятся в его владении, перед тем, как передаст управление в пользовательский режим (то есть завершит рабочую процедуру и вернет управление Диспетчеру ввода/вывода). Ядро воспримет это как ошибку. Процедура AddDevice, DriverEntry или какая-либо рабочая процедура драйвера, получая для себя мьютекс, не должны планировать его освобождение в другой рабочей процедуре или в другом программном потоке данного драйвера.

Таблица 10.35. Прототип вызова KeReleaseMutex

LONG KeReleaseMutex IRQL == PASSIVE_LEVEL
Параметры Уменьшает на единицу "счетчик занятости" объекта мьютекса, обозначая намерение инициатора вызова тут же вызвать (или не вызывать) KeWaitXxx.
IN PKMUTEX pMutex Указатель на объект мьютекса
IN BOOLEAN doCallOfKeWaitXxx • TRUE &#8212 следом за данным вызовом текущий программный поток собирается сделать вызов KeWaitForXxx

(используется редко)

• FALSE &#8212 применяемое на практике значение (см. документацию DDK)
Возвращаемое значение 0, если объект мьютекса перешел в сигнальное состояние
<


Запрос на владение мьютексом выполняется вызовом KeWaitForSingleObject

либо вызовом KeWaitForMultipleObject из кода, работающего на уровне IRQL равном PASSIVE_LEVEL. Специально для мьютексов придумано также макроопределение KeWaitForMutexObject, которое есть текстуальная подстановка все того же системного вызова KeWaitForSingleObject.

Единственная функция, которую можно вызывать из кода уровня IRQL выше, чем PASSIVE_LEVEL, &#8212 это вызов KeReadStateMutex (таблица 10.36).

Таблица 10.36. Прототип вызова KeReadStateMutex

LONG KeReadStateMutex IRQL &#60= DISPATCH_LEVEL
Параметры Возвращает состояние объекта мьютекса
IN PKMUTEX pMutex Указатель на объект мьютекса
Возвращаемое значение 1, если объект мьютекса находится в сигнальном состоянии
Описанные выше простые мьютексы применяются теперь не так широко, как быстрые мьютексы, которые рассмотрены ниже.

Быстрый мьютекс (fast mutex) &#8212 это синхронизационный объект, который работает практически так же, как и описанный выше обычный мьютекс режима ядра, однако не допускает повторных (рекурсивных) запросов на владение из одного и того же программного потока. Такой мьютекс выполняет меньше работы и функционирует быстрее.

Таблица 10.37. Функции для работы с объектами быстрых мьютексов

Что необходимо сделать Используемый вызов
Создать быстрый мьютекс ExInitializeFastMutex
Сделать запрос на владение ExAcquireFastMutex

ExAcquireFastMutexUnsafe

ExTryToAcquireFastMutex
Освободить объект ExReleaseFastMutex

ExReleaseFastMutexUnsafe
Объект быстрого мьютекса описывается типом FAST_MUTEX (см. например, заголовочный файл DDK wdm.h) и используется для синхронизации доступа к одному или нескольким элементам данных. Любой код, задумавший воспользоваться этими данными, должен сначала сделать запрос на владение соответствующим объектом FAST_MUTEX.

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



Перед использованием функций ExAcquireFastMutex и ExReleaseFastMutex

следует выполнить инициализацию объекта быстрого мьютекса вызовом ExInitializeFastMutex, см. таблицу 10.38. И хотя память под структуру объекта выделяет инициатор этого вызова, как и в ранее описанных случаях для других объектов синхронизации, непосредственно обращаться к полям этого объекта не следует &#8212 необходимо пользоваться только вызовами, предлагаемыми в DDK.

Таблица 10.38. Прототип вызова ExInitializeFastMutex

VOID ExInitializeFastMutex IRQL &#60= DISPATCH_LEVEL
Параметры Инициализирует объект быстрого мьютекса
IN PFAST_MUTEX pFastMutex Указатель на место в нестраничной памяти, подготовленное инициатором данного вызова для объекта быстрого мьютекса
Возвращаемое значение void
В случае если запрос на владение вызовом ExAcquireFastMutex

удовлетворен быть не может (у объекта быстрого мьютекса уже есть владельцы), поток блокируется до наступления сигнального состояния. Блокируется также и процедура APC, адресованная данному программному потоку. При успешном завершении вызова поток инициатора вызова выполняется на уровне IRQL равном APC_LEVEL, а прежнее значение сохраняется в объекте быстрого мьютекса (оно будет восстановлено при освобождении объекта быстрого мьютекса вызовом ExReleaseFastMutex).

Таблица 10.39. Прототип вызова ExAcquireFastMutex

VOID ExAcquireFastMutex IRQL &#60 DISPATCH_LEVEL
Параметры Запрашивает владение объектом быстрого мьютекса
IN PFAST_MUTEX pFastMutex Указатель на объект быстрого мьютекса
Возвращаемое значение void
Таблица 10.40. Прототип вызова ExAcquireFastMutexUnsafe

VOID ExAcquireFastMutexUnsafe IRQL == APC_LEVEL
Параметры Запрашивает владение объектом быстрого мьютекса
IN PFAST_MUTEX pFastMutex Указатель на объект быстрого мьютекса
Возвращаемое значение void
В случае если запрос на владение вызовом ExAcquireFastMutexUnsafe

удовлетворен быть не может, поток блокируется до наступления сигнального состояния, однако, процедура APC, адресованная данному программному потоку, не блокируется.


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

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

Можно пытаться получить владение объектом быстрого мьютекса без блокировки вызывающего потока (в случае занятости нужного объекта быстрого мьютекса) при помощи вызова ExTryToAcquireFastMutex. B случае неудачи этот вызов возвратит значение FALSE. При удовлетворении запроса возвращается, соответственно, значение TRUE, см. таблицу 10.41.

Владение объектом быстрого мьютекса отменяется его текущим владельцем по вызову ExReleaseFastMutex. Редакция пакета DDK для XP настаивает на том, чтобы этот вызов выполнялся из кода, работающего на уровне IRQL равном DISPATCH_LEVEL, вплоть до того, что инициатор вызова должен установить явно этот уровень перед вызовом ExReleaseFastMutex. Обычно не следует об этом беспокоиться, если уровень IRQL не менялся со времени последнего вызова ExAcquireFastMutex, поскольку он автоматически устанавливает именно это значение.

Таблица 10.41. Прототип вызова ExTryToAcquireFastMutex

BOOLEAN ExTryToAcquireFastMutex IRQL &#60 DISPATCH_LEVEL
Параметры Запрашивает владение объектом быстрого мьютекса
IN PFAST_MUTEX pFastMutex Указатель на объект быстрого мьютекса
Возвращаемое значение TRUE &#8212 при успешном завершении, иначе &#8212 FALSE
Таблица 10.42. Прототип вызова ExReleaseFastMutex



VOID ExReleaseFastMutex IRQL == APC_LEVEL
Параметры Отменяет владение объектом быстрого мьютекса, полученного при помощи вызова ExAcquireFastMutexUnsafe
IN PFAST_MUTEX pFastMutex Указатель на объект быстрого мьютекса
Возвращаемое значение void
Таблица 10.43. Прототип вызова ExReleaseFastMutexUnsafe

VOID ExReleaseFastMutexUnsafe IRQL &#60= APC_LEVEL
Параметры Отменяет владение объектом быстрого мьютекса, полученного при помощи вызова ExAcquireFastMutexUnsafe
IN PFAST_MUTEX pFastMutex Указатель на объект быстрого мьютекса
Возвращаемое значение void

Содержание раздела