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

Дата публикации 3 май 2004

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



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

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

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

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

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



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

Код (Text):
  1.  
  2.  
  3.  ;@echo off
  4.  ;goto make
  5.  
  6.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  7.  ;
  8.  ;  MutualExclusion - Взаимоисключающий доступ к разделяемому ресурсу
  9.  ;
  10.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  11.  
  12.  .386
  13.  .model flat, stdcall
  14.  option casemap:none
  15.  
  16.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  17.  ;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
  18.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  19.  
  20.  include \masm32\include\w2k\ntstatus.inc
  21.  include \masm32\include\w2k\ntddk.inc
  22.  include \masm32\include\w2k\ntoskrnl.inc
  23.  
  24.  includelib \masm32\lib\w2k\ntoskrnl.lib
  25.  
  26.  include \masm32\Macros\Strings.mac
  27.  
  28.  NUM_THREADS equ 5       ; не должно быть больше чем MAXIMUM_WAIT_OBJECTS (64)
  29.  NUM_WORKS   equ 10
  30.  
  31.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  32.  ;                                       М А К Р О С Ы                                              
  33.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  34.  
  35.  include Mutex.mac
  36.  
  37.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  38.  ;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                                
  39.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  40.  
  41.  .const
  42.  
  43.  CCOUNTED_UNICODE_STRING "\\Device\\MutualExclusion", g_usDeviceName, 4
  44.  CCOUNTED_UNICODE_STRING "\\DosDevices\\MutualExclusion", g_usSymbolicLinkName, 4
  45.  
  46.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  47.  ;                     Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                                
  48.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  49.  
  50.  .data?
  51.  
  52.  g_pkWaitBlock       PKWAIT_BLOCK    ?
  53.  g_apkThreads        DWORD NUM_THREADS dup(?)    ; Массив указателей на KTHREAD
  54.  g_dwCountThreads    DWORD   ?
  55.  g_kMutex            KMUTEX  
  56.  g_dwWorkElement     DWORD   ?
  57.  
  58.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  59.  ;                                           К О Д                                                  
  60.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  61.  
  62.  .code
  63.  
  64.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  65.  ;                                        ThreadProc                                                
  66.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  67.  
  68.  ThreadProc proc uses ebx Param:DWORD
  69.  
  70.  local liDelayTime:LARGE_INTEGER
  71.  local pkThread:DWORD     ; PKTHREAD
  72.  local dwWorkElement:DWORD
  73.  
  74.      invoke PsGetCurrentThread
  75.      mov pkThread, eax
  76.      invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is entering ThreadProc\n"), pkThread
  77.  
  78.      xor ebx, ebx
  79.      .while ebx < NUM_WORKS
  80.  
  81.          invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is working on #%d\n"), pkThread, ebx
  82.        
  83.          MUTEX_WAIT addr g_kMutex
  84.  
  85.          ; Считываем значение разделяемого ресурса
  86.  
  87.          push g_dwWorkElement
  88.          pop dwWorkElement
  89.  
  90.          ; Имитируем работу с разделяемым ресурсом
  91.  
  92.          invoke rand             ; Выдает псевдослучайное число в диапазоне 0 - 07FFFh
  93.          shl eax, 4              ; * 16
  94.          neg eax                 ; задержка = 0 - ~50 мс
  95.          or liDelayTime.HighPart, -1
  96.          mov liDelayTime.LowPart, eax
  97.          invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime
  98.  
  99.          ; Изменяем разделяемый ресурс и записываем его назад
  100.  
  101.          inc dwWorkElement
  102.  
  103.          push dwWorkElement
  104.          pop g_dwWorkElement
  105.  
  106.          MUTEX_RELEASE addr g_kMutex
  107.  
  108.          mov eax, liDelayTime.LowPart
  109.          neg eax
  110.          mov edx, 3518437209     ; Магическое число
  111.          mul edx                 ; Деление на 10000 через умножение. Получим миллисекунды.
  112.          shr edx, 13
  113.          invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X work #%d is done (%02dms)\n"), \
  114.                             pkThread, ebx, edx
  115.  
  116.          inc ebx
  117.  
  118.      .endw
  119.  
  120.      invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is about to terminate\n"), pkThread
  121.  
  122.      invoke PsTerminateSystemThread, STATUS_SUCCESS
  123.  
  124.      ret
  125.  
  126.  ThreadProc endp
  127.  
  128.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  129.  ;                                          CleanUp                                                  
  130.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  131.  
  132.  CleanUp proc pDriverObject:PDRIVER_OBJECT
  133.  
  134.      invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
  135.  
  136.      mov eax, pDriverObject
  137.      invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject
  138.  
  139.      .if g_pkWaitBlock != NULL
  140.          invoke ExFreePool, g_pkWaitBlock
  141.          and g_pkWaitBlock, NULL
  142.      .endif
  143.  
  144.      ret
  145.  
  146.  CleanUp endp
  147.  
  148.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  149.  ;                                       DriverUnload                                                
  150.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  151.  
  152.  DriverUnload proc pDriverObject:PDRIVER_OBJECT
  153.  
  154.      invoke DbgPrint, $CTA0("MutualExclusion: Entering DriverUnload\n")
  155.      invoke DbgPrint, $CTA0("MutualExclusion: Wait for threads exit...\n")
  156.  
  157.      ; Ждем окончания работы всех потоков
  158.  
  159.      .if g_dwCountThreads > 0
  160.  
  161.          invoke KeWaitForMultipleObjects, g_dwCountThreads, addr g_apkThreads, WaitAll, \
  162.                      Executive, KernelMode, FALSE, NULL, g_pkWaitBlock
  163.  
  164.          .while g_dwCountThreads
  165.              dec g_dwCountThreads
  166.              mov eax, g_dwCountThreads   ; zero-based
  167.              fastcall ObfDereferenceObject, g_apkThreads[eax * type g_apkThreads]
  168.          .endw
  169.  
  170.      .endif
  171.  
  172.      invoke CleanUp, pDriverObject
  173.  
  174.      ; Выдаем результаты работы. Значение g_dwWorkElement должно быть равно NUM_THREADS * NUM_WORKS
  175.  
  176.      invoke DbgPrint, $CTA0("MutualExclusion: WorkElement = %d\n"), g_dwWorkElement
  177.  
  178.      invoke DbgPrint, $CTA0("MutualExclusion: Leaving DriverUnload\n")
  179.  
  180.      ret
  181.  
  182.  DriverUnload endp
  183.  
  184.  ; На всякий случай проверим, ещё на этапе компиляции, не превышает ли значение NUM_THREADS
  185.  ; максимально допустимого MAXIMUM_WAIT_OBJECTS.
  186.  
  187.  ; Если мы заставим систему ждать более чем MAXIMUM_WAIT_OBJECTS объектов,
  188.  ; то получим BSOD с кодом 0xC (MAXIMUM_WAIT_OBJECTS_EXCEEDED).
  189.  
  190.  IF NUM_THREADS GT MAXIMUM_WAIT_OBJECTS
  191.      .ERR Maximum number of wait objects exceeded!
  192.  ENDIF
  193.  
  194.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  195.  ;                                       StartThread                                                
  196.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  197.  
  198.  StartThreads proc uses ebx
  199.  
  200.  local hThread:HANDLE
  201.  local i:DWORD
  202.    
  203.      and i, 0
  204.      xor ebx, ebx
  205.      .while i < NUM_THREADS
  206.    
  207.          invoke PsCreateSystemThread, addr hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadProc, 0
  208.          .if eax == STATUS_SUCCESS
  209.  
  210.              invoke ObReferenceObjectByHandle, hThread, THREAD_ALL_ACCESS, NULL, KernelMode, \
  211.                                      addr g_apkThreads[ebx * type g_apkThreads], NULL
  212.  
  213.              invoke ZwClose, hThread
  214.              invoke DbgPrint, $CTA0("MutualExclusion: System thread created. Thread Object: %08X\n"), \
  215.                                      g_apkThreads[ebx * type g_apkThreads]
  216.              inc ebx
  217.          .else
  218.              invoke DbgPrint, $CTA0("MutualExclusion: Can't create system thread. Status: %08X\n"), eax
  219.          .endif
  220.          inc i
  221.      .endw
  222.  
  223.      mov g_dwCountThreads, ebx
  224.      .if ebx != 0
  225.          mov eax, STATUS_SUCCESS
  226.      .else
  227.          mov eax, STATUS_UNSUCCESSFUL
  228.      .endif
  229.  
  230.      ret
  231.  
  232.  StartThreads endp
  233.  
  234.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  235.  ;                                       DriverEntry                                                
  236.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  237.  
  238.  DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
  239.  
  240.  local status:NTSTATUS
  241.  local pDeviceObject:PDEVICE_OBJECT
  242.  local liTickCount:LARGE_INTEGER
  243.  
  244.      mov status, STATUS_DEVICE_CONFIGURATION_ERROR
  245.  
  246.      invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, \
  247.                                 FILE_DEVICE_UNKNOWN, 0, FALSE, addr pDeviceObject
  248.      .if eax == STATUS_SUCCESS
  249.          invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
  250.          .if eax == STATUS_SUCCESS
  251.  
  252.              mov eax, NUM_THREADS
  253.              mov ecx, sizeof KWAIT_BLOCK
  254.              xor edx, edx
  255.              mul ecx
  256.  
  257.              and g_pkWaitBlock, NULL
  258.              invoke ExAllocatePool, NonPagedPool, eax
  259.              .if eax != NULL
  260.                  mov g_pkWaitBlock, eax
  261.  
  262.                  MUTEX_INIT addr g_kMutex
  263.  
  264.                  invoke KeQueryTickCount, addr liTickCount
  265.  
  266.                  invoke srand, liTickCount.LowPart
  267.  
  268.                  and g_dwWorkElement, 0
  269.  
  270.                  invoke StartThreads
  271.                  .if eax == STATUS_SUCCESS
  272.                      mov eax, pDriverObject
  273.                      mov (DRIVER_OBJECT PTR [eax]).DriverUnload, offset DriverUnload
  274.                      mov status, STATUS_SUCCESS
  275.                  .else
  276.                      invoke CleanUp, pDriverObject
  277.                  .endif
  278.              .else
  279.                  invoke CleanUp, pDriverObject
  280.                  invoke DbgPrint, $CTA0("MutualExclusion: Couldn't allocate memory for Wait Block\n")
  281.              .endif
  282.          .else
  283.              invoke IoDeleteDevice, pDeviceObject
  284.          .endif
  285.      .endif
  286.  
  287.      mov eax, status
  288.      ret
  289.  
  290.  DriverEntry endp
  291.  
  292.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  293.  ;                                                                                                  
  294.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  295.  
  296.  end DriverEntry
  297.  
  298.  :make
  299.  
  300.  set drv=MutualExclusion
  301.  
  302.  \masm32\bin\ml /nologo /c /coff %drv%.bat
  303.  \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj
  304.  
  305.  del %drv%.obj
  306.  
  307.  echo.
  308.  pause
  309.  
  310.  


13.2 Процедура DriverEntry

Код (Text):
  1.  
  2.  
  3.              mov eax, NUM_THREADS
  4.              mov ecx, sizeof KWAIT_BLOCK
  5.              xor edx, edx
  6.              mul ecx
  7.  
  8.              and g_pkWaitBlock, NULL
  9.              invoke ExAllocatePool, NonPagedPool, eax
  10.              .if eax != NULL
  11.                  mov g_pkWaitBlock, eax
  12.  
  13.  

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

Код (Text):
  1.  
  2.  
  3.                  MUTEX_INIT g_kMutex
  4.  
  5.  

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

Код (Text):
  1.  
  2.  
  3.  MUTEX_INIT MACRO mtx:REQ
  4.      invoke KeInitializeMutex, mtx, 0
  5.  ENDM
  6.  
  7.  

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

Код (Text):
  1.  
  2.  
  3.  KMUTANT STRUCT                           ; sizeof = 020h
  4.      Header          DISPATCHER_HEADER  ; 0000h
  5.      MutantListEntry LIST_ENTRY         ; 0010h
  6.      OwnerThread     PVOID             ?  ; 0018h  PTR KTHREAD
  7.      Abandoned       BYTE              ?  ; 001Ch  BOOLEAN
  8.      ApcDisable      BYTE              ?  ; 001Dh
  9.                      WORD              ?  ; 001Eh  padding
  10.  KMUTANT ENDS
  11.  
  12.  

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

Код (Text):
  1.  
  2.  
  3.                  invoke KeQueryTickCount, addr liTickCount
  4.  
  5.                  invoke srand, liTickCount.LowPart
  6.  
  7.  

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

Код (Text):
  1.  
  2.  
  3.                  and g_dwWorkElement, 0
  4.  
  5.  

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



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

Код (Text):
  1.  
  2.  
  3.      and i, 0
  4.      xor ebx, ebx
  5.      .while i < NUM_THREADS
  6.    
  7.          invoke PsCreateSystemThread, addr hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadProc, 0
  8.          .if eax == STATUS_SUCCESS
  9.  
  10.              invoke ObReferenceObjectByHandle, hThread, THREAD_ALL_ACCESS, NULL, KernelMode, \
  11.                                      addr g_apkThreads[ebx * type g_apkThreads], NULL
  12.  
  13.              invoke ZwClose, hThread
  14.  
  15.              inc ebx
  16.  
  17.          .endif
  18.          inc i
  19.      .endw
  20.  
  21.      mov g_dwCountThreads, ebx
  22.  
  23.  

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

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

Код (Text):
  1.  
  2.  
  3.  IF NUM_THREADS GT MAXIMUM_WAIT_OBJECTS
  4.      .ERR Maximum number of wait objects exceeded!
  5.  ENDIF
  6.  
  7.  

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



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

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

Код (Text):
  1.  
  2.  
  3.      invoke PsGetCurrentThread
  4.      mov pkThread, eax
  5.      invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X is entering ThreadProc\n"), pkThread
  6.  
  7.  

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

Код (Text):
  1.  
  2.  
  3.      xor ebx, ebx
  4.      .while ebx < NUM_WORKS
  5.  
  6.  

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

Код (Text):
  1.  
  2.  
  3.          MUTEX_WAIT g_kMutex
  4.  
  5.  

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

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

Код (Text):
  1.  
  2.  
  3.  MUTEX_WAIT MACRO mtx:REQ
  4.      invoke KeWaitForMutexObject, mtx, Executive, KernelMode, FALSE, NULL
  5.  ENDM
  6.  
  7.  

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

Код (Text):
  1.  
  2.  
  3.  #define KeWaitForMutexObject KeWaitForSingleObject
  4.  
  5.  

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

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

Код (Text):
  1.  
  2.  
  3.          push g_dwWorkElement
  4.          pop dwWorkElement
  5.  
  6.  

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

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

Код (Text):
  1.  
  2.  
  3.          invoke rand
  4.          shl eax, 4              ; * 16
  5.          neg eax                 ; задержка = 0 - ~50 мс
  6.          or liDelayTime.HighPart, -1
  7.          mov liDelayTime.LowPart, eax
  8.          invoke KeDelayExecutionThread, KernelMode, FALSE, addr liDelayTime
  9.  
  10.  

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

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

Код (Text):
  1.  
  2.  
  3.          inc dwWorkElement
  4.  
  5.          push dwWorkElement
  6.          pop g_dwWorkElement
  7.  
  8.  

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

Код (Text):
  1.  
  2.  
  3.          MUTEX_RELEASE g_kMutex
  4.  
  5.  

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

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

Код (Text):
  1.  
  2.  
  3.  MUTEX_RELEASE MACRO mtx:REQ
  4.      invoke KeReleaseMutex, mtx, FALSE
  5.  ENDM
  6.  
  7.  

Последний параметр функции 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):
  1.  
  2.  
  3.          mov eax, liDelayTime.LowPart
  4.          neg eax
  5.          mov edx, 3518437209     ; Магическое число
  6.          mul edx
  7.          shr edx, 13
  8.          invoke DbgPrint, $CTA0("MutualExclusion: Thread %08X work #%d is done (%02dms)\n"), \
  9.                             pkThread, ebx, edx
  10.  
  11.  

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

Код (Text):
  1.  
  2.  
  3.          inc ebx
  4.  
  5.      .endw
  6.  
  7.  

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

Код (Text):
  1.  
  2.  
  3.      invoke PsTerminateSystemThread, STATUS_SUCCESS
  4.  
  5.  

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



13.5 Процедура DriverUnload

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

Код (Text):
  1.  
  2.  
  3.      .if g_dwCountThreads > 0
  4.  
  5.          invoke KeWaitForMultipleObjects, g_dwCountThreads, addr g_apkThreads, WaitAll, \
  6.                      Executive, KernelMode, FALSE, NULL, g_pkWaitBlock
  7.  
  8.  

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

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

Код (Text):
  1.  
  2.  
  3.  KTHREAD STRUCT
  4.  . . .
  5.      WaitBlock KWAIT_BLOCK 4 dup()
  6.  . . .
  7.  KTHREAD ENDS
  8.  
  9.  

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

Код (Text):
  1.  
  2.  
  3.          .while g_dwCountThreads
  4.              dec g_dwCountThreads
  5.              mov eax, g_dwCountThreads
  6.              fastcall ObfDereferenceObject, g_apkThreads[eax * type g_apkThreads]
  7.          .endw
  8.  
  9.  

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

Код (Text):
  1.  
  2.  
  3.      invoke DbgPrint, $CTA0("MutualExclusion: WorkElement = %d\n"), g_dwWorkElement
  4.  
  5.  

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

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

© Four-F

0 1.416
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532