在上篇教程中,我们讲解了内核同步对象中的计时器对象的使用方法,有关同步的另一个常见的用法就是对数据的独占访问。
在本教程中,我们将同时启动多个线程,所有这些线程都会数次对一个ULONG类型的共享变量进行累加操作,最终这个共享变量的值将会等于所有线程工作次数的总和。
这个共享变量可以是任意类型的数据,比如我们拦截到的系统服务(这些将在后面的教程里讨论)的相关统计数据。如果没有同步机制,这些共享数据的结果就无法预知了,我们可能得不到正确的统计数据甚至严重的话可能会导致系统崩溃。
解决上述问题最好的方案就是使用互斥(Mutex) 对象了,在内核中,Mutex又被称为突变体(mutants)。 互斥,顾名思义,就是排它性访问,也就是同一时间内只允许一个线程访问共享数据,同一驱动的所有线程共同拥有一个Mutex对象,如果一个线程要访问共享变量,它首先要获取这个Mutex对象,一旦某个线程取得了Mutex对象,就可以对共享变量进行存取操作,否则就只能等待直到其他线程释放Mutex对象。通过Mutex机制就能确保同一时间只能有一个线程访问共享变量。
11.1教程源码:
代码:
unit MutualExclusion; {$POINTERMATH ON} interface uses nt_status, ntoskrnl, fcall; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation const { 不能超过MAXIMUM_WAIT_OBJECTS (64) - 最大等待对象数 } NUM_THREADS = 5; NUM_WORKS = 10; var g_usDeviceName, g_usSymbolicLinkName: UNICODE_STRING; g_pkWaitBlock: PKWAIT_BLOCK; { PKTHREAD数组 } g_apkThreads: array[0..NUM_THREADS - 1] of PVOID; g_dwCountThreads: DWORD; g_kMutex: KMUTEX; g_dwWorkElement: DWORD; function ThreadProc: NTSTATUS; stdcall; var liDelayTime:LARGE_INTEGER; pkThread: PVOID; { PKTHREAD } dwWorkElement: ULONG; dwCount, dwTmp: ULONG; begin pkThread := PsGetCurrentThread; DbgPrint('MutualExclusion: Thread %08X is entering ThreadProc'#13#10, pkThread); dwCount := 0; while dwCount < NUM_WORKS do begin DbgPrint('MutualExclusion: Thread %08X is working on #%d'#13#10, pkThread, dwCount); KeWaitForMutexObject(@g_kMutex, Executive, KernelMode, False, nil); { 读取线程间共享资源的值 } dwWorkElement := g_dwWorkElement; { 这里做些其他的事情 } liDelayTime.HighPart := liDelayTime.HighPart or -1; liDelayTime.LowPart := -(rand shl 4); KeDelayExecutionThread(KernelMode, false, @liDelayTime); { 设置新的线程间共享资源的值 } Inc(dwWorkElement); g_dwWorkElement := dwWorkElement; KeReleaseMutex(@g_kMutex, False); dwTmp := (((ULONG(-liDelayTime.LowPart) * 3518437209) and $FFFF0000) shr 16) shr 13; DbgPrint('MutualExclusion: Thread %08X work #%d is done (%02dms)'#13#10, pkThread, dwCount, dwTmp); { 计数器加一,做下一次循环 } Inc(dwCount); end; DbgPrint('MutualExclusion: Thread %08X is about to terminate'#13#10, pkThread); Result := PsTerminateSystemThread(STATUS_SUCCESS); end; procedure CleanUp(pDriverObject:PDRIVER_OBJECT); begin IoDeleteSymbolicLink(@g_usSymbolicLinkName); IoDeleteDevice(pDriverObject^.DeviceObject); if g_pkWaitBlock <> nil then begin ExFreePool(g_pkWaitBlock); g_pkWaitBlock := nil; end; end; procedure DriverUnload(pDriverObject:PDRIVER_OBJECT); stdcall; begin DbgPrint('MutualExclusion: Entering DriverUnload'#13#10); DbgPrint('MutualExclusion: Wait for threads exit...'#13#10); { 因为ThreadProc存在于我们的驱动主体中,因此只要还有一个 { 线程在运行,就不能卸载驱动,必须要等到所有的线程都退出 } if g_dwCountThreads > 0 then begin { 没有设置超时时间,一直等待到所有线程退出 } { 因为有多个线程对象,所以使用KeWaitForMultipleObjects } KeWaitForMultipleObjects(g_dwCountThreads, @g_apkThreads, WaitAll, Executive, KernelMode, False, nil, g_pkWaitBlock); { 执行到这里时,所有线程均已退出,就可以释放所有线程对象了 } while g_dwCountThreads > 0 do begin Dec(g_dwCountThreads); ObfDereferenceObject(g_apkThreads[g_dwCountThreads]); end; end; CleanUp(pDriverObject); { 打印结果. 这里g_dwWorkElement的值应该等于NUM_THREADS * NUM_WORKS } DbgPrint('MutualExclusion: WorkElement = %d'#13#10, g_dwWorkElement); DbgPrint('MutualExclusion: Leaving DriverUnload'#13#10); end; function StartThreads: NTSTATUS; var hThread:HANDLE; i, dwCount:ULONG; rtnCode: NTSTATUS; begin i := 0; { dwCount保存实际运行的线程数 } dwCount := 0; while i < NUM_THREADS do begin { 启动NUM_THREADS个线程 } rtnCode := PsCreateSystemThread(@hThread, THREAD_ALL_ACCESS, nil, 0, nil, @ThreadProc, nil); if rtnCode = STATUS_SUCCESS then begin { 我们不需要PsCreateSystemThread返回的线程句柄. } { 但是我们需要指向它的指针. 以便我们引用线程对象并且关闭它. } ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, nil, KernelMode, @g_apkThreads[dwCount], nil); ZwClose(hThread); DbgPrint('MutualExclusion: System thread created. Thread Object: %08X'#13#10, g_apkThreads[dwCount]); Inc(dwCount); end else begin DbgPrint('MutualExclusion: Can''t create system thread. Status: %08X'#13#10, rtnCode); end; Inc(i); end; g_dwCountThreads := dwCount; if dwCount <> 0 then begin Result := STATUS_SUCCESS; { 返回成功说明至少有一个线程在运行 } end else begin Result := STATUS_UNSUCCESSFUL; { 无法启动任何线程 } end; end; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; var status:NTSTATUS; pDeviceObject:PDEVICE_OBJECT; liTickCount:LARGE_INTEGER; begin status := STATUS_DEVICE_CONFIGURATION_ERROR; RtlInitUnicodeString(@g_usDeviceName, '\Device\MutualExclusion'); RtlInitUnicodeString(@g_usSymbolicLinkName, '\DosDevices\MutualExclusion'); if IoCreateDevice(pDriverObject, 0, @g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, False, @pDeviceObject) = STATUS_SUCCESS then begin if IoCreateSymbolicLink(@g_usSymbolicLinkName, @g_usDeviceName) = STATUS_SUCCESS then begin { 因为ThreadProc存在于我们的驱动主体中,因此只要还有一个 { 线程在运行,就不能卸载驱动,必须要等到所有的线程都退出 { 方可卸载驱动。为了达到这个目的,我们需要一些内存。必须 { 在这里分配这些内存,因为如果我们在DriverUnload中去分配, { 一旦分配失败,就没有办法停止驱动了。} { 每个线程对象都有一个内建的等待块(wait block)数组(缺省一 { 个数组中有3个等待块)用于有几个对象并存时的等待操作。由于 { 没有额外的等待块可供使用,因此通常情况下,这些内建的等待 { 块数组被用于多个等待操作。当然,如果需要并存的对象的数目 { 超过了内建等待块的数目, 可以通过设置WaitBlockArray参数指 { 定一个替代的等待块用于等待操作。} { 在本例中,我们的NUM_THREADS大于THREAD_WAIT_OBJECTS(3). } { 因此就必须要使用自己的Wait Block了. } g_pkWaitBlock := ExAllocatePool(NonPagedPool, NUM_THREADS * SizeOf(KWAIT_BLOCK)); if g_pkWaitBlock <> nil then begin { 初始化mutex } KeInitializeMutex(@g_kMutex, 0); { 出于性能方面的考虑, 可以使用Ex..FastMutex函数替代 { Ke..Mutex. 当然, 快速mutex无法递归取得而内核mutex { 却可以;另一个缺点是ExAcquireFastMutex设置IRQL=APC_LEVEL, { 并且调用ExAcquireFastMutex返回后,调用者函数也将运行在 { APC_LEVEL. } KeQueryTickCount(@liTickCount); { 初始化随机数发生器种子 } srand(liTickCount.LowPart); g_dwWorkElement := 0; if StartThreads = STATUS_SUCCESS then begin pDriverObject^.DriverUnload := @DriverUnload; status := STATUS_SUCCESS; end else begin CleanUp(pDriverObject); end; end else begin CleanUp(pDriverObject); DbgPrint('MutualExclusion: Couldn''t allocate memory for Wait Block'#13#10); end; end else begin IoDeleteDevice(pDeviceObject); end; end; result := status; end; end.
代码:
g_pkWaitBlock := ExAllocatePool(NonPagedPool, NUM_THREADS * SizeOf(KWAIT_BLOCK));
代码:
KeInitializeMutex(@g_kMutex, 0);
代码:
KMUTANT = packed record Header:TDispatcherHeader; MutantListEntry:TListEntry; OwnerThread:PKThread; Abandoned:Boolean; ApcDisable:Byte; Alignment0:Word; end;
代码:
KeQueryTickCount(@liTickCount);
代码:
g_dwWorkElement := 0;
11.3 创建线程
代码:
i := 0; { dwCount保存实际运行的线程数 } dwCount := 0; while i < NUM_THREADS do begin { 启动NUM_THREADS个线程 } rtnCode := PsCreateSystemThread(@hThread, THREAD_ALL_ACCESS, nil, 0, nil, @ThreadProc, nil); if rtnCode = STATUS_SUCCESS then begin { 我们不需要PsCreateSystemThread返回的线程句柄. } { 但是我们需要指向它的指针. 以便我们引用线程对象并且关闭它. } ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, nil, KernelMode, @g_apkThreads[dwCount], nil); ZwClose(hThread); DbgPrint('MutualExclusion: System thread created. Thread Object: %08X'#13#10, g_apkThreads[dwCount]); Inc(dwCount); end else begin DbgPrint('MutualExclusion: Can''t create system thread. Status: %08X'#13#10, rtnCode); end; Inc(i); end; g_dwCountThreads := dwCount;
11.4 线程函数ThreadProc
我们创建了NUM_THREADS个线程,这些线程的线程函数最终都会获得执行的权限,其ThreadProc函数将会执行。
代码:
pkThread := PsGetCurrentThread; DbgPrint('MutualExclusion: Thread %08X is entering ThreadProc'#13#10, pkThread);
代码:
dwCount := 0; while dwCount < NUM_WORKS do begin
首先调用KeWaitForMutexObject竞争 Mutex,一旦取得Mutex,线程开始运行,并可以自由的操作共享资源,其余NUM_THREADS-1个线程将处于等待状态,直到这个线程释放Mutex。
为了模拟线程对共享资源的访问,我们简单地随机停止线程执行050毫秒。
代码:
liDelayTime.HighPart := liDelayTime.HighPart or -1; liDelayTime.LowPart := -(rand shl 4); KeDelayExecutionThread(KernelMode, false, @liDelayTime);
代码:
Inc(dwWorkElement); g_dwWorkElement := dwWorkElement;
代码:
KeReleaseMutex(@g_kMutex, False);
代码:
function KeReleaseMutex(Mutex:PKMutex; Wait:LongBool):LONG; stdcall;
代码:
Result := PsTerminateSystemThread(STATUS_SUCCESS);
11.5 DriverUnload
与TimerWorks驱动类似,我们必须要等待所有线程结束方可卸载驱动,因为所有线程都位于驱动主体中,如果我们在不合适的时候卸载驱动可能导致系统崩溃。解决方法就是等待所有的线程结束。
代码:
if g_dwCountThreads > 0 then begin { 没有设置超时时间,一直等待到所有线程退出 } { 因为有多个线程对象,所以使用KeWaitForMultipleObjects } KeWaitForMultipleObjects(g_dwCountThreads, @g_apkThreads, WaitAll, Executive, KernelMode, False, nil, g_pkWaitBlock);
代码:
while g_dwCountThreads > 0 do begin Dec(g_dwCountThreads); ObfDereferenceObject(g_apkThreads[g_dwCountThreads]); end;
代码:
DbgPrint('MutualExclusion: WorkElement = %d'#13#10, g_dwWorkElement);