3.3.1 事件对象
使用事件对象是一种常见的同步方法。驱动程序可以使用事件对象同步完成IRP,或者驱动线程间同步执行某些操作。
要使用事件对象,首先需要调用KeInitializeEvent, IoCreateNotificationEvent或者IoCreateSynchronizationEvent初始化事件。KeInitializeEvent函数原型:
VOID KeInitializeEvent( IN PRKEVENT Event, IN EVENT_TYPE Type, IN BOOLEAN State);
3.3.2 信号灯对象
任何驱动程序都可以使用信号灯对象在驱动线程和处理例程间同步I/O操作。例如:当驱动程序没有未决的IRP时,驱动程序的某个线程处于等待状态;当驱动程序的IRP处理例程接受到IRP并排到未决的IRP队列中后,将增加一个信号灯,等待的线程满足等待条件,进入处理流程。
在使用信号灯之前,必须调用KeInitializeSemaphore函数初始化信号灯。KeInitializeSemaphore函数原型:
VOID KeInitializeSemaphore( IN PRKSEMAPHORE Semaphore, IN LONG Count, IN LONG Limit);
3.3.3 互斥体对象
互斥体对象是一种确保对共享资源互斥访问的同步机制。
在使用互斥体对象之前,要首先调用KeInitializeMutex函数初始化互斥体对象。KeInitializeMutex函数原型:
VOID KeInitializeMutex(IN PRKMUTEX Mutex, IN ULONG Level);
完全释放互斥体后互斥体对象才会有信号KeReleaseMutex
3.3.4 定时器对象
驱动程序可以使用定时器对象执行一个定时的操作。定时器对象包括DPC定时器和I/O定时器。I/O定时器的定时固定为1秒,不可变更;而DPC定时器的定时可以指定,更加灵活。
在使用DPC定时器之前,要首先调用KeInitializeTimer或者KeInitializeTimerEx函数初始化定时器对象。
KeInitializeTimer函数原型:VOID KeInitializeTimer(IN PKTIMER Timer);
KeInitializeTimerEx函数原型:VOID KeInitializeTimerEx(IN PKTIMER Timer, IN TIMER_TYPE Type); Type指定为NotificationTimer或SynchronizationTimer
DPC定时器典型使用方法:
1. 驱动程序的一个线程调用KeSetTimer设置定时器。KeSetTimer原型为:BOOLEAN KeSetTimer(IN PKTIMER Timer, IN LARGE_INTEGER DueTime, IN PKDPC Dpc OPTIONAL);
Timer为DPC定时器对象;DueTime指定了定时的时间;Dpc指定了一个DPC对象,为可选参数,如果设置一个DPC,那么DueTime时间到了以后。将执行该DPC;如果不指定DPC,那么该定时器对象功能表现为延迟执行,类似与KeDelayExecutionThread。
2. 驱动线程接着调用KeWaitForSignalObject等待定时器对象有信号
3. DueTime时间已过
4. 内核从定时器队列中出列一个定时器,将其设置为有信号,并将原等待线程由等待状态更改为准备执行状态
5. 内核执行线程分发,线程开始执行。
//
//头文件
//
#include "ntddk.h"
//
//驱动入口函数
//
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
HANDLE hThread;
OBJECT_ATTRIBUTES ObjectAttributes;
CLIENT_ID CID;
NTSTATUS status;
KdPrint(("DriverEntry Cur Process:%s Cur IRQL:%d\n",
(char*)((ULONG)IoGetCurrentProcess()+0x174),KeGetCurrentIrql()));
//初始化定时器
KeInitializerTimer(&Timer);
DriverObject->DriverUnload = SyncTechUnload;
InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
//创建一个系统线程
status = PsCreateSystemThread(
&hThread, GENERIC_READ | GENERIC_WRITE,
&ObjectAttributes, NtCurrentProcess(),
&CID, (PKSTART_ROUTINE) ThreadStart, NULL);
if (!NT_SUCESS(status))
{
KdPrint(("PsCreateSystenThread Failure!\n"));
return 0;
}
ZwClose(hThread);
KdPrint(("exit\n"));
return STATUS_SUCCESS;
}
//
//线程设置Timer等候执行DPC
//
VOID ThreadStart(__in PVOID StartContext)
{
LARGE_INTEGER DueTime;
KDPC Dpc;
KdPrint(("Cur Process: %s IRQL:%d\n",
(char*)((ULONG)IoGetCurrentProcess()+0x174),
KeGetCurrentIrql()));
DueTime = RtlConvertLongToLargeInteger(-1000000);
//初始化一个DPC
KeInitializeDpc(&Dpc,(PKDEFERRED_ROUTINE)CustomDpc,NULL);
//设置DPC定时器
KeSetTime(&Timer,DueTime,&Dpc);
//等待定时器
KeWaitForSignalObject(&Timer,Executive,KernelMode,FALSE,NULL);
KdPrint(("ThreadStart time expire!\n"));
return;
}
//
//简单输出进程名和当前的IRQL,注意该函数运行在dispatch级别
//
VOID CustonDpc(
__in struct _KDPC *Dpc,
__in_opt PVOID DeferredContext,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2)
{
KdPrint(("CustomDpc Process: %s IRQL:%d\n",
(char*)((ULONG)IoGetCurrentProcess()+0x174),//使用了硬编码
KeGetCurrentIrql()));
return;
}
上述代码中,驱动程序创建了一个系统线程,在此系统线程中设置了一个定时器,该定时器绑定了一个DPC,如果设置的时间到了就会激发DPC的交付
3.3.5 自旋锁
自旋锁是一种内核模式专用的同步机制。使用自旋锁可以在并行访问时保护共享数据及资源。
使用前调用KeInitializeSpinLock函数初始化自旋锁,调用KeAcquireSpinLock获取自旋锁,在访问完共享数据后调用函数KeReleaseSpinLock函数释放自旋锁。
3.3.6 回调对象
内核的回调机制为驱动程序提供了一种通用的通知方法:当某条件满足时,驱动程序A向驱动程序B通知某一消息。驱动程序A首先需要创建一个回调对象,其它的驱动程序(如驱动程序B)可以向该回调对象注册回调函数,当条件满足时,驱动程序A将调用所以向其注册的回调函数。创建一个回调对象:
1. 首先需求调用InitializeObjectAttributes函数初始回调对象的属性,注意必须为回调对象赋予名字,而且Attributes参数必须设置OBJ_PERMANENT标记,
2. 然后调用ExCreateCallback函数创建回调对象,ExCreateCallback函数原型如下:
NTSTATUS ExCreateCallback(OUT PCALBACK_OBJECT *CallbackObject, IN POBJECT_ATTRIBUTES ObjectAttributes, IN BOOLEAN Create, IN BOOLEAN ALLowMultipleCallbacks);
3. 其它驱动向该回调对象注册回调函数,这个回调函数在条件满足时被调用。注册回调函数使用的函数是ExRegisterCallback,函数原型如下:
PVOID ExRegisterCallback(IN PCALLBACK_OBJECT CallbackObject, IN PCALLBACK_FUCTION CallbackFunction, IN PVOID CallbackContext);
其中VOID (*PCALLBACK_FUNCTION)(IN PVOID CallbackContext, IN PVOID Argument1, IN PVOID Argument2);
//Callback驱动程序//
//
//头文件
//
#include "ntddk.h"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNICODE_STRING DeviceName,Win32Device;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS status;
unsigned i;
UNICODE_STRING DemoCallback;
OBJECT_ATTRIBUTES oa;
RtlInitializeUnicodeString(&DeviceName,L"\\Device\\Callback0");
RtlInitializeUnicodeString(&Win32Device,L"\\DosDevices\\Callback0");
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = CallbackDefaultHandler;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = CallbackCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CallbackCreateClose;
DriverObject->DriverUnload = CallbackUnload;
status = IoCreateDevice(DriverObject,
0,&DeviceName,FILE_DEVICE_UNKNOWN,0,&FALSE,&DeviceObject);
if (!NT_SUCCESS(status))
{
return status;
}
if (!DeviceObject)
{
return STATUS_UNEXPECTED_IO_ERROR;
}
DeviceObject->Flags |= DO_DIRECT_IO;
status = IoCreateSymbolicLink(&Win32Device,&DeviceName);
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
//创建一个回调对象
//注意回调对象都放在\Callback目录中
RtlInitUnicodeString(&DemoCallback,L"\\Callback\\DemoCallback");
//注意一定要指定OBJ_PERMANENT,否则ExCreateCallback无法成功创建一个带有名字的回调函数
InitializeObjectAttributes(
&oa,&DemoCallback,
OBJ_CASE_INSENSITIVE|OBJ_PERMANENT,
NULL,NULL);
status = ExCreateCallback(&pCallback, &oa, TRUE, TRUE);
if (!NT_SUCCESS(status))
{
KdPrint(("Callback ExCreateCallback Failure!status: 0x%08x\n",status));
}
KdPrint(("Callback --pCallback: 0x%08x\n",pCallback));
return STATUS_SUCCESS;
}
//
//当有程序打开或者关闭本驱动的时候,将通知所以向回调对象注册的函数
//
NTSTATUS CallbackCreateClose(IN PDEVIE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION pSP = IoGetCurrentIrpStackLoaction(Irp);
char* pImageName = NULL;
if (pCallback)
{
//获取进程名,使用硬编码:x174
pImageName = (char*)((ULONG)PsGetCurrentProcess()+0x174);
//
//通知注册函数
//arg1:主功能码(IRP_MJ_READ或者IRP_MJ_WRITE))
//arg2:当前进程的进程名
//
ExNotifyCallback(pCallback,(PVOID)pSP->MajorFunction,pImageName);
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//Client驱动程序//
//
//头文件
//
#include "ntddk.h"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
UNICODE_STRING DemoCallback;
OBJECT_ATTRIBUTES oa;
DriverObject->DriverUnload = ClientUnload;
do
{
//打开回调对象
RtlInitUnicodeString(&DemoCallback,L"\\Callback\\DemoCallback");
InitializeObjectAttributes(
&oa, &DemoCallback,
OBJ_CASE_INSENSITIVE|OBJ_PERMANENT,
NULL,NULL);
//指定ExCreateCallback的Create参数为FALSE,即打开回调对象而不是创建
status = ExCreateCallback(&pCallback,&oa,FALSE,TRUE);
if (!NT_SUCCESS(status))
{
KdPrint(("Client:ExCreateCallback Failure!Status:0x%08x\n",
status));
break;
}
KdPrint(("Client --pCallback:0x%08x\n",pCallback));
//向该回调对象注册一个回调函数
pRegisterHandle = ExRegisterCallback(
pCallback, (PCALLBACK_FUNCTION)CallbackFunction, NULL);
if (NULL == pRegisterHandle)
{
KdPrint(("Client:ExRegisterCall Failure!\n"));
ObDereferenceObject(pCallback);
break;
}
//不需要回调函数时,减少引用次数(减少到0时,将会被删除)
ObDereferenceObject(pCallback);
} while ();
return STATUS_SUCCESS;
}
//
//回调函数
//当有程序打开或者关闭Callback驱动程序时,Callback驱动将调用此函数
//本函数只是简单地输出Argument1和Argument2。Argument1和Argument2的
//语义由创建回调对象的驱动程序(本例中即Callback)定义
//Argument1:IRP的主功能码(IRP_MJ_CREATE或者IRP_MJ_CLOSE)
//Argument2:打开或关闭callback驱动的进程名
//
VOID CallbackFunction(
IN PVOID CallbackContext,
IN PVOID Argument1,
IN PVOID Argument2)
{
KdPrint(("Client CallbackFunction--Argument1:%s Argument:%s\n",
((LONG)Argument1 == IRP_MJ_CREATE)?"IRP_MJ_CREATE":(((LONG)Argument1 == IRP_MJ_CLOSE)?
"IRP_MJ_CLOSE":"exception!"),Argument2));
return;
}
//CallbackUser应用程序//
#include <windows.h>
int main()
{
HANDLE hFile;
hFile = CreateFileA(
"\\\\.\\Callback0",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
printf("CreateFile Failure!err:%d\n",
GetLastError());
return 0;
}
CloseHandle(hFile);
return 0;
}
加载Callback驱动程序,创建一个回调对象;加载Client驱动程序,向回调对象注册回调函数;最后执行CallbackUser,打开和关闭Callback驱动程序,从而触发回调条件,Callback驱动程序将通知所以向回调对象注册的回调函数
3.3.7 原子操作
原子操作保证代码执行的原子性,不会被打断。常用的原子操作包括:InterlockedIncrement、InterlockedDecrement、InterlockedCompareExchang等