代码之家  ›  专栏  ›  技术社区  ›  Jay D

将指针从C传递到程序集

  •  5
  • Jay D  · 技术社区  · 16 年前

    我想在C/C++程序中使用“原子测试交换和汇编锁”的汇编语言实现原子交换装配指令。

    class LockImpl 
    {
      public:
      static void lockResource(DWORD resourceLock )
      {
        __asm 
        {
          InUseLoop:  mov     eax, 0;0=In Use
                      xchg    eax, resourceLock
                      cmp     eax, 0
                      je      InUseLoop
        }
    
      }
    
      static void unLockResource(DWORD resourceLock )
      {
        __asm 
        {
          mov resourceLock , 1 
        }   
    
      }
    };
    

    这是可行的,但这里有一个bug。

    问题是我要传递dword*资源锁而不是dword资源锁。

    因此,问题是如何将指针从C/C++转换成汇编并将其返回。?

    事先谢谢。

    当做, -杰伊。

    这是为了避免用户空间和内核空间之间的上下文切换。

    7 回复  |  直到 16 年前
        1
  •  1
  •   Nick    16 年前

    问题中原始版本的主要问题是,它需要使用寄存器间接寻址,并为锁双字取一个引用(或指针参数),而不是按值参数。

    这是VisualC++的工作解决方案。 编辑: 我已经和作者离线工作,我们已经验证了这个答案中的代码在他的测试工具中是否正确工作。

    但是,如果您使用的是Windows,那么应该使用联锁API(即InterlockedExchange)。

    编辑:如CAF所述, lock xchg 不是必需的,因为 xchg 自动断言一个总线锁。

    我还添加了一个更快的版本,在尝试执行 交换 . 这显著减少了内存接口上的总线锁争用。通过对长时间持有的锁进行退避(先产生,然后休眠),该算法可以加快相当多的速度(在有争议的多线程情况下)。对于单线程CPU情况,使用一个操作系统锁,立即休眠持有的锁将是最快的。

    class LockImpl
    {
        // This is a simple SpinLock
        //  0 - in use / busy
        //  1 - free / available
    public:
        static void lockResource(volatile DWORD &resourceLock )
        {
            __asm 
            {
                mov     ebx, resourceLock
    InUseLoop:
                mov     eax, 0           ;0=In Use
                xchg    eax, [ebx]
                cmp     eax, 0
                je      InUseLoop
            }
    
        }
    
        static void lockResource_FasterVersion(DWORD &resourceLock )
        {
            __asm 
            {
                mov     ebx, resourceLock
    InUseLoop:
                mov     eax, [ebx]    ;// Read without BusLock 
                cmp     eax, 0
                je      InUseLoop     ;// Retry Read if Busy
    
                mov     eax, 0
                xchg    eax, [ebx]    ;// XCHG with BusLock
                cmp     eax, 0
                je      InUseLoop     ;// Retry if Busy
            }
        }
    
        static void unLockResource(volatile DWORD &resourceLock)
        {
            __asm 
            {
                mov     ebx, resourceLock
                mov     [ebx], 1 
            }       
    
        }
    };
    
    // A little testing code here
    volatile DWORD aaa=1;
    void test()
    {
     LockImpl::lockResource(aaa);
     LockImpl::unLockResource(aaa);
    }
    
        2
  •  6
  •   Greg Hewgill    16 年前

    如果你是为Windows写的,你应该认真考虑使用 critical section 对象。关键部分API函数经过了优化,这样它们就不会转换到内核模式,除非它们确实需要这样做,所以正常情况下无争用的开销非常小。

    旋转锁最大的问题是,如果您在一个CPU系统上,并且正在等待锁,那么您正在使用所有可以使用的周期,并且持有锁的任何东西在您的时间片打开并且内核抢占线程之前都无法运行。

    使用关键部分将比尝试滚动您自己的用户模式自旋锁更成功。

        3
  •  4
  •   caf    16 年前

    就您的实际问题而言,这非常简单:只需更改函数头即可使用 volatile DWORD *resourceLock ,并更改接触的装配线 resourceLock 要使用间接寻址:

    mov ecx, dword ptr [resourceLock]
    xchg eax, dword ptr [ecx]
    

    mov ecx, dword ptr [resourceLock]
    lock mov dword ptr [ecx], 1
    

    但是,请注意,还有一些其他问题即将出现:

    • 你说你在Windows上开发这个,但是想切换到Linux。但是,您使用的是特定于MSVC的内联程序集-当您移动到Linux时,必须将其移植到gcc样式(特别是涉及从Intel语法切换到AT&T语法)。你会的 许多的 即使在Windows上也最好使用GCC进行开发;这样可以最大限度地减少迁移的痛苦(请参见针对Windows的GCC的mingw)。

    • GregHewgill完全正确地处理了无用的旋转,阻止了锁支架获得CPU。如果你已经旋转太久了,考虑让CPU工作。

    • 在多处理器x86上,您很可能遇到内存加载和存储在锁周围重新排序的问题。- mfence 可能需要有关锁定和解锁过程的说明。


    实际上,如果您担心锁定,这意味着您正在使用线程,这可能意味着您已经在使用平台特定的线程API。因此,使用本机同步原语,并在切换到Linux时切换到pthreads版本。

        4
  •  3
  •   Gregory Pakosz    16 年前

    显然,您正在使用C++代码中的内联汇编块编译MSVC。

    一般来说,你应该 compiler intrinsics 因为内联程序集没有未来:在为x64编译时,它不再支持我的MS编译器。

    如果需要在程序集中对函数进行微调,则必须在单独的文件中实现它们。

        5
  •  1
  •   Fozi    16 年前

    您应该使用这样的工具:

    volatile LONG resourceLock = 1;
    
    if(InterlockedCompareExchange(&resourceLock, 0, 1) == 1) {
        // success!
        // do something, and then
        resourceLock = 1;
    } else {
        // failed, try again later
    }
    

    InterlockedCompareExchange .

        6
  •  0
  •   Thomas Matthews    16 年前

    查看编译器文档,了解如何打印生成的函数汇编语言。

    打印此函数的汇编语言:

    static void unLockResource(DWORD resourceLock )
    {
      resourceLock = 0;
      return;
    }
    

    这可能不起作用,因为编译器可以优化函数并删除所有代码。您应该更改上面的函数以将指针传递给 resourceLock 然后让函数设置锁。打印此工作功能的程序集。

        7
  •  -1
  •   Adisak    16 年前

    我已经提供了一个工作版本,它回答了原始海报的问题,包括如何在ASM中传递参数以及如何使其锁正常工作。

    许多其他答案质疑使用asm的明智性,并提到应该使用intrinsics或c os调用。下面的工作也是我的ASM答案的C++版本。其中有一小段asm,仅当您的平台不支持interlockedexchange()时才需要使用。

    class LockImpl
    {
        // This is a simple SpinLock
        //  0 - in use / busy
        //  1 - free / available
    public:
    #if 1
        static DWORD MyInterlockedExchange(volatile DWORD *variable,DWORD newval)
        {
            // InterlockedExchange() uses LONG / He wants to use DWORD
            return((DWORD)InterlockedExchange(
                (volatile LONG *)variable,(LONG)newval));
        }
    #else
        // You can use this if you don't have InterlockedExchange()
        // on your platform. Otherwise no ASM is required.
        static DWORD MyInterlockedExchange(volatile DWORD *variable,DWORD newval)
        {
            DWORD old;
            __asm 
            {
                mov     ebx, variable
                mov     eax, newval
                xchg    eax, [ebx]  ;// XCHG with BusLock
                mov     old, eax
            }
            return(old);
        }
    #endif
        static void lockResource(volatile DWORD &resourceLock )
        {
            DWORD oldval;
            do 
            {
                while(0==resourceLock)
                {
                    // Could have a yield, spin count, exponential 
                    // backoff, OS CS fallback, etc. here
                }
                oldval=MyInterlockedExchange(&resourceLock,0);
            } while (0==oldval);
        }
        static void unLockResource(volatile DWORD &resourceLock)
        {
            // _ReadWriteBarrier() is a VC++ intrinsic that generates
            // no instructions / only prevents compiler reordering.
            // GCC uses __sync_synchronize() or __asm__ ( :::"memory" )
            _ReadWriteBarrier();
            resourceLock=1;
        }
    };
    
    推荐文章