68.4 Windows NT: Critical section
临界区在任何操作系统多线程环境中都是非常重要的,它保证一个线程在某一时刻访问一些数据的时候,阻塞其它正要访问这些数据的线程。
下面是Windows NT操作系统的CRITICAL_SECTION声明:
Listing 68.14: (Windows Research Kernel v1.2) public/sdk/inc/nturtl.h
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // from the thread's ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
下面展示了EnterCriticalSection()函数的运行过程:
Listing 68.15: Windows 2008/ntdll.dll/x86 (begin)
_RtlEnterCriticalSection@4
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
arg_0 = dword ptr 8
mov edi, edi
push ebp
mov ebp, esp
sub esp, 0Ch
push esi
push edi
mov edi, [ebp+arg_0]
lea esi, [edi+4] ; LockCount
mov eax, esi
lock btr dword ptr [eax], 0
jnb wait ; jump if CF=0
loc_7DE922DD:
mov eax, large fs:18h
mov ecx, [eax+24h]
mov [edi+0Ch], ecx
mov dword ptr [edi+8], 1
pop edi
xor eax, eax
pop esi
mov esp, ebp
pop ebp
retn 4
... skipped
在这段代码中最重要的指令是BTR(带LOCK前缀):把目的操作数中由源操作数所指定位的值送往标志位CF,并将目的操作数中的该位置0。这是一个原子操作,会阻塞掉其它同时想要访问这段内存的CPU(参看BTR指令的LOCK前缀)。如果LockCount是1,则重置并返回:我们现在正处于临界区。如果不是,则表示其它线程正在占用,将进入等待状态。
使用WaitForSingleObject()进入等待状态。
下面展示了LeaveCriticalSection()函数的运行过程:
Listing 68.16: Windows 2008/ntdll.dll/x86 (begin)
_RtlLeaveCriticalSection@4 proc near
arg_0 = dword ptr 8
mov edi, edi
push ebp
mov ebp, esp
push esi
mov esi, [ebp+arg_0]
add dword ptr [esi+8], 0FFFFFFFFh ;RecursionCount
jnz short loc_7DE922B2
push ebx
push edi
lea edi, [esi+4] ; LockCount
mov dword ptr [esi+0Ch], 0
mov ebx, 1
mov eax, edi
lock xadd [eax], ebx
inc ebx
cmp ebx, 0FFFFFFFFh
jnz loc_7DEA8EB7
loc_7DE922B0:
pop edi
pop ebx
loc_7DE922B2:
xor eax, eax
pop esi
pop ebp
retn 4
... skipped
XADD指令功能是:交换并相加。这种情况下,LockCount加1并把结果保存到EBX寄存器,同时把1赋值给LockCount。这个操作是原子的,因为它使用了LOCK前缀,这意味着系统会阻塞其它CPU或CPU核心同时访问这块内存。
LOCK前缀是非常重要的:如果两个线程,每个都工作在不同的CPU或CPU核心,它们都能够进入critical section并修改内存数据,这种行为将导致不确定的后果。