Windows 自旋锁分析(四)

83 篇文章 0 订阅
13 篇文章 1 订阅

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如何获访问互斥的资源。

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_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获得资源则是随机的,与等待的先后次序无关。
关于更深层次的性能比较,期待其他人的分析了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值