首页
社区
课程
招聘
[原创]Delphi驱动开发研究之内核同步对象—Mutex
发表于: 2010-1-17 16:29 9197

[原创]Delphi驱动开发研究之内核同步对象—Mutex

2010-1-17 16:29
9197

在上篇教程中,我们讲解了内核同步对象中的计时器对象的使用方法,有关同步的另一个常见的用法就是对数据的独占访问。
        在本教程中,我们将同时启动多个线程,所有这些线程都会数次对一个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);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (4)
雪    币: 89
活跃值: (185)
能力值: ( LV9,RANK:270 )
在线值:
发帖
回帖
粉丝
2
传说中的沙发
2010-1-17 19:01
0
雪    币: 109
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
学习~~~~~~~
2010-1-18 14:59
0
雪    币: 270
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
一直很欣赏楼主对D的坚持,支持楼主呀!
2010-1-19 13:05
0
雪    币: 163
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
楼主万岁,希望一直走下去
2010-12-15 15:50
0
游客
登录 | 注册 方可回帖
返回
//