在上篇教程中,我们讲解了内核同步对象中的计时器对象的使用方法,有关同步的另一个常见的用法就是对数据的独占访问。
在本教程中,我们将同时启动多个线程,所有这些线程都会数次对一个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;
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;
pkThread := PsGetCurrentThread;
DbgPrint('MutualExclusion: Thread %08X is entering ThreadProc'#13#10, pkThread);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课