【问题标题】:Passing a pointer from C to assembly将指针从 C 传递到程序集
【发布时间】:2009-12-23 19:16:07
【问题描述】:

我想在我的 C/C++ 程序中使用带有原子交换汇编指令的“_test_and_set lock”汇编语言实现。

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 
    }   

  }
};

这可行,但这里有一个错误。

问题是我想传递 DWORD * resourceLock 而不是 DWORD resourceLock。

所以问题是如何将指针从 C/C++ 传递给程序集并将其取回。 ?

提前致谢。

问候, -杰。

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

【问题讨论】:

  • 顺便说一句,没有标准的汇编语言。此外,汇编和 C 或 C++ 之间传递的参数因编译器而异。因此,您必须提供编译器和平台标识(或查看我对平台无关进程的回答)。
  • lockResource(DWORD *resourceLock) 有什么问题?即仅将 resourceLock 声明为指针,否则保持代码相同?
  • 我不明白这个问题。你试过static void lockResource(DWORD * resourceLock) 没用吗?
  • @gf:现在我的目标平台是 Windows Server 2008,但最终它会是 Monta Vista:Carrer Grade 5.0。所以目前我正在使用 Visual Studio 2008 的内置编译器
  • @x4u : 没有得到你想说的.. 错误是资源只有一个实例,所有线程都得到一个指向资源的指针。资源锁是资源的一部分。这里的错误是我按值发送资源锁。我必须通过引用发送它。

标签: c++ c visual-studio-2008 pointers assembly


【解决方案1】:

如果您正在为 Windows 编写此代码,您应该认真考虑使用 critical section 对象。临界区 API 函数经过优化,除非确实需要,否则它们不会转换到内核模式,因此正常情况下没有争用的开销很小。

自旋锁的最大问题是,如果您在单个 CPU 系统上并且正在等待锁,那么您将使用所有可以使用的周期,而持有锁的任何东西都不会有机会运行,直到您的时间片结束并且内核抢占您的线程。

使用临界区将比尝试滚动您自己的用户模式自旋锁更成功。

【讨论】:

  • 我不想编写特定于 Windows 的代码。这就是为什么我没有使用 Interlocked 和其他东西的原因。我想让它尽可能通用。我正在拼命等待 Linux 的迁移,但至少在接下来的几个月内不会发生这种情况。
  • 编写程序集无助于可移植性。如果您想要可移植的关键部分,请编写中性 API。考虑提升同步:boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html
  • 我投了反对票,原因很简单,因为它没有回答所提出的问题。这个问题是有效的,很高兴看到它的答案。
  • 我投赞成票是因为在一次只能运行一个线程的系统上自旋锁是个坏主意。对此投反对票就像是在说“我不在乎与孩子们近距离射击是否是个坏主意,我只会击中我要求你设置的目标!”......最后,我不明白为什么这个逻辑不能被抽象出来,所以无论你改变什么,都只需要在一个小地方完成一次。显然,该计划是使用当前计划执行此操作,否则 OP 将不会使用 DWORD 并编写内联程序集。
  • 如果您知道您的平台有多个硬件线程,可以编写自旋锁。默认的 Window CS 基本上是一个组合互斥锁 + 自旋锁与自旋计数。它在回退到操作系统互斥体之前尝试旋转旋转计数。在单处理器系统上,使用的自旋计数始终为零,这使得那里的默认行为只是一个互斥体。
【解决方案2】:

就您的实际问题而言,这很简单:只需将函数头更改为使用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 开发,你也会好多;这将最大限度地减少迁移的痛苦(请参阅 mingw for gcc for Windows)。

  • Greg Hewgill 关于无用旋转、阻止锁持有者获取 CPU 是绝对正确的。如果您旋转的时间过长,请考虑让出 CPU。

  • 在多处理器 x86 上,您可能会遇到内存加载和存储围绕您的锁重新排序的问题 - 可能需要锁定和解锁过程中的 mfence 指令。

    李>

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

【讨论】:

  • 这对 VC++ 内联 asm 不起作用——至少在我的调试版本中的测试中不起作用。你实际上需要一个mov ebx, resourceLock 后跟一个xchg eax, [ebx] 来做他想做的事。只是 xchg eax, [resourceLock] 实际上会将 eax 与存储在指针中的值(或引用的地址)交换。
  • 解释:假设我们的指针有一个值(它的解引用内存也有一个值)——比如DWORD *resourceLock == 0x493004*(DWORD*)(0x00493004) == 1。然后在mov ebx, resourceLock; xchg eax, [ebx] 之后,eax 将等于 1。在xchg eax, [resourceLock] 之后,eax 将等于 0x493004,这绝对不是您想要的。
  • 谢谢,已修复。好久没用Intel语法汇编了,有点生疏了!
  • @CAF: FWIW,你在解锁码中的lock mov可以是mov。 X86 强制执行可见的写入顺序,因此第二个锁是不必要的,实际上只会减慢速度。 X86 仅将 1) load-to-load 和 2) load-above-stores 重新排序到不同的地址。没有商店重新排序,商店永远不会超过负载。由于此代码使用xchg(具有隐式锁定/mfence),因此无需在代码中添加 mfence。锁定期间的任何修改都将在解锁写入可见之前可见。
  • @CAF:在解锁代码中,由于 mov 不必被总线锁定,因此可以在 C 中通过简单的赋值而不是 ASM 来完成。但是,将其从 ASM 中取出意味着没有编译器读写障碍,因此您必须添加一个。基本上这适用于指针:_ReadWriteBarrier(); *resourceLock=1;(通过引用,您消除了 resourceLock 之前的 *)。
【解决方案3】:

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

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

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

【讨论】:

  • 内联汇编(即 C/C++ 代码中的汇编块)不再受 MS 支持。据我所见,他的代码看起来确实是用 MSVC 编译的内联汇编
  • @Ken 你喜欢这样改写吗?
  • @Ken,@Gregory:是的.. 没错。 !我讨厌 Windowz .. 但我的管理层认为它有助于加快开发速度 .. WTF .. 我看到很多与调度相关的问题,其中 Win OS 调度程序没有按预期做正确的事情,这使得一些线程疯狂运行并且其他人坐在旁边.. :( 我尝试强制执行 CPU Affinity 但这严重破坏了性能..
  • @Ken 很好的内联汇编块,给定的语法只能是 MS,这就是我最初这样回答的原因:)
【解决方案4】:

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

这是适用于 Visual C++ 的有效解决方案。 编辑: 我已与作者离线工作,我们已验证此答案中的代码在他的测试工具中正确工作。

但如果您使用的是 Windows,则应该使用 Interlocked API(即 InterlockedExchange)。

编辑:正如 CAF 所指出的,lock xchg 不是必需的,因为 xchg 会自动声明 BusLock。

我还添加了一个更快的版本,它在尝试执行 xchg 之前执行非锁定读取。这显着减少了内存接口上的 BusLock 争用。通过对长时间持有的锁进行退避(yield then sleep),算法可以加快很多(在有争议的多线程情况下)。对于单线程 CPU 的情况,使用在持有锁上立即休眠的 OS 锁将是最快的。

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);
}

【讨论】:

  • xchg 应该暗示总线锁。但是,如果您没有在unLockResource 中的mov 上添加lock 前缀,我认为您将需要在它之前使用mfence 指令,以确保您刚刚离开的关键部分的所有副作用都被其他处理器才被允许进入其临界区。
  • 我试过你的代码,但它不起作用。发生的情况是只有第一个线程获取锁并且无法释放它。其次,我尝试了 mfense/sfense + lock 组合,但这似乎也不起作用。
  • 您是否验证了使用此锁的程序可以与任何其他类型的锁一起使用?我在 QuadCore 上运行了上面的代码,在一个极具争议的测试平台上使用 8 个线程在锁下进行多线程修改和独立验证。这个测试平台足够强大,它在 MSDN 网站上的常见 Ruediger-Asche-ReaderWriterLock 中发现了一个错误。尽管它比我实际使用的锁定机制慢了几个数量级,但这段代码通过了“正确性”功能的出色表现。可能你的调用代码不正确?
  • lockTestThreadStatus_ = 1;而(lockTestThreadStatus_ ==1) { LockImpl::lockResource(commonResource->lockCnt); //===========关键部分=== commonResource->resource++; printf("线程 ID : %ld 资源值 :%ld \n", GetCurrentThreadId(), commonResource->resource); //===========关键部分结束==== LockImpl::unLockResource(commonResource->lockCnt); }
  • 这是我的调用代码..这基本上是线程函数。我希望看到多个线程 ID 一个接一个地增加这个资源计数器。但是使用上面的代码,我只看到一个线程 ID 不断地进行增量。具有讽刺意味的是,已发布的代码显示了正确的行为,我认为我知道其中的错误。 :) 这个测试我在没有超线程的双核机器上运行
【解决方案5】:

你应该使用这样的东西:

volatile LONG resourceLock = 1;

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

InterlockedCompareExchange

【讨论】:

  • 他的代码应该与 InterlockedExchange() 完美配合。对于未经检查的值交换,InterlockedExchange 比 InterlockedCompareExchange 快一点。
  • 是的,但你通常在这种情况下使用 CAS,我想让他朝着正确的方向前进。
  • 哦,据我所知,内在的 InterlockedCompareExchange 和 CompareExchange 都归结为一个汇编指令,所以我不会说一个比另一个快。好吧,CAS 可能还需要一个循环。
  • 其实CAS和XCHG有很大的不同。一方面,XCHG 总是成功,而 CAS 可能会失败。 CAS 通常需要额外的代码在设置之前预先读取值,如果失败,您必须重试或其他代码。尽管由于 BusLock 导致两条 ASM 指令都相当慢,但 XCHG 可以比 CAS 执行得更快,这仅仅是因为您总是必须检查 CAS 是否成功。如果您的算法足够简单,可以使用 XCHG,那么 XCHG 是首选操作。
【解决方案6】:

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

打印此函数的汇编语言:

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

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

【讨论】:

    【解决方案7】:

    我已经提供了一个工作版本,它回答了原始发布者关于如何获取 ASM 中传递的参数以及如何让他的锁正常工作的问题。

    许多其他答案都质疑使用 ASM 是否明智,并提到应该使用内部函数或 C OS 调用。以下内容也适用,并且是我的 ASM 答案的 C++ 版本。那里有一个 ASM 的 sn-p,只有在您的平台不支持 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;
        }
    };
    

    【讨论】:

    • 问题不是关于是否使用内联汇编,而是关于通过引用从 C 中的汇编而不是通过值来传递变量。不过感谢您的扩展代码..!
    • 哇...考虑到最佳答案只是说“使用关键部分”并且根本没有回答这个问题,在这里被否决似乎有点奇怪。特别是当许多其他答案都是关于不使用 ASM 的抱怨时,这不仅仅是一个后续问题,而是一个解决这些响应的实际工作示例。实际上,我对此进行了一些思考和工作,与作者离线联系以帮助在他自己的代码测试平台中实现功能,然后还提供了一个额外的非 ASM 工作版本。我想竭尽全力帮助他人是不值得的。
    猜你喜欢
    • 1970-01-01
    • 2013-07-06
    • 2018-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多