5,KeAcquireInStackQueuedSpinLock的实现机制
在单核处理器(WindowsXP)下
观察KeAcquireInStackQueuedSpinLock的实现
hal!KeAcquireInStackQueuedSpinLock:
mov eax,dword ptr ds:[FFFE0080h]
shr eax,4
mov al,byte ptr hal!HalpVectorToIRQL [eax]
mov dwordptr ds:[0FFFE0080h],41h
mov byte ptr [edx+8],al
ret
和KeReleaseSpinLock的实现基本上一样的,只不过将原来的IRQL保存在了LockHandle里。
而KeAcquireInStackQueuedSpinLock就是将IRQL降为原来的值了。
在多核处理器(Windows2003)下
仔细比对WRK上的代码和Windbg上的汇编代码,除了一个标志位上有不同外其他实现是一样的。
本来希望对这些汇编代码做一些解释,但是解释来解释去,发现远不如直接用WRK的代码简洁明了。
以下将直接分析WRK上的实现。
下面代码直接复制于WRK
typedef struct _KSPIN_LOCK_QUEUE {
struct_KSPIN_LOCK_QUEUE * volatile Next;
PKSPIN_LOCKvolatile Lock;
}
typedef struct _KLOCK_QUEUE_HANDLE {
KSPIN_LOCK_QUEUE LockQueue;
KIRQLOldIrql;
}
KeAcquireInStackQueuedSpinLock (PKSPIN_LOCK SpinLock, PKLOCK_QUEUE_HANDLE LockHandle);
{
LockHandle->LockQueue.Lock = SpinLock;
LockHandle->LockQueue.Next = NULL;
LockHandle->OldIrql =KfRaiseIrql(DISPATCH_LEVEL);
KxAcquireQueuedSpinLock(&LockHandle->LockQueue,SpinLock);
return;
}
KxAcquireQueuedSpinLock (PKSPIN_LOCK_QUEUE LockQueue,PKSPIN_LOCKSpinLock )
{
PKSPIN_LOCK_QUEUE TailQueue;
TailQueue = InterlockedExchangePointer((PVOID *)SpinLock,LockQueue);
if (TailQueue != NULL) {
KxWaitForLockOwnerShip(LockQueue, TailQueue);
}
return;
}
ULONG64 KxWaitForLockOwnerShip(PKSPIN_LOCK_QUEUELockQueue,PKSPIN_LOCK_QUEUE TailQueue)
{
ULONG64SpinCount;
*((ULONG64volatile *)&LockQueue->Lock) |=LOCK_QUEUE_WAIT;
TailQueue->Next = LockQueue;
SpinCount =0;
do {
__asm { rep nop }
} while((*((ULONG64 volatile*)&LockQueue->Lock)& LOCK_QUEUE_WAIT) != 0);
KeMemoryBarrier();
returnSpinCount;
}
下面将分析KeAcquireInStackQueuedSpinLock如何获访问互斥的资源。
如图所示,LockHandle是在栈上建立的的数据。当线程1首次进入InStackQueuedSpinLock时,初始时*SpinLock的值为NULL,因此TailQueue的值也为NULL。SpinLock通过InterlockedExchangePointer被置成为指向栈上的LockHandle。线程1进入被InStackQueuedSpinLock保护的资源。
当线程1还在占用状态,线程2准备获得InStackQueuedSpinLock时,由于线程1已经将SpinLock的值置成为线程1上的LockHandle,线程2获得线程1的LockHandle,这时线程2的TailQueue指向线程1的LockHandle,不为空,线程2进入等待状态。
线程2进入等待状态后,先将自己的lock置成等待状态,然后将线程1的LockHandle的next指针置成为指向自己的LockHandle,完成将自己栈上的节点插入链表。
同样,当其他线程准备获得InStackQueuedSpinLock时,将会在自己的栈上建立节点,并插入到线程2的节点之后。
这就是KeAcquireInStackQueuedSpinLock的名称的由来了,InStack是指在栈上建立节点,Queued就是将这些节点形成链表了。
算法KeAcquireInStackQueuedSpinLock描述为:
1, 链表的表尾保存在SpinLock中。初始值为NULL。将IRQL升级为DISPATCH_LEVEL,
LockQueue->Lock置为SpinLock。
2, 当线程准备获得资源时,执行如下独占处理器和相关存储空间操作:
1> 保存SpinLock到TailQueue
2> 将SpinLock指向当前栈上节点
3, 如果TailQueue为空,则直接获得资源。
4, 如果TailQueue不为空,LockQueue->Lock置为LOCK_QUEUE_WAIT,
然后TailQueue->Next置成当前节点,等待LockQueue->Lock的值不为LOCK_QUEUE_WAIT。
下面分析释放InStackQueuedSpinLock
KeReleaseInStackQueuedSpinLock (PKLOCK_QUEUE_HANDLELockHandle)
{
KxReleaseQueuedSpinLock(&LockHandle->LockQueue);
KeLowerIrql(LockHandle->OldIrql);
return;
}
KxReleaseQueuedSpinLock (PKSPIN_LOCK_QUEUE LockQueue)
{
PKSPIN_LOCK_QUEUENextQueue;
NextQueue =ReadForWriteAccess(&LockQueue->Next);
if(NextQueue == NULL) {
if (InterlockedCompareExchangePointer((PVOID*)LockQueue->Lock,
NULL,
LockQueue) == LockQueue) {
return;
}
NextQueue = KxWaitForLockChainValid(LockQueue);
}
ASSERT(((ULONG64)NextQueue->Lock &LOCK_QUEUE_WAIT) != 0);
InterlockedXor64((LONG64 volatile*)&NextQueue->Lock,LOCK_QUEUE_WAIT);
LockQueue->Next = NULL;
}
KxWaitForLockChainValid ( PKSPIN_LOCK_QUEUELockQueue)
{
PKSPIN_LOCK_QUEUENextQueue;
do {
KeYieldProcessor();
} while((NextQueue = LockQueue->Next) == NULL);
returnNextQueue;
}
线程准备退出时,首先检查链表上有没有其他节点在等待,如果有节点在等待,直接将下一个节点的Lock的LOCK_QUEUE_WAIT标志位去掉,并将本节点的从链表中脱离出来。
如果没有节点在等待,这时候的操作复杂一些,因为在任何时刻都有可能有节点进入。本节点的Lock指向的是SpinLock,而SpinLock指向的是表尾节点。下面的操作作为一个原子操作完成:
1, 如果Lock指向的是当前节点LockQueue,说明在此之前没有节点插入,则将Lock即SpinLock的值还原为初始状态NULL。
2, 如果Lock指向的不是当前节点,则说明已经或者正在有节点插入。说明其他线程在此之前执行完了KeAcquireInStackQueuedSpinLock第2步。
如果是1,已经完成退出操作,直接退出。
如果是2,则等到则点插入完成即当前节点的next指向下一个节点,然后回到刚开始,将下一个节点的Lock的LOCK_QUEUE_WAIT标志位去掉,并将本节点的从链表中脱离出来。
算法KeReleaseInStackQueuedSpinLock描述为:
1, 获得下一个节点NextQueue。
2, 如果NextQueue不为空,将下一个节点的Lock的LOCK_QUEUE_WAIT标志位去掉,并将本节点的从链表中脱离出来。
3, 将IRQL降为原来的值并且退出。
4, 如果NextQueue为空,执行如下独占处理器和相关存储空间操作:
1>Lock指向的是当前节点LockQueue,将Lock即SpinLock的值还原为初始状态NULL,转3。
2> Lock指向的不是当前节点。
5, 等待NextQueue不为空,然后转3。
分析:
在单核下,KeAcquireInStackQueuedSpinLock和KeReleaseSpinLock的实现基本上一样。
并没有性能上的提高。
在多核下。KeAcquireInStackQueuedSpinLock相对于KeReleaseSpinLock有如下不同:
1, KeAcquireInStackQueuedSpinLock每一个线程等待自己栈上的一个变量变化,
而KeReleaseSpinLock等待全局的SpinLock发生变化。
2, KeAcquireInStackQueuedSpinLock能维护一个队列,保证等待的队列先进先出。
而KeReleaseSpinLock获得资源则是随机的,与等待的先后次序无关。
关于更深层次的性能比较,期待其他人的分析了。