{更新系统时间到共享内存} procedure UpdateTime; stdcall; var SysTime:LARGE_INTEGER; begin KeQuerySystemTime(@SysTime); ExSystemTimeToLocalTime(@SysTime, g_pSharedMemory); end;
procedure TimerRoutine(pDeviceObject:PDEVICE_OBJECT; pContext:PVOID); stdcall; begin UpdateTime; end;
{清理过程--释放资源} procedure Cleanup(pDeviceObject:PDEVICE_OBJECT); stdcall; begin if g_fTimerStarted then begin IoStopTimer(pDeviceObject); DbgPrint('SharingMemory: Timer stopped'#13#10); end;
if (g_pUserAddress <> nil) and (g_pMdl <> nil) then begin MmUnmapLockedPages(g_pUserAddress, g_pMdl); DbgPrint('SharingMemory: Memory at address %08X unmapped'#13#10, g_pUserAddress); g_pUserAddress := nil; end;
if g_pMdl <> nil then begin IoFreeMdl(g_pMdl); DbgPrint('SharingMemory: MDL at address %08X freed'#13#10, g_pMdl); g_pMdl := nil; end;
if g_pSharedMemory <> nil then begin ExFreePool(g_pSharedMemory); DbgPrint('SharingMemory: Memory at address %08X released'#13#10, g_pSharedMemory); g_pSharedMemory := nil; end; end;
function DispatchCleanup(pDeviceObject:PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall; begin DbgPrint(#13#10'SharingMemory: Entering DispatchCleanup'#13#10); Cleanup(pDeviceObject);
代码:g_pSharedMemory := ExAllocatePool(NonPagedPool, PAGE_SIZE); if g_pSharedMemory <> nil then begin 获得了控制代码IOCTL_GIVE_ME_YOUR_MEMORY,我们来从非分页内存中分配一个内存页。驱动应该将这部分内存映射到用户进程的地址空间中,在接收到请求的进程上下文中,即在我们的应用程序的地址空间中。使用非分页内存以及使用一个内存页的原因后面会介绍到。 ExAllocatePool返回系统空间中的地址,也就是说驱动程序是与当前上下文无关的。现在需要将这块内存映射到这个进程的地址空间中去,使之被共享。我们的驱动程序是单层的,所以对IRP_MJ_DEVICE_CONTROL的处理我们想放在我们应用程序的地址上下文中。在我们将分配的一个内存页映射到进程地址空间之前必须先分配MDL(Memory Descriptor List。) MDL是一个结构体,用于描述一片内存区域中的物理内存页。其定义如下:
代码:g_pUserAddress := MmMapLockedPagesSpecifyCache(g_pMdl, UserMode, MmCached, nil, 0, NormalPagePriority); if g_pUserAddress <> nil then begin DbgPrint('SharingMemory: Memory mapped into user space at address %08X'#13#10, g_pUserAddress); pSystemBuffer := p_Irp^.AssociatedIrp.SystemBuffer; PVOID(pSystemBuffer^) := g_pUserAddress; MmMapLockedPagesSpecifyCache返回我们的内存页映射到用户空间中的地址。我们将这个地址传递到应用程序中。从这一刻起该内存页就成为共享的了,并且驱动程序对其的使用不依赖于当前的地址上下文,而用户进程也能以自己的地址来访问。 为了直观起见,函数UpdateTime将把当前系统时间放在我们的内存页中。
代码:{更新系统时间到共享内存} procedure UpdateTime; stdcall; var SysTime:LARGE_INTEGER; begin KeQuerySystemTime(@SysTime); ExSystemTimeToLocalTime(@SysTime, g_pSharedMemory); end; KeQuerySystemTime取得的是格林威治时间。再用ExSystemTimeToLocalTime将其转换为本地时间。
代码:if IoInitializeTimer(p_DeviceObject, @TimerRoutine, @dwContext) = STATUS_SUCCESS then Begin IoInitializeTimer初始化内核Timer,Timer将与设备对象建立关联。DEVICE_OBJECT结构体中有一个Timer域,其中有指向IO_TIMER结构体的指针。函数IoInitializeTimer的第一个参数定义了Timer要和哪一个设备对象关联。第二个参数是一个指向系统启用Timer时要调用的函数的指针。TimerRoutine函数将调用UpdateTime,在我们的内存页中更新系统时间。TimerRoutine运行在IRQL = DISPATCH_LEVEL(DDK中有记载)。这就是我们使用非分页内存的第一个也是最主要的原因。IoInitializeTimer的最后一个参数是一个指向任意数据的指针。这个指针将被传递到TimerRoutine中。我们这里不需要指定这个值,所以只是随便虚构一个变量。
代码:if p_Irp^.IoStatus.Status <> STATUS_SUCCESS then begin DbgPrint('SharingMemory: Something went wrong:'#13#10); Cleanup(p_DeviceObject); end; 如果上述各阶段有一个发生问题,就要收回资源。
代码:{清理过程--释放资源} procedure Cleanup(pDeviceObject:PDEVICE_OBJECT); stdcall; begin if g_fTimerStarted then begin IoStopTimer(pDeviceObject); DbgPrint('SharingMemory: Timer stopped'#13#10); end;
if (g_pUserAddress <> nil) and (g_pMdl <> nil) then begin MmUnmapLockedPages(g_pUserAddress, g_pMdl); DbgPrint('SharingMemory: Memory at address %08X unmapped'#13#10, g_pUserAddress); g_pUserAddress := nil; end;
if g_pMdl <> nil then begin IoFreeMdl(g_pMdl); DbgPrint('SharingMemory: MDL at address %08X freed'#13#10, g_pMdl); g_pMdl := nil; end;
if g_pSharedMemory <> nil then begin ExFreePool(g_pSharedMemory); DbgPrint('SharingMemory: Memory at address %08X released'#13#10, g_pSharedMemory); g_pSharedMemory := nil; end; end;