Драйверы режима ядра: Часть 13: Базовая техника. Синхронизация: Взаимоисключающий доступ — Архив WASM.RU

Все статьи

Драйверы режима ядра: Часть 13: Базовая техника. Синхронизация: Взаимоисключающий доступ — Архив WASM.RU



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

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

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

В данном случае для синхронизации потоков как нельзя лучше подходит объект синхронизации мьютекс (mutex). В ядре он также носит название мутант (mutant). Термин mutex происходит от слов "mutual exclusion", что означает "взаимоисключающий доступ".

Перед тем как начать работу с разделяемым ресурсом поток должен захватить мьютекс. Единовременно владеть мьютексом может только один поток. Поэтому, если потоку удается захватить мьютекс, это означает, что мьютекс был свободен. Если мьютекс занят, система переводит поток в режим ожидания до тех пор, пока мьютекс не освободится. Захватив мьютекс, поток начинает работать с ресурсом, имея гарантию на то, что, сколько бы потоков не пыталось захватить тот же самый мьютекс, все они будут ждать его освобождения. Закончив работу с ресурсом, поток освобождает мьютекс. Даже если в этот момент мьютекс ожидает несколько потоков, захватить его сможет только один поток (какой именно - решает планировщик). Таким образом, мьютекс гарантирует, что только один поток получает монопольный доступ к ресурсу.



13.1 Исходный текст драйвера MutualExclusion


 ;@echo off
 ;goto make

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;
 ;  MutualExclusion - Взаимоисключающий доступ к разделяемому ресурсу
 ;
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .386
 .model flat, stdcall
 option casemap:none

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 include \masm32\include\w2k\ntstatus.inc
 include \masm32\include\w2k\ntddk.inc
 include \masm32\include\w2k\ntoskrnl.inc

 includelib \masm32\lib\w2k\ntoskrnl.lib

 include \masm32\Macros\Strings.mac

 NUM_THREADS equ 5       ; не должно быть больше чем MAXIMUM_WAIT_OBJECTS (64)
 NUM_WORKS   equ 10

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       М А К Р О С Ы                                               
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 include Mutex.mac

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .const

 CCOUNTED_UNICODE_STRING "\\Device\\MutualExclusion", g_usDeviceName, 4
 CCOUNTED_UNICODE_STRING "\\DosDevices\\MutualExclusion", g_usSymbolicLinkName, 4

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                     Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .data?

 g_pkWaitBlock       PKWAIT_BLOCK    ?
 g_apkThreads        DWORD NUM_THREADS dup(?)    ; Массив указателей на KTHREAD
 g_dwCountThreads    DWORD   ?
 g_kMutex            KMUTEX  <>
 g_dwWorkElement     DWORD   ?

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                           К О Д                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .code

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        ThreadProc                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 ThreadProc proc uses ebx Param:DWORD

 local liDelayTime:LARGE_INTEGER
 local pkThread:DWORD     ; PKTHREAD
 local dwWorkElement:DWORD

     invoke PsGetCurrentThread
     mov pkThread, eax
     invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is entering ThreadProc\n"), pkThread

     xor ebx, ebx
     .while ebx < NUM_WORKS

         invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is working on #%d\n"), pkThread, ebx
        
         MUTEX_WAIT addr g_kMutex

         ; Считываем значение разделяемого ресурса

         push g_dwWorkElement
         pop dwWorkElement

         ; Имитируем работу с разделяемым ресурсом

         invoke rand             ; Выдает псевдослучайное число в диапазоне 0 - 07FFFh
         shl eax, 4              ; * 16
         neg eax                 ; задержка = 0 - ~50 мс
         or liDelayTime.HighPart, -1
         mov liDelayTime.LowPart, eax
         invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime

         ; Изменяем разделяемый ресурс и записываем его назад

         inc dwWorkElement

         push dwWorkElement
         pop g_dwWorkElement

         MUTEX_RELEASE addr g_kMutex

         mov eax, liDelayTime.LowPart
         neg eax
         mov edx, 3518437209     ; Магическое число
         mul edx                 ; Деление на 10000 через умножение. Получим миллисекунды.
         shr edx, 13
         invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X work #%d is done (%02dms)\n"), \
                            pkThread, ebx, edx

         inc ebx

     .endw

     invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is about to terminate\n"), pkThread

     invoke PsTerminateSystemThread, STATUS_SUCCESS

     ret

 ThreadProc endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                          CleanUp                                                  
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 CleanUp proc pDriverObject:PDRIVER_OBJECT

     invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName

     mov eax, pDriverObject
     invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject

     .if g_pkWaitBlock != NULL
         invoke ExFreePool, g_pkWaitBlock
         and g_pkWaitBlock, NULL
     .endif

     ret

 CleanUp endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DriverUnload                                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 DriverUnload proc pDriverObject:PDRIVER_OBJECT

     invoke DbgPrint, $CTA0("MutualExclusion: Entering DriverUnload\n")
     invoke DbgPrint, $CTA0("MutualExclusion: Wait for threads exit...\n")

     ; Ждем окончания работы всех потоков

     .if g_dwCountThreads > 0

         invoke KeWaitForMultipleObjects, g_dwCountThreads, addr g_apkThreads, WaitAll, \
                     Executive, KernelMode, FALSE, NULL, g_pkWaitBlock

         .while g_dwCountThreads
             dec g_dwCountThreads
             mov eax, g_dwCountThreads   ; zero-based
             fastcall ObfDereferenceObject, g_apkThreads[eax * type g_apkThreads]
         .endw

     .endif

     invoke CleanUp, pDriverObject

     ; Выдаем результаты работы. Значение g_dwWorkElement должно быть равно NUM_THREADS * NUM_WORKS

     invoke DbgPrint, $CTA0("MutualExclusion: WorkElement = %d\n"), g_dwWorkElement
 
     invoke DbgPrint, $CTA0("MutualExclusion: Leaving DriverUnload\n")

     ret

 DriverUnload endp

 ; На всякий случай проверим, ещё на этапе компиляции, не превышает ли значение NUM_THREADS
 ; максимально допустимого MAXIMUM_WAIT_OBJECTS.

 ; Если мы заставим систему ждать более чем MAXIMUM_WAIT_OBJECTS объектов,
 ; то получим BSOD с кодом 0xC (MAXIMUM_WAIT_OBJECTS_EXCEEDED).

 IF NUM_THREADS GT MAXIMUM_WAIT_OBJECTS
     .ERR Maximum number of wait objects exceeded!
 ENDIF

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       StartThread                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 StartThreads proc uses ebx

 local hThread:HANDLE
 local i:DWORD
    
     and i, 0
     xor ebx, ebx
     .while i < NUM_THREADS
    
         invoke PsCreateSystemThread, addr hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadProc, 0
         .if eax == STATUS_SUCCESS

             invoke ObReferenceObjectByHandle, hThread, THREAD_ALL_ACCESS, NULL, KernelMode, \
                                     addr g_apkThreads[ebx * type g_apkThreads], NULL

             invoke ZwClose, hThread
             invoke DbgPrint, $CTA0("MutualExclusion: System thread created. Thread Object: %08X\n"), \
                                     g_apkThreads[ebx * type g_apkThreads]
             inc ebx
         .else
             invoke DbgPrint, $CTA0("MutualExclusion: Can't create system thread. Status: %08X\n"), eax
         .endif
         inc i
     .endw

     mov g_dwCountThreads, ebx
     .if ebx != 0
         mov eax, STATUS_SUCCESS
     .else
         mov eax, STATUS_UNSUCCESSFUL
     .endif

     ret

 StartThreads endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DriverEntry                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

 local status:NTSTATUS
 local pDeviceObject:PDEVICE_OBJECT
 local liTickCount:LARGE_INTEGER

     mov status, STATUS_DEVICE_CONFIGURATION_ERROR

     invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, \
                                FILE_DEVICE_UNKNOWN, 0, FALSE, addr pDeviceObject
     .if eax == STATUS_SUCCESS
         invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
         .if eax == STATUS_SUCCESS

             mov eax, NUM_THREADS
             mov ecx, sizeof KWAIT_BLOCK
             xor edx, edx
             mul ecx

             and g_pkWaitBlock, NULL
             invoke ExAllocatePool, NonPagedPool, eax
             .if eax != NULL
                 mov g_pkWaitBlock, eax

                 MUTEX_INIT addr g_kMutex

                 invoke KeQueryTickCount, addr liTickCount

                 invoke srand, liTickCount.LowPart

                 and g_dwWorkElement, 0

                 invoke StartThreads
                 .if eax == STATUS_SUCCESS
                     mov eax, pDriverObject
                     mov (DRIVER_OBJECT PTR [eax]).DriverUnload, offset DriverUnload
                     mov status, STATUS_SUCCESS
                 .else
                     invoke CleanUp, pDriverObject
                 .endif
             .else
                 invoke CleanUp, pDriverObject
                 invoke DbgPrint, $CTA0("MutualExclusion: Couldn't allocate memory for Wait Block\n")
             .endif
         .else
             invoke IoDeleteDevice, pDeviceObject
         .endif
     .endif

     mov eax, status
     ret

 DriverEntry endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                                                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 end DriverEntry

 :make

 set drv=MutualExclusion

 \masm32\bin\ml /nologo /c /coff %drv%.bat
 \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj

 del %drv%.obj

 echo.
 pause



13.2 Процедура DriverEntry


             mov eax, NUM_THREADS
             mov ecx, sizeof KWAIT_BLOCK
             xor edx, edx
             mul ecx

             and g_pkWaitBlock, NULL
             invoke ExAllocatePool, NonPagedPool, eax
             .if eax != NULL
                 mov g_pkWaitBlock, eax

Выделяем блок памяти размером NUM_THREADS * sizeof KWAIT_BLOCK. Константа NUM_THREADS определяет, сколько потоков мы запустим. Сохраняем указатель на выделенный блок памяти в переменной g_pkWaitBlock. Эта память понадобится нам при выгрузке драйвера. Зачем она нужна и почему мы выделяем её при инициализации драйвера, я объясню позже. Пока мы её не используем.


                 MUTEX_INIT g_kMutex

Как я уже сказал, мы будем использовать для синхронизации мьютекс. Перед использованием его надо инициализировать, вызовом функции KeInitializeMutex. Я использую макрос MUTEX_INIT. В упрощенном виде он выглядит так (полная версия макроса более универсальна - см. Mutex.mac):


 MUTEX_INIT MACRO mtx:REQ
     invoke KeInitializeMutex, mtx, 0
 ENDM

Функция KeInitializeMutex просто заполняет структуру KMUTANT, описывающую объект мьютекс, при этом мьютекс устанавливается в свободное состояние.


 KMUTANT STRUCT                           ; sizeof = 020h
     Header          DISPATCHER_HEADER <> ; 0000h
     MutantListEntry LIST_ENTRY        <> ; 0010h
     OwnerThread     PVOID             ?  ; 0018h  PTR KTHREAD
     Abandoned       BYTE              ?  ; 001Ch  BOOLEAN
     ApcDisable      BYTE              ?  ; 001Dh
                     WORD              ?  ; 001Eh  padding
 KMUTANT ENDS

Итак, мьютекс готов к использованию.


                 invoke KeQueryTickCount, addr liTickCount

                 invoke srand, liTickCount.LowPart

Для максимального приближения к боевым условиям нам нужно заставить потоки обращаться к разделяемому ресурсу хаотично. Для этого мы будем генерировать некое псевдослучайное число, и использовать его как временной интервал для задержки потока. Ntoskrnl.exe экспортирует стандартную библиотечную функцию rand. Эта функция возвращает псевдослучайное число в диапазоне 0 - 07FFFh. Для генерации используется так называемое начальное число или "затравка" (seed), которое хранится в глобальной неэкспортируемой переменной ядра. Изначально "затравка" инициализирована единицей, т.е. начальное число для генератора псевдослучайных чисел уже существует. Но мы всё же сделаем всё по правилам: вызовем функцию srand и проинициализируем "затравку" также псевдослучайным числом, в качестве которого используем младшую часть 64-битного чила, возвращаемого функцией KeQueryTickCount. KeQueryTickCount возвращает количество тактов, прошедших с момента подачи питания на процессор.


                 and g_dwWorkElement, 0

g_dwWorkElement - это наш разделяемый ресурс.



13.3 Создаем системные потоки


     and i, 0
     xor ebx, ebx
     .while i < NUM_THREADS
    
         invoke PsCreateSystemThread, addr hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadProc, 0
         .if eax == STATUS_SUCCESS

             invoke ObReferenceObjectByHandle, hThread, THREAD_ALL_ACCESS, NULL, KernelMode, \
                                     addr g_apkThreads[ebx * type g_apkThreads], NULL

             invoke ZwClose, hThread

             inc ebx

         .endif
         inc i
     .endw

     mov g_dwCountThreads, ebx

Здесь нет никаких принципиальных отличий от того, что мы делали в прошлый раз в драйвере TimerWorks. Единственная разница в том, что мы запускаем не один, а NUM_THREADS потоков, сохраняем указатели на них в массиве g_apkThreads, а количество фактически созданных потоков - в переменной g_dwCountThreads. Стартовая функция всех потоков одна и та же - ThreadProc.

И ещё один момент.


 IF NUM_THREADS GT MAXIMUM_WAIT_OBJECTS
     .ERR Maximum number of wait objects exceeded!
 ENDIF

Эти три строчки не позволят вам скомпилировать драйвер, если по ошибке вы измените NUM_THREADS на значение большее, чем MAXIMUM_WAIT_OBJECTS. MAXIMUM_WAIT_OBJECTS - это максимальное количество объектов ожидания, ожидать которые можно одновременно, и равно оно 64. Точно также как и в драйвере TimerWorks в процедуре DriverUnload мы будем ждать все работающие потоки и их количество не должно быть больше MAXIMUM_WAIT_OBJECTS.



13.4 Процедура потоков

Итак, мы создали NUM_THREADS потоков. Все они рано или поздно по решению планировщика начнут выполнять процедуру ThreadProc. Причем в многопроцессорной системе это, возможно, будет происходить в буквальном смысле одновременно.


     invoke PsGetCurrentThread
     mov pkThread, eax
     invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is entering ThreadProc\n"), pkThread

Функцией PsGetCurrentThread получаем указатель на структуру текущего потока и выводим её адрес в отладочном сообщении.


     xor ebx, ebx
     .while ebx < NUM_WORKS

Организуем цикл, повторяющийся NUM_WORKS раз. В цикле имитируем случайную работу потоков с разделяемым ресурсом. Каждый поток должен будет увеличить на единицу значение единственной глобальной переменной g_dwWorkElement и проделать эту операцию NUM_WORKS раз.


         MUTEX_WAIT g_kMutex

Если поток оказывается в этой точке, значит, он успешно захватил мьютекс и потоку гарантировано, что до тех пор, пока он не освободит мьютекс, никто больше не сможет его (мьютекс) захватить. Значит, поток может монопольно работать с ресурсом, т.к. все остальные NUM_THREADS - 1 потоков для входа в этот участок кода также должны захватить тот же самый мьютекс.

Здесь я тоже использую макрос. В упрощенном виде он выглядит так (полная версия макроса также более универсальна):


 MUTEX_WAIT MACRO mtx:REQ
     invoke KeWaitForMutexObject, mtx, Executive, KernelMode, FALSE, NULL
 ENDM

Параметры функции KeWaitForMutexObject полностью идентичны параметрам функции KeWaitForSingleObject и всё сказанное в прошлый раз относительно функции KeWaitForSingleObject также применимо к KeWaitForMutexObject. Более того, в заголовочных файлах KeWaitForMutexObject определена так:


 #define KeWaitForMutexObject KeWaitForSingleObject

И если уж совсем на чистоту, то эти две функции имеют одинаковую точку входа. Т.е. KeWaitForMutexObject и KeWaitForSingleObject - это просто имена-синонимы одной и той же функции.

В ядре существует ещё один мьютекс - быстрый мьютекс (fast mutex). Быстрым он называется потому, что его захват/освобождение проходят быстрее. Он значительно отличается от обычного мьютекса и менее универсален.


         push g_dwWorkElement
         pop dwWorkElement

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

Для имитации работы потока с ресурсом мы просто приостановим его выполнение на случайный интервал из диапазона 0-50 миллисекунд. Т.е. предполагается, что всё это время поток производит с ресурсом какие-то манипуляции.


         invoke rand
         shl eax, 4              ; * 16
         neg eax                 ; задержка = 0 - ~50 мс
         or liDelayTime.HighPart, -1
         mov liDelayTime.LowPart, eax
         invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime

Как я уже сказал, функция rand выдает псевдослучайное число в диапазоне 0 - 07FFFh. Умножив его на 16, мы получим необходимое нам время задержки. Всё что я говорил в прошлый раз про параметр DueTime функции KeSetTimerEx полностью применимо к параметру DelayTime функции KeDelayExecutionThread. Единственное отличие в том, что в функцию KeDelayExecutionThread передается не само 64-битное значение, а указатель на него. Как следует из имени функции KeDelayExecutionThread, она приостанавливает работу потока на некоторое время. Эта функция должна вызываться только при IRQL = PASSIVE_LEVEL. Внутренне она использует для ожидания таймер.

Про функцию rand следует сказать ещё пару слов. Как я уже сказал, эта функция генерирует псевдослучайное число на основании начального значения, которое мы проинициализировали вызовом функции srand при запуске драйвера. Каждый вызов rand меняет это начальное число, а поскольку это глобальная переменная ядра, то мы оказываем некоторое влияние на результат возможного вызова этой функции каким-то другим кодом. Но поскольку результатом является случайное число, то ни менее, ни более случайным оно не станет. Так что пользоваться функцией rand можно без особых проблем. Я, кстати, не обнаружил использования этой функции самим ядром.


         inc dwWorkElement

         push dwWorkElement
         pop g_dwWorkElement

Изменяем разделяемый ресурс и записываем его назад.


         MUTEX_RELEASE g_kMutex

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

И опять макрос, упрощенная форма которого выглядит так:


 MUTEX_RELEASE MACRO mtx:REQ
     invoke KeReleaseMutex, mtx, FALSE
 ENDM

Последний параметр функции KeReleaseMutex нужен для увеличения общей производительности. Если сразу после освобождения мьютекса вы собираетесь снова ждать, возможно, какой-то другой объект, то последний параметр можно установить в TRUE. Тогда KeReleaseMutex не будет снимать блокировку с базы данных диспетчера, а следующая функция KeWaitXxx соответственно не будет её устанавливать и вызовы KeReleaseMutex-KeWaitXxx пройдут как единая атомарная операция: блокировка базы данных диспетчера будет установлена при входе в KeReleaseMutex, а снята внутри KeWaitXxx. Я использую значение FALSE, т.к. макросы MUTEX_INIT, MUTEX_WAIT и MUTEX_RELEASE - это общее решение на все случаи жизни. Если время для вас критично, то тогда можно прибегнуть к оптимизации. Я привожу упрощенные версии макросов для того, чтобы в случае если вы с ними не дружите, вам было понятно какие функции и как следует использовать.

Функция KeReleaseMutex, кстати, является оболочкой вокруг более гибкой функции KeReleaseMutant.

Ещё несколько моментов по поводу обычного (не быстрого) мьютекса. За каждым захватом мьютекса должно следовать его освобождение. Причем мьютекс можно захватывать рекурсивно, т.е. несколько раз подряд, но только одним потоком. Если поток несколько раз захватил мьютекс, то ровно столько же раз должен его освободить. Количество рекурсивных захватов мьютекса ограничено константой MINLONG, равной 80000000h. Если вы превысите это несуразно большое значение, то получите BSOD с кодом STATUS_MUTANT_LIMIT_EXCEEDED. Если попытаетесь освободить не вами занятый мьютекс, то - STATUS_MUTANT_NOT_OWNED.


         mov eax, liDelayTime.LowPart
         neg eax
         mov edx, 3518437209     ; Магическое число
         mul edx
         shr edx, 13
         invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X work #%d is done (%02dms)\n"), \
                            pkThread, ebx, edx

Для контроля выводим сообщение, в котором поток уведомляет о том, какую по счету работу он сделал, и сколько это заняло времени. Здесь я использую "магическое деление" - это деление через умножение, что гораздо быстрее (хотя, в данном случае, время абсолютно не критично). Поскольку задержка измеряется в 100-наносекундных интервалах, надо умножить это число на 10000, для того чтобы перейти к миллисекундам. Для вычисления "магических чисел" ищите на http://www.wasm.ru/ в разделе "Образовательные программы" утилиту Magic Divider by The Svin.


         inc ebx

     .endw

Переходим к следующей работе.


     invoke PsTerminateSystemThread, STATUS_SUCCESS

Если все работы сделаны, разрушаем поток.



13.5 Процедура DriverUnload

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


     .if g_dwCountThreads > 0

         invoke KeWaitForMultipleObjects, g_dwCountThreads, addr g_apkThreads, WaitAll, \
                     Executive, KernelMode, FALSE, NULL, g_pkWaitBlock

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

Первый параметр определяет количество объектов в массиве указателей на объекты, адрес которого передается во втором параметре. Третий параметр определяет, хотим ли мы ждать все объекты или первый освободившийся. Если вы захотите ждать первый освободившийся объект, то определить, какой именно объект перешел в свободное состояние можно будет по возвращенному KeWaitForMultipleObjects значению. Если оно будет равно STATUS_WAIT_0, то значит, вы дождались первый объект в массиве, если STATUS_WAIT_1 - второй, и т.д. Последний параметр - это указатель на блок памяти, который функция KeWaitForMultipleObjects использует для организации ожидания. Размер этого блока должен быть равен произведению размера структуры KWAIT_BLOCK на количество объектов. В DriverEntry мы уже выделили необходимую память. Мы сделали это заранее, т.к. если вдруг (что мало вероятно, но все же) мы не сможем выделить эту память сейчас - при выгрузке драйвера, то, как же мы будем ждать завершения работы наших потоков?


 KTHREAD STRUCT
 . . .
     WaitBlock KWAIT_BLOCK 4 dup(<>)
 . . .
 KTHREAD ENDS

Если количество объектов не более THREAD_WAIT_OBJECTS (3), можно не выделять WaitBlock, т.к. в этом случае система будет использовать память, зарезервированную прямо в объекте "поток":


         .while g_dwCountThreads
             dec g_dwCountThreads
             mov eax, g_dwCountThreads
             fastcall ObfDereferenceObject, g_apkThreads[eax * type g_apkThreads]
         .endw

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


     invoke DbgPrint, $CTA0("MutualExclusion: WorkElement = %d\n"), g_dwWorkElement

Выдаем результаты работы. Значение g_dwWorkElement должно быть равно NUM_THREADS * NUM_WORKS. Закомментарив макросы MUTEX_WAIT и MUTEX_RELEASE, вы ни за что не получите верного результата, т.к. потоки будут бесконтрольно изменять разделяемый ресурс.

Исходный код драйвера в архиве.

2002-2013 (c) wasm.ru