深入理解原子操作-windows原子操作函数剖析

      上一篇文章中, 我详细的整理了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前缀,软件控制的总线锁。


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值