Передача указателя от C до блока

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

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

С уважением, - Сойка.

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

5
задан Gregory Pakosz 24 December 2009 в 00:03
поделиться

7 ответов

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

Вот работающее решение для Visual C++. EDIT: Я работал в автономном режиме с автором, и мы проверили, что код в этом ответе корректно работает в его тестовом жгуте.

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

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

Я также добавил более быструю версию, которая делает считывание без блокировки перед попыткой сделать xchg. Это значительно снижает нагрузку на BusLock на интерфейс памяти. Алгоритм можно значительно ускорить (в спорном многопоточном случае), сделав откаты (уступить, а затем поспать) для блокировок, удерживаемых в течение длительного времени. Для однопоточного 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);
}
1
ответ дан 18 December 2019 в 13:15
поделиться

Что касается вашего вопроса, то он довольно прост: просто измените заголовки функций, чтобы использовать volatile DWORD *resourceLock, и измените строки сборки, которые касаются resourceLock, чтобы использовать indirection:

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 для потокового управления. Поэтому используйте нативные примитивы синхронизации, а при переходе на Linux переключитесь на pthreads.

.
4
ответ дан 18 December 2019 в 13:15
поделиться

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

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

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

.
6
ответ дан 18 December 2019 в 13:15
поделиться

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

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

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

.
3
ответ дан 18 December 2019 в 13:15
поделиться

Вы должны использовать нечто подобное:

volatile LONG resourceLock = 1;

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

См. InterlockedCompareExchange.

.
1
ответ дан 18 December 2019 в 13:15
поделиться

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

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

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

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

.
0
ответ дан 18 December 2019 в 13:15
поделиться

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

Многие другие ответы ставили под сомнение целесообразность использования ASM вообще и говорили о том, что следует использовать либо внутренние вызовы операционной системы, либо вызовы операционной системы на языке Си. Следующий ответ также работает и является 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;
    }
};
-1
ответ дан 18 December 2019 в 13:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: