上一篇文章中, 我详细的整理了Intel处理器是如何在底层处理锁操的,在处理器指令集这一层次,这也是软件和硬件开发人员所能见到的处理器的最后一层,下面我就从汇编这一层来看看windows是如何封装原子操作函数的,了解清楚底层实现,以便在高并发中更好的利用原子操作。
Windows有6个原子操作函数:
InterlockedExchange:把目标操作数(参数1所指向的内存中的数)与一个值(参数2)交换,返回参数1的原始值。
InterlockedCompareExchange:是把目标操作数(第1参数所指向的内存中的数)与一个值(第3参数)比较,如果相等,
则用另一个值(第2参数)与目标操作数(第1参数所指向的内存中的数)交换;返回值为参数1的原始值
InterlockedIncrement:参数所指的内存中的数字加1
InterlockedDecrement:参数所指的内存中的数字减1
InterlockedExchangeAdd:参数1所指的内存中的数字,加上参数2对应的值,返回未加前的参数1
InterlockedExchangePointer:把目标操作数(参数1的指针)设置为参数2对应的指针,返回未重新指向前的参数1指针
然后是临界区。
下面就以32位函数逐渐反汇编来分析。
1. InterlockedExchange
C++调用示例:
long data = 0;
InterlockedExchange(&data,100);
反汇编代码:
0040724A mov esi,esp ;扩展栈指针,指向当前栈顶,放入esi寄存器,等会儿从函数出栈用于平衡栈。
0040724C push 64h ;100压入栈中,传参数用,这样esp-8
0040724E lea eax,[data] ;取得变量data的内存地址
00407251 push eax ;内存地址入栈,传参数用,这样esp-8
00407252 call dword ptr ds:[42C780h]
00407258 cmp esi,esp ;检查栈是否平衡,这里由被调用者平衡栈,call dword ptr ds:[42C780h] 出来后已经平衡。
0040725A call _RTC_CheckEsp (0419BD0h) ;检查栈是否平衡,不平衡就跳到错误处理。
下面看看这一句是怎么实现的:
call dword ptr ds:[42C780h]
↓
770A5E40 mov edi,edi ;占位,实现两个NOP操作,为实现hot-patching技术,即运行时修改一个函数的行为。
770A5E42 push ebp ;栈基地址入栈
770A5E43 mov ebp,esp ;栈顶指针入栈基地址寄存器
770A5E45 mov eax,dword ptr [ebp+0Ch] ;ebp+0Ch 栈指针加0Ch的地址存储的值,正好是参数100
770A5E48 mov ecx,dword ptr [ebp+8] ;ebp+8 栈指针加8的地址存储的值,正好是参数data的地址
770A5E4B xchg eax,dword ptr [ecx] ;将100与ecx的地址存储的值即参数data进行交换,eax变成了0,data值变为了100
770A5E4D pop ebp ;恢复ebp之前的值。
770A5E4E ret 8 ; 平衡栈,前面压入8个字节,这里弹出8个字节,这里是由被调用者平衡栈。
这里xchg按照前面的说法,是属于总线锁中的自动原子锁操作。
2. InterlockedCompareExchange
c++调用示例:
long waitxchg = 30;
long compare = 20;
long dest = 20;
InterlockedCompareExchange(&dest,waitxchg,compare);
反汇编代码:
00407803 mov dword ptr [waitxchg],1Eh 立即数移入内存空间,下同
long compare = 20;
0040780A mov dword ptr [compare],14h
long dest = 20;
00407811 mov dword ptr [dest],14h
00407818 mov esi,esp
0040781A mov eax,dword ptr [compare] ;compare值移入寄存中,用于参数传递,下同
0040781D push eax ;参数压入内存栈中,下同
0040781E mov ecx,dword ptr [waitxchg] ;waitxchg,用于参数传递,下同
00407821 push ecx
00407822 lea edx,[dest] ;dest的地址移入edx寄存器中
00407825 push edx
00407826 call dword ptr ds:[42C788h]
0040782C cmp esi,esp
0040782E call _RTC_CheckEsp (0419BD0h)
看看这一句调用:
call dword ptr ds:[42C788h]
↓
770AC260 mov edi,edi
770AC262 push ebp
770AC263 mov ebp,esp
770AC265 mov edx,dword ptr [ebp+0Ch]
770AC268 mov ecx,dword ptr [ebp+8]
770AC26B mov eax,dword ptr [ebp+10h]
770AC26E lock cmpxchg dword ptr [ecx],edx
770AC272 pop ebp
770AC273 ret 0Ch
最终调用这一句,按文档说明,cmpxchg加前缀lock,这是属于软件控制的总线锁。
lock cmpxchg dword ptr [ecx],edx
3. InterlockedIncrement
c++调用示例:
long data = 1;
InterlockedIncrement(&data);
反汇编代码:
0040780A mov esi,esp
0040780C lea eax,[data]
0040780F push eax
00407810 call dword ptr ds:[42C784h]
00407816 cmp esi,esp
00407818 call _RTC_CheckEsp (0419BD0h)
看看这一句调用:
call dword ptr ds:[42C784h] ;这里栈会减去4字节
↓
770B1A90 mov edi,edi
770B1A92 push ebp
770B1A93 mov ebp,esp
770B1A95 mov ecx,dword ptr [ebp+8] ;取得data的地址
770B1A98 mov eax,1
770B1A9D lock xadd dword ptr [ecx],eax ;eax的值加到data
770B1AA1 inc eax ;eax加1,作为返回值,为什么不取ecx的值呢,因为这样慢,直接用寄存器加1快。
770B1AA2 pop ebp
770B1AA3 ret 4
最终调用这一句,按文档说明,xadd
lock xadd dword ptr [ecx],eax加前缀lock,这是属于软件控制的总线锁。
4. InterlockedDecrement
c++调用示例:
long data = 1;
InterlockedDecrement(&data);
反汇编代码:
0040780A mov esi,esp
0040780C lea eax,[data]
0040780F push eax
00407810 call dword ptr ds:[42C78Ch]
00407816 cmp esi,esp
00407818 call _RTC_CheckEsp (0419BD0h)
call dword ptr ds:[42C78Ch] ;
↓
770B1B80 mov edi,edi
770B1B82 push ebp
770B1B83 mov ebp,esp
770B1B85 mov ecx,dword ptr [ebp+8]
770B1B88 or eax,0FFFFFFFFh ;将eax寄存器每位都置为1
770B1B8B lock xadd dword ptr [ecx],eax ;交换加之后,ecx地址对应的值变为了1,即data值为1
;0FFFFFFFFh加上2产生了进位,值变为了1,不处理进位标识
;用这种方式来实现减法
770B1B8F dec eax
770B1B90 pop ebp
770B1B91 ret 4
同理3一样,这是属于软件控制的总线锁。
5. InterlockedExchangeAdd
c++调用示例:
long data = 2;
InterlockedExchangeAdd(&data,10);
反汇编代码:
0040780A mov esi,esp
0040780C push 0Ah
0040780E lea eax,[data]
00407811 push eax
00407812 call dword ptr ds:[42C790h]
00407818 cmp esi,esp
0040781A call _RTC_CheckEsp (0419BD0h)
00407812 call dword ptr ds:[42C790h]
↓
74DFC410 mov edi,edi
74DFC412 push ebp
74DFC413 mov ebp,esp
74DFC415 mov eax,dword ptr [ebp+0Ch]
74DFC418 mov ecx,dword ptr [ebp+8]
74DFC41B lock xadd dword ptr [ecx],eax
74DFC41F pop ebp
74DFC420 ret 8
同理3一样,这是属于软件控制的总线锁。
6. InterlockedExchangePointer
c++调用示例:
long data = 2;
long* pdest = NULL;
InterlockedExchangePointer((void**)&pdest,(void*)&data);
反汇编代码:
00407261 lea eax,[data]
00407264 push eax
00407265 lea ecx,[pdest]
00407268 push ecx
00407269 call InterlockedExchangePointer (0401ADCh)
↓
0040F3E0 push ebp
0040F3E1 mov ebp,esp
0040F3E3 sub esp,0C0h
0040F3E9 push ebx
0040F3EA push esi
0040F3EB push edi
0040F3EC lea edi,[ebp-0C0h]
0040F3F2 mov ecx,30h
0040F3F7 mov eax,0CCCCCCCCh
0040F3FC rep stos dword ptr es:[edi]
return( reinterpret_cast<void*>(static_cast<LONG_PTR>(
::InterlockedExchange(reinterpret_cast<LONG*>(pp),
static_cast<LONG>(reinterpret_cast<LONG_PTR>(pNew))))) );
0040F3FE mov esi,esp
0040F400 mov eax,dword ptr [pNew]
0040F403 push eax
0040F404 mov ecx,dword ptr [pp]
0040F407 push ecx
0040F408 call dword ptr ds:[42C788h]
0040F40E cmp esi,esp
0040F410 call _RTC_CheckEsp (0419C80h)
↓
0040F408 call dword ptr ds:[42C788h]
74DF5E40 mov edi,edi
74DF5E42 push ebp
74DF5E43 mov ebp,esp
74DF5E45 mov eax,dword ptr [ebp+0Ch]
74DF5E48 mov ecx,dword ptr [ebp+8]
74DF5E4B xchg eax,dword ptr [ecx]
74DF5E4D pop ebp
74DF5E4E ret 8
这里xchg按照和1一样,是属于总线锁中的自动原子锁操作。
7. 临界区:CRITICAL_SECTION
看看定义:
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;
调用示例:
定义变量:
CRITICAL_SECTION m_mutex;
初始化:
::InitializeCriticalSection(&m_mutex);
删除:
::DeleteCriticalSection(&m_mutex);
调用:
int data = 1;
::EnterCriticalSection(&m_mutex);
data ++;
::LeaveCriticalSection(&m_mutex);
看看EnterCriticalSection反汇编:
00405A93 mov dword ptr [data],1
00405A9A mov eax,dword ptr [this]
00405A9D add eax,0C0h
00405AA2 mov esi,esp
00405AA4 push eax
00405AA5 call dword ptr ds:[42C738h]
00405AAB cmp esi,esp
00405AAD call _RTC_CheckEsp (0419C40h)
↓
00405AA5 call dword ptr ds:[42C738h]
776160B0 mov edi,edi
776160B2 push ebp
776160B3 mov ebp,esp
776160B5 and esp,0FFFFFFF8h
776160B8 mov ecx,dword ptr [ebp+8] ;将m_mutex地址移入ecx
776160BB mov edx,dword ptr fs:[18h]
776160C2 lea eax,[ecx+4]
776160C5 lock btr dword ptr [eax],0
776160CA jae 776160E1
776160CC mov eax,dword ptr [edx+24h]
776160CF mov dword ptr [ecx+0Ch],eax
776160D2 xor eax,eax
776160D4 mov dword ptr [ecx+8],1
776160DB mov esp,ebp
776160DD pop ebp
776160DE ret 4
关键语句:
lock btr dword ptr [eax],0
btr ----位测试并复位
最终这儿还是使用锁来判断临界区是否可用,lock前缀,软件控制的总线锁。