天天看點

Windows 自旋鎖分析(一)

自旋鎖是一種在核心定義,隻能在核心态下使用的同步機制。自旋鎖用來保護共享資料或者資源,使得并發執行的程式或者在高優先級IRQL的對稱多處理器的程式能夠正确通路這些資料。分析Windows自旋鎖,首先需要介紹Windows的IRQL。

1 Interrupt Request Level(IRQL)介紹

IRQL是Interrupt RequestLevel,中斷請求級别。一個由windows虛拟出來的概念,劃分在windows下中斷的優先級,這裡中斷包括了硬中斷和軟中斷,硬中斷是由硬體産生,而軟中斷則是完全虛拟出來的。處理器在一個IRQL上執行線程代碼。IRQL是幫助決定線程如何被中斷的。在同一處理器上,線程隻能被更進階别IRQL的線程能中斷。每個處理器都有自己的中斷。IRQL在Windows下IRQL有如下值:

名稱               級别              解釋

             Software IRQL

PASSIVE_LEVEL      0                 Passive release level

LOW_LEVEL          0                 Lowest interrupt level

APC_LEVEL           1                 APC interrupt level

DISPATCH_LEVEL     2                 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!KeAcquireSpinLockRaiseToSynch:

   mov    eax,dword ptr fs:[00000024h]

   mov    byte ptr fs:[24h],1Bh

hal!KeAcquireSpinLockRaiseToSynch+0xe

   lock bts dword ptr[ecx],0

  jb     hal!KeAcquireSpinLockRaiseToSynch+0x16

   ret

hal!KeAcquireSpinLockRaiseToSynch+0x16

  test    dwordptr [ecx],1

  je     hal!KeAcquireSpinLockRaiseToSynch+0xe

   pause

  jmp    hal!KeAcquireSpinLockRaiseToSynch+0x16

KfAcquireSpinLock是不會執行到KeAcquireSpinLockRaiseToSynch的頭兩行代碼的。

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還原。

現在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)      

繼續閱讀