3.1.5 同步处理

Windows是多任务抢占式的操作系统,如果多个线程要求操作同一个资源,这时就需要同步处理了。如果没有同步机制的控制,所有的线程将会任意运行。如果驱动程序没有处理好同步问题,会出现程序出错、操作系统性能下降甚至死锁等现象。

下面将简要介绍在WinPcap中所涉及的同步处理机制。

1.用户模式下的等待

在应用程序中,可以使用WaitForSingleObject函数等待一个同步对象,其原型如下:


DWORD WaitForSingleObject(

HANDLE hHandle,//同步对象句柄

DWORD dwMilliseconds//等待时间

);


参数dwMilliseconds是等待的超时时间,单位为毫秒(ms)。

同步对象有两种状态,一种是激发状态,一种是未激发状态。如果同步对象处于未激发状态,WaitForSingleObject则进入休眠状态,等待同步对象被激发。如果同步对象在指定的等待时间内,还是没有处于激发状态,则自动停止休眠。dwMilliseconds也可以设定为INFINITE,表示无限期地等待下去,直到等待的同步对象被激发为止。另外,dwMilliseconds也可以为0,其作用是强迫操作系统从当前线程上切换到其他线程上。

2.用户模式下的事件

事件是一种典型的同步对象。用户模式下的事件和内核模式的事件对象紧密相连。在使用事件之前,需要对事件进行初始化,可使用CreateEvent函数完成这项任务,该函数的原型如下:


HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes,//安全属性

BOOL bManualReset,//复位方式

BOOL bInitialState,//初始化状态

LPCTSTR lpName//对象名称

);


CreateEvent函数会使操作系统创建一个内核事件对象,此函数返回的句柄就代表了这个内核事件对象。一般情况下,CreateEvent函数的安全属性会设置为NULL。它的第二个参数bManualReset表示创建的事件是否是手动模式。如果是手动模式的事件,事件处于激发状态后,则需要手动设置才能回到未激发状态;如果是自动模式,在事件处于激发状态后,遇到任意一个等待(如WaitForSingleObject)操作,则自动变回未激发状态。

下面为WinPcap中创建一个事件的代码:


HANDLE hEvent;

hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);


3.用户模式下的互斥体

互斥体是一种常用的同步对象,可以避免多个线程争夺同一个资源。也就是说,多线程环境中只能有一个线程占有互斥体。获得互斥体的线程如果不释放互斥体,其他线程永远不会获得该互斥体。同一个线程可以递归获得互斥体,递归获得互斥体的意思是,得到互斥体的线程还可以再次获得这个互斥体,也就是说互斥体对于已经获得互斥体的线程不产生“互斥”作用。

互斥体也有两种状态,激发态和未激发态。如果线程获得互斥体时的状态是未激发态,当释放互斥体时,互斥体的状态则为激发态。

初始化互斥体的函数是CreateMutex,其原型如下;


HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全属性

BOOL bInitialOwner,//初始化的占有状态

LPCTSTR lpName//对象名称

);


上述函数中第二个参数bInitialOwner表示互斥体初始化时是否被占有。如果没有被占有,为激发态,否则为未激发态。

获得互斥体的函数是WaitForSingleObject,而释放互斥体的函数是ReleaseMutex。

下面为WinPcap中使用互斥体的代码:


//原型互斥体

HANDLE g_AdaptersInfoMutex=NULL;

……

//初始化互斥体

g_AdaptersInfoMutex=CreateMutex(NULL,FALSE,NULL);

……

//获得互斥体事件

WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);

……

//释放互斥体

ReleaseMutex(g_AdaptersInfoMutex);


4.自旋锁

自旋锁是一种同步处理机制,它能保证某个资源只能被一个线程所拥有,可用于驱动程序的同步处理。自旋锁的作用一般是使各个派遣函数之间同步。

初始化的自旋锁处于解锁状态时,它可以被程序获取。获取后的自旋锁处于锁住状态,不能被再次获取。锁住的自旋锁必须被释放后,才能再次被获取。如果自旋锁已被锁住,这时有程序申请获取这个自旋锁,则程序会处于“自旋”状态。所谓自旋状态,就是不停地询问是否可以获取自旋锁。

自旋锁不同于线程中的等待事件。在线程中如果等待某个事件,操作系统会使这个线程进入休眠状态,CPU会运行其他的线程。自旋锁则不同,CPU不会切换到别的线程,而是让这个线程一直“自旋”等待。因此,对自旋锁的占用时间不宜过长,否则会导致申请自旋锁的其他线程长时间处于自旋,浪费CPU的处理时间。

NDIS库使用NDIS_SPIN_LOCK数据结构表示自旋锁。使用自旋锁前,首先要对其初始化,可使用NdisAllocateSpinLock函数进行。申请获得自旋锁可使用NdisAcquireSpinLock函数,释放自旋锁可使用NdisReleaseSpinLock函数。这些函数的原型如下:


VOID NdisAllocateSpinLock(IN PNDIS_SPIN_LOCK SpinLock);

VOID NdisAcquireSpinLock(IN PNDIS_SPIN_LOCK SpinLock);

VOID NdisReleaseSpinLock(IN PNDIS_SPIN_LOCK SpinLock);


下面为WinPcap驱动程序中使用自旋锁的代码:


NDIS_SPIN_LOCK WriteLock;//_OPEN_INSTANCE结构体中的自旋锁字段

NdisAllocateSpinLock(&Open->WriteLock);//初始化自旋锁

NdisAcquireSpinLock(&Open->WriteLock);//申请获得自旋锁

if(Open->WriteInProgress)

{

NdisAcquireSpinLock(&Open->WriteLock);//释放自旋锁

SET_FAILURE_UNSUCCESSFUL();

break;

}

else

{

Open->WriteInProgress=TRUE;

}

NdisAcquireSpinLock(&Open->WriteLock);//释放自旋锁


5.NDIS库提供的事件

NDIS库提供的事件用NDIS_EVENT数据结构表示。使用事件前,首先要对其初始化,可使用NdisInitializeEvent函数完成。另外,可以使用NdisWaitEvent函数把调用者置为等待状态,直到给定事件为激发态或等待超时;也可使用NdisSetEvent函数把一个给定的事件设为激发态;还可使用NdisResetEvent函数清除一个给定事件的状态。各函数的原型如下:


VOID NdisInitializeEvent(IN PNDIS_EVENT Event);

BOOLEAN NdisWaitEvent(IN PNDIS_EVENT Event,IN UINT MsToWait);

VOID NdisSetEvent(IN PNDIS_EVENT Event);

VOID NdisResetEvent(IN PNDIS_EVENT Event);


下面为WinPcap中使用NDIS库事件的代码:


//_OPEN_INSTANCE结构体中的事件字段

NDIS_EVENT NdisWriteCompleteEvent;

NTSTATUS NPF_Open(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

……

//初始化事件

NdisInitializeEvent(&Open->NdisWriteCompleteEvent);

……

}

NTSTATUS NPF_Write(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

……

//清除事件的状态

NdisResetEvent(&Open->NdisWriteCompleteEvent);

……

//等待事件

NdisWaitEvent(&Open->NdisWriteCompleteEvent,0);

……

}

VOID NPF_SendComplete(IN NDIS_HANDLE ProtocolBindingContext,

IN PNDIS_PACKET pPacket,IN NDIS_STATUS Status)

{

……

//把事件设为激发态

NdisSetEvent(&Open->NdisWriteCompleteEvent);

……

}


6.变量的原子操作

内核提供了对一个变量进行原子操作的函数,诸如对变量原子进行递增、递减操作,或者设置指定的值。

对一个变量进行原子递增操作时,可使用InterlockedIncrement函数,其原型如下:


LONG InterlockedIncrement(IN PLONG Addend);


上述函数中,参数Addend指向一个LONG型变量,函数返回递增后的值。

对一个变量进行原子递减操作时,可使用InterlockedDecrement函数,函数原型如下:


LONG InterlockedDecrement(IN PLONG Addend);


上述函数中,参数Addend指向一个LONG型变量,函数返回递减后的值。

对一个变量进行原子设置指定值的操作时,可使用InterlockedExchange函数,其原型如下:


LONG InterlockedExchange(IN OUT PLONG Target,IN LONG Value);


上述函数中,参数Target指向一个需要进行设置的LONG型变量;参数Value为需要设置的值。InterlockedExchange函数返回参数Target所指的初始值。

下面为WinPcap中使用原子操作的代码:


//申明需要原子操作的变量

ULONG g_NumOpenedInstances=0;

NTSTATUS NPF_Open(IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp)

{

……

//原子递增

localNumOpenedInstances=

InterlockedIncrement(&g_NumOpenedInstances);

……

}

NTSTATUS NPF_Cleanup(IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp)

{

……

//原子递减

localNumOpenInstances=

InterlockedDecrement(&g_NumOpenedInstances);

……

}