Windows 自旋锁分析(一)

来源:互联网 发布:学软件开发去哪里 编辑:程序博客网 时间:2024/06/08 14:55

自旋锁是一种在内核定义,只能在内核态下使用的同步机制。自旋锁用来保护共享数据或者资源,使得并发执行的程序或者在高优先级IRQL的对称多处理器的程序能够正确访问这些数据。分析Windows自旋锁,首先需要介绍Windows的IRQL。

 

1 Interrupt Request Level(IRQL)介绍

IRQL是Interrupt RequestLevel,中断请求级别。一个由windows虚拟出来的概念,划分在windows下中断的优先级,这里中断包括了硬中断和软中断,硬中断是由硬件产生,而软中断则是完全虚拟出来的。处理器在一个IRQL上执行线程代码。IRQL是帮助决定线程如何被中断的。在同一处理器上,线程只能被更高级别IRQL的线程能中断。每个处理器都有自己的中断。IRQL在Windows下IRQL有如下值:

 

名称               级别              解释
             Software IRQL
PASSIVE_LEVEL                      Passive release level
LOW_LEVEL                          Lowest interrupt level
APC_LEVEL                           APC interrupt level
DISPATCH_LEVEL                     Dispatcher level
             Hardware IRQL
             from 3 to 26 for device ISR
PROFILE_LEVEL     27                 timer used for profiling
CLOCK1_LEVEL      28                  Intervalclock 1 level - Not used on x86
CLOCK2_LEVEL      28                 Interval clock 2 level
SYNCH_LEVEL       28                  synchronizationlevel
IPI_LEVEL         29                 Interprocessor interrupt level
POWER_LEVEL       30                  Powerfailure level
HIGH_LEVEL        31                 Highest interrupt level

 

Windows的自旋锁有一系列函数组成,实现在不同场景下的自旋锁机制。

本系列文章主要涉及的体系结构是X86(32位)体系结构。如不特殊注明,都是在X86(32位)体系结构下。

下面分析KeAcquireSpinLock 的实现机制

 

2 KeAcquireSpinLock 的实现机制

KeAcquireSpinLock 在单核处理器和多核处理器上的实现是不一样的。

 

在单核处理器(WindowsXP)下
hal!KfAcquireSpinLock:
 
  mov    edx,dword ptr ds:[0FFFE0080h]
 
   movdword ptr ds:[0FFFE0080h],41h
 
   shr    edx,4
 
   movzx  eax,byte ptr hal!HalpVectorToIRQL [edx]
 
   ret
观察KeGetCurrentIrql的实现
hal!KeGetCurrentIrql:
 
   mov    eax,dword ptr ds:[FFFE0080h]
 
   shr    eax,4
 
   movzx  eax,byte ptr hal!HalpVectorToIRQL [eax]
 
   ret
有充足的理由说明ds:[FFFE0080h]地址处存放的是IRQL相关的数据。
KfAcquireSpinLock就是改变当前的IRQL,KfAcquireSpinLock将IRQL改到一个什么值了呢?
db hal!HalpVectorToIRQL:
00 
ff  ff 01 02ff  05 06 07 08 09 0a 1b 1c 1d 1e
00 
00  00 00 0000  00 00 2a 00 00 00 c4 00 00 00
如果先调用KfAcquireSpinLock那么ds:[0FFFE0080h]的值是0x41,再调用KeGetCurrentIrql,由以上表得到返回值是(BYTE*)HalpVectorToIRQL[4]是2。单核处理器下,KfAcquireSpinLock的工作就是将IRQL升为DISPATCH_LEVEL。KfReleaseSpinLock就是将IRQL还原为原来的值了。

 

在多核处理器(Windows2003)下

KeAcquireSpinLock 通过调用KfAcquireSpinLock来实现功能
hal!KfAcquireSpinLock:
 
  mov    eax,dword ptr fs:[00000024h]
 
  mov    byte ptr fs:[24h],2
 
  jmp    hal!KeAcquireSpinLockRaiseToSynch+0xe
hal!KeAcquireSpinLockRaiseTo
Synch:
 
  mov    eax,dword ptr fs:[00000024h]
 
  mov    byte ptr fs:[24h],1Bh
hal!KeAcquireSpinLockRaiseTo
Synch+0xe
 
  lock bts dword ptr[ecx],0
 
 jb     hal!KeAcquireSpinLockRaiseToSynch+0x16
 
  ret
hal!KeAcquireSpinLockRaiseTo
Synch+0x16
 
 test    dwordptr [ecx],1
 
 je     hal!KeAcquireSpinLockRaiseToSynch+0xe
 
  pause
 
 jmp    hal!KeAcquireSpinLockRaiseToSynch+0x16
KfAcquireSpinLock是不会执行到KeAcquireSpinLockRaiseTo
Synch的头两行代码的。

fs:[00000024h] 保存的是当前线程的IRQL,可以通过函数来证实。
hal!KeGetCurrentIrql:
 
  mov    al,byte ptr fs:[00000024h]
 
   ret
将KfAcquireSpinLock翻译成伪代码:
KfAcquireSpinLock(SpinLock){
 
  KeRaiseIrql(DISPATCH_LEVEL,OldIrql);    
 
  While(TRUE){
 
   //独占处理器和相关存储空间执行下面代码

     //由Lock指令实现
 
   lastbit=SpinLock.lastbit;
 
   SpinLock.lastbit=1;
 
   //释放独占
 
   if(lastbit ==1){
 
      while(SpinLock.lastbit ==1) ;
 
    }
 
  else break;
 
  }
 
  ReturnOldIrql; 
}
对KfAcquireSpinLock的解释就比较容易了,首先提升IRQL到DISPATCH_LEVEL,然后一直等到SpinLock被别人释放,设置SpinLock的值为占有然后退出。

在Windows2003多核处理器下KeReleaseSpinLock通过调用KfReleaseSpinLock来实现功能
hal!KfReleaseSpinLock:
 
   lock andbyte ptr [ecx],0
 
  mov    cl,dl
 
  call   hal!KfLowerIrql
 
   ret
释放SpinLock,将IRQL还原。

 

分析:

显而易见,在单核环境里实现DISPATCH_LEVEL及其以下IRQL的同步,将当前线程升级到DISPATCH_LEVEL足够了。但是在多核环境下,每一个核都有自己的IRQL,提升IRQL来实现同步是不行的,在多核的情况下,Windows系统引入了lock指令来实现同步。

 

现在DDK(3790)中是这么定义的

#if defined(_X86_)

#define KeAcquireSpinLock(a,b)  *(b) =KfAcquireSpinLock(a)
#define KeReleaseSpinLock(a,b) 
KfReleaseSpinLock(a,b)

#else

//
// These functions are imported for IA64, ntddk, ntifs, nthal,ntosp, and wdm.
// They can be inlined for the system on AMD64.
//

#define KeAcquireSpinLock(SpinLock, OldIrql) \
 
   *(OldIrql) =KeAcquireSpinLockRaiseToDpc(SpinLock)

0 0
原创粉丝点击