自旋鎖是一種在核心定義,隻能在核心态下使用的同步機制。自旋鎖用來保護共享資料或者資源,使得并發執行的程式或者在高優先級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)