Windows 自旋锁分析(四)

来源:互联网 发布:玄彬韩国地位知乎 编辑:程序博客网 时间:2024/06/05 03:49

5,KeAcquireInStackQueuedSpinLock的实现机制

在单核处理器(WindowsXP)下
观察KeAcquireInStackQueuedSpinLock的实现
hal!KeAcquireInStackQueuedSpinLock:
mov eax,dword ptr ds:[FFFE0080h]
shr eax,4
mov al,byte ptr hal!HalpVectorToIRQL [eax]
mov dword ptr 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_LOCK volatile Lock;
}
typedef struct _KLOCK_QUEUE_HANDLE {
KSPIN_LOCK_QUEUE LockQueue;
KIRQL OldIrql;
}
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_LOCK SpinLock )
{
PKSPIN_LOCK_QUEUE TailQueue;
TailQueue = InterlockedExchangePointer((PVOID *)SpinLock, LockQueue);
if (TailQueue != NULL) {
KxWaitForLockOwnerShip(LockQueue, TailQueue);
  }
return;
}
ULONG64 KxWaitForLockOwnerShip(PKSPIN_LOCK_QUEUE LockQueue,PKSPIN_LOCK_QUEUE TailQueue)
{
ULONG64 SpinCount;
*((ULONG64 volatile *)&LockQueue->Lock) |= LOCK_QUEUE_WAIT;
TailQueue->Next = LockQueue;
SpinCount = 0;
do {
__asm { rep nop }
} while ((*((ULONG64 volatile *)&LockQueue->Lock) & LOCK_QUEUE_WAIT) != 0);
KeMemoryBarrier();
return SpinCount;
}


下面将分析KeAcquireInStackQueuedSpinLock如何获访问互斥的资源。

Windows <wbr>自旋锁分析(四)

如图所示,LockHandle是在栈上建立的的数据。当线程1首次进入InStackQueuedSpinLock时,初始时*SpinLock的值为NULL,因此TailQueue的值也为NULL。SpinLock通过InterlockedExchangePointer被置成为指向栈上的LockHandle。线程1进入被InStackQueuedSpinLock保护的资源。

Windows <wbr>自旋锁分析(四)

当线程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_HANDLE LockHandle)
{
KxReleaseQueuedSpinLock(&LockHandle->LockQueue);
KeLowerIrql(LockHandle->OldIrql);
return;
}

KxReleaseQueuedSpinLock (PKSPIN_LOCK_QUEUE LockQueue)
{
PKSPIN_LOCK_QUEUE NextQueue;
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_QUEUE LockQueue)
{
PKSPIN_LOCK_QUEUE NextQueue;
do {
KeYieldProcessor();
} while ((NextQueue = LockQueue->Next) == NULL);
return NextQueue;
}


线程准备退出时,首先检查链表上有没有其他节点在等待,如果有节点在等待,直接将下一个节点的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获得资源则是随机的,与等待的先后次序无关。
关于更深层次的性能比较,期待其他人的分析了。

0 0