Передача указателя из C в сборку - PullRequest
5 голосов
/ 23 декабря 2009

Я хочу использовать реализацию ассемблера "_test_and_set lock" с инструкцией по сборке атомарного свопа в моей программе на 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 
    }   

  }
};

Это работает, но здесь есть ошибка.

Проблема в том, что я хочу передать DWORD * resourceLock вместо DWORD resourceLock.

Итак, вопрос в том, как передать указатель из C / C ++ в сборку и получить его обратно. ?

спасибо заранее.

С уважением, -Jay.

P.S. это сделано, чтобы избежать переключения контекста между пользовательским пространством и пространством ядра.

Ответы [ 7 ]

6 голосов
/ 23 декабря 2009

Если вы пишете это для Windows, вам следует серьезно подумать об использовании критической секции объекта. API-функции критической секции оптимизированы таким образом, что они не будут переходить в режим ядра, если в этом нет особой необходимости, поэтому обычный случай отсутствия конфликтов имеет очень мало накладных расходов.

Самая большая проблема с вашей спин-блокировкой заключается в том, что если вы работаете в системе с одним процессором и ожидаете блокировки, то вы используете все циклы, которые можете, и все, что удерживает блокировку, даже не будет получите возможность работать до тех пор, пока ваш временной интервал не истечет и ядро ​​не вытеснит ваш поток.

Использование критической секции будет более успешным, чем попытка накатить собственную спин-блокировку в пользовательском режиме.

4 голосов
/ 24 декабря 2009

С точки зрения вашего фактического вопроса, это довольно просто: просто измените заголовки функций на 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). Вы будете намного лучше разрабатывать с gcc даже под Windows; это минимизирует трудность миграции (см. mingw для gcc для Windows).

  • Грег Хьюгилл абсолютно прав насчет бесполезного вращения, мешающего владельцу замка получить процессор. Подумайте об уступке процессора, если вы вращались слишком долго.

  • В многопроцессорном процессоре x86 у вас могут возникнуть проблемы с загрузкой памяти и переупорядочением хранилищ вокруг вашей блокировки - mfence могут потребоваться инструкции для процедур блокировки и разблокировки.


Действительно, если вы беспокоитесь о блокировке, это означает, что вы используете многопоточность, что, вероятно, означает, что вы уже используете API-интерфейсы многопоточности, специфичные для платформы. Поэтому используйте собственные примитивы синхронизации и переключайтесь на версии pthreads при переходе на Linux.

3 голосов
/ 23 декабря 2009

Очевидно, вы компилируете с MSVC, используя встроенные блоки сборки в вашем коде C ++.

Как общее замечание, вы действительно должны использовать встроенные функции компилятора , поскольку встроенная сборка не имеет будущего: она больше не поддерживается моими компиляторами MS при компиляции для x64.

Если вам нужно настроить функции в сборке, вам придется реализовать их в отдельных файлах.

1 голос
/ 24 декабря 2009

Основные проблемы с исходной версией, о которой идет речь, заключается в том, что ей нужно использовать косвенную адресацию регистра и принимать ссылку (или параметр указателя), а не параметр-значение для блокировки DWORD.

Вот рабочее решение для Visual C ++. РЕДАКТИРОВАТЬ: Я работал в автономном режиме с автором, и мы убедились, что код в этом ответе работает в его тестовом жгуте правильно.

Но если вы используете Windows, вам действительно следует использовать Interlocked API (т.е. InterlockedExchange).

Редактировать: Как отмечает CAF, lock xchg не требуется, поскольку xchg автоматически устанавливает BusLock.

Я также добавил более быструю версию, которая выполняет чтение без блокировки, прежде чем пытаться выполнить xchg. Это значительно уменьшает конкуренцию BusLock на интерфейсе памяти. Алгоритм может быть ускорен совсем немного больше (в спорном случае многопоточного), делая backoffs (выход потом спать) для блокировок долгого времени. В случае однопоточного ЦП наиболее быстрым будет использование блокировки ОС, которая немедленно отключается при удерживаемых блокировках.

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);
}
1 голос
/ 23 декабря 2009

Вы должны использовать что-то вроде этого:

volatile LONG resourceLock = 1;

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

См. InterlockedCompareExchange .

0 голосов
/ 25 декабря 2009

Я уже предоставил рабочую версию, которая ответила на первоначальный вопрос автора как о том, как получить параметры, передаваемые в ASM, так и о том, как заставить его блокировку работать правильно.

Многие другие ответы поставили под сомнение целесообразность использования ASM и отметили, что следует использовать либо встроенные вызовы, либо вызовы ОС C. Следующее работает также и является C ++ версией моего ответа ASM. Там есть фрагмент 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;
    }
};
0 голосов
/ 23 декабря 2009

Просмотрите документацию вашего компилятора, чтобы узнать, как напечатать сгенерированный язык ассемблера для функций.

Распечатать язык ассемблера для этой функции:

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

Это может не работать, потому что компилятор может оптимизировать функцию и удалить весь код. Вы должны изменить указанную выше функцию, чтобы передать указатель на resourceLock, а затем установить функцию блокировки. Распечатать сборку этой рабочей функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...