Драйверы режима ядра: Часть 13: Базовая техника. Синхронизация: Взаимоисключающий доступ — Архив WASM.RU
В прошлой статье мы использовали синхронизацию для ожидания срабатывания таймера и ожидания окончания работы потока. Другой часто встречающейся задачей, которую решает синхронизация, является задача монопольного доступа к данным.
В примере, который мы рассмотрим в этот раз, создается уже не один, а несколько потоков. Все потоки работают с одним и тем же ресурсом, которым является переменная размером DWORD. В задачу каждого потока входит несколько раз увеличить значение этой переменной на единицу. По окончании работ значение переменой должно быть равно произведению количества потоков на количество проходов.
Переменная, значение которой увеличивают потоки, является прототипом каких-либо данных, например, статистических данных о кол-ве перехватов одного из системных сервисов (этим мы займемся в одной из следующих статей). Если работу потоков не синхронизировать, то они будут обращаться к разделяемым данным в непредсказуемом порядке, что неизбежно приведет к тому, что вместо статистических данных мы, в лучшем случае, получим мусор, а в худшем драйвер станет причиной краха системы.
В данном случае для синхронизации потоков как нельзя лучше подходит объект синхронизации мьютекс (mutex). В ядре он также носит название мутант (mutant). Термин mutex происходит от слов "mutual exclusion", что означает "взаимоисключающий доступ".
Перед тем как начать работу с разделяемым ресурсом поток должен захватить мьютекс. Единовременно владеть мьютексом может только один поток. Поэтому, если потоку удается захватить мьютекс, это означает, что мьютекс был свободен. Если мьютекс занят, система переводит поток в режим ожидания до тех пор, пока мьютекс не освободится. Захватив мьютекс, поток начинает работать с ресурсом, имея гарантию на то, что, сколько бы потоков не пыталось захватить тот же самый мьютекс, все они будут ждать его освобождения. Закончив работу с ресурсом, поток освобождает мьютекс. Даже если в этот момент мьютекс ожидает несколько потоков, захватить его сможет только один поток (какой именно - решает планировщик). Таким образом, мьютекс гарантирует, что только один поток получает монопольный доступ к ресурсу.
13.1 Исходный текст драйвера MutualExclusion
Код (Text):
;@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
Код (Text):
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. Эта память понадобится нам при выгрузке драйвера. Зачем она нужна и почему мы выделяем её при инициализации драйвера, я объясню позже. Пока мы её не используем.
Код (Text):
MUTEX_INIT g_kMutexКак я уже сказал, мы будем использовать для синхронизации мьютекс. Перед использованием его надо инициализировать, вызовом функции KeInitializeMutex. Я использую макрос MUTEX_INIT. В упрощенном виде он выглядит так (полная версия макроса более универсальна - см. Mutex.mac):
Код (Text):
MUTEX_INIT MACRO mtx:REQ invoke KeInitializeMutex, mtx, 0 ENDMФункция KeInitializeMutex просто заполняет структуру KMUTANT, описывающую объект мьютекс, при этом мьютекс устанавливается в свободное состояние.
Код (Text):
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Итак, мьютекс готов к использованию.
Код (Text):
invoke KeQueryTickCount, addr liTickCount invoke srand, liTickCount.LowPartДля максимального приближения к боевым условиям нам нужно заставить потоки обращаться к разделяемому ресурсу хаотично. Для этого мы будем генерировать некое псевдослучайное число, и использовать его как временной интервал для задержки потока. Ntoskrnl.exe экспортирует стандартную библиотечную функцию rand. Эта функция возвращает псевдослучайное число в диапазоне 0 - 07FFFh. Для генерации используется так называемое начальное число или "затравка" (seed), которое хранится в глобальной неэкспортируемой переменной ядра. Изначально "затравка" инициализирована единицей, т.е. начальное число для генератора псевдослучайных чисел уже существует. Но мы всё же сделаем всё по правилам: вызовем функцию srand и проинициализируем "затравку" также псевдослучайным числом, в качестве которого используем младшую часть 64-битного чила, возвращаемого функцией KeQueryTickCount. KeQueryTickCount возвращает количество тактов, прошедших с момента подачи питания на процессор.
Код (Text):
and g_dwWorkElement, 0g_dwWorkElement - это наш разделяемый ресурс.
13.3 Создаем системные потоки
Код (Text):
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.
И ещё один момент.
Код (Text):
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. Причем в многопроцессорной системе это, возможно, будет происходить в буквальном смысле одновременно.
Код (Text):
invoke PsGetCurrentThread mov pkThread, eax invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is entering ThreadProc\n"), pkThreadФункцией PsGetCurrentThread получаем указатель на структуру текущего потока и выводим её адрес в отладочном сообщении.
Код (Text):
xor ebx, ebx .while ebx < NUM_WORKSОрганизуем цикл, повторяющийся NUM_WORKS раз. В цикле имитируем случайную работу потоков с разделяемым ресурсом. Каждый поток должен будет увеличить на единицу значение единственной глобальной переменной g_dwWorkElement и проделать эту операцию NUM_WORKS раз.
Код (Text):
MUTEX_WAIT g_kMutexЕсли поток оказывается в этой точке, значит, он успешно захватил мьютекс и потоку гарантировано, что до тех пор, пока он не освободит мьютекс, никто больше не сможет его (мьютекс) захватить. Значит, поток может монопольно работать с ресурсом, т.к. все остальные NUM_THREADS - 1 потоков для входа в этот участок кода также должны захватить тот же самый мьютекс.
Здесь я тоже использую макрос. В упрощенном виде он выглядит так (полная версия макроса также более универсальна):
Код (Text):
MUTEX_WAIT MACRO mtx:REQ invoke KeWaitForMutexObject, mtx, Executive, KernelMode, FALSE, NULL ENDMПараметры функции KeWaitForMutexObject полностью идентичны параметрам функции KeWaitForSingleObject и всё сказанное в прошлый раз относительно функции KeWaitForSingleObject также применимо к KeWaitForMutexObject. Более того, в заголовочных файлах KeWaitForMutexObject определена так:
Код (Text):
#define KeWaitForMutexObject KeWaitForSingleObjectИ если уж совсем на чистоту, то эти две функции имеют одинаковую точку входа. Т.е. KeWaitForMutexObject и KeWaitForSingleObject - это просто имена-синонимы одной и той же функции.
В ядре существует ещё один мьютекс - быстрый мьютекс (fast mutex). Быстрым он называется потому, что его захват/освобождение проходят быстрее. Он значительно отличается от обычного мьютекса и менее универсален.
Код (Text):
push g_dwWorkElement pop dwWorkElementСчитываем значение разделяемого ресурса в локальную память потока (а именно в стек) и начинаем с ним работать.
Для имитации работы потока с ресурсом мы просто приостановим его выполнение на случайный интервал из диапазона 0-50 миллисекунд. Т.е. предполагается, что всё это время поток производит с ресурсом какие-то манипуляции.
Код (Text):
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 можно без особых проблем. Я, кстати, не обнаружил использования этой функции самим ядром.
Код (Text):
inc dwWorkElement push dwWorkElement pop g_dwWorkElementИзменяем разделяемый ресурс и записываем его назад.
Код (Text):
MUTEX_RELEASE g_kMutexРабота сделана, и мьютекс надо освободить, для того чтобы другие потоки, которые уже, наверное, ждут его освобождения, тоже могли проделать свою часть работы.
И опять макрос, упрощенная форма которого выглядит так:
Код (Text):
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.
Код (Text):
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.
Код (Text):
inc ebx .endwПереходим к следующей работе.
Код (Text):
invoke PsTerminateSystemThread, STATUS_SUCCESSЕсли все работы сделаны, разрушаем поток.
13.5 Процедура DriverUnload
Точно так же как и в драйвере TimerWorks, прежде чем позволить выгрузить драйвер надо убедиться, что все потоки завершили работу, т.к. потоковая процедура находится в теле драйвера и его преждевременная выгрузка приведет к краху системы. Мы решим эту проблему точно также как и в прошлый раз - будем ждать, но теперь уже несколько потоков.
Код (Text):
.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 мы уже выделили необходимую память. Мы сделали это заранее, т.к. если вдруг (что мало вероятно, но все же) мы не сможем выделить эту память сейчас - при выгрузке драйвера, то, как же мы будем ждать завершения работы наших потоков?
Код (Text):
KTHREAD STRUCT . . . WaitBlock KWAIT_BLOCK 4 dup() . . . KTHREAD ENDSЕсли количество объектов не более THREAD_WAIT_OBJECTS (3), можно не выделять WaitBlock, т.к. в этом случае система будет использовать память, зарезервированную прямо в объекте "поток":
Код (Text):
.while g_dwCountThreads dec g_dwCountThreads mov eax, g_dwCountThreads fastcall ObfDereferenceObject, g_apkThreads[eax * type g_apkThreads] .endwДождавшись окончания работы всех потоков, уменьшаем количество ссылок на их объекты, которое мы увеличили при создании потоков.
Код (Text):
invoke DbgPrint, $CTA0("MutualExclusion: WorkElement = %d\n"), g_dwWorkElementВыдаем результаты работы. Значение g_dwWorkElement должно быть равно NUM_THREADS * NUM_WORKS. Закомментарив макросы MUTEX_WAIT и MUTEX_RELEASE, вы ни за что не получите верного результата, т.к. потоки будут бесконтрольно изменять разделяемый ресурс.
Исходный код драйвера в архиве.
© Four-F
Драйверы режима ядра: Часть 13: Базовая техника. Синхронизация: Взаимоисключающий доступ
Дата публикации 3 май 2004