Как выполнять атомарные операции в Linux, которые работают на x86, arm, GCC и icc?

каждая современная ОС предоставляет сегодня некоторые атомарные операции:

  • Windows имеет Interlocked* API
  • FreeBSD есть <machine/atomic.h>
  • Солярис <atomic.h>
  • Mac OS X имеет <libkern/OSAtomic.h>

что-нибудь подобное для Linux?

  • мне нужно, чтобы он работал на большинстве поддерживаемых Linux платформ, включая: x86, x86_64 и arm.
  • мне нужно, чтобы он работал по крайней мере на GCC и Intel Компилятор.
  • мне не нужно использовать библиотеку 3rd par, такую как glib или qt.
  • мне нужно, чтобы он работал на C++ (C не требуется)

вопросы:

  • GCC atomic builtins __sync_* не поддерживаются на всех платформах (ARM)и не поддерживаются компилятором Intel.
  • насколько я знаю <asm/atomic.h> не следует использовать в пользовательском пространстве,и я не использовал его вообще. Кроме того, я не уверен, что он будет работать с Intel компилятор.

какие предложения?

Я знаю, что есть много вопросов, но некоторые из них указывают на __sync* что для меня неосуществимо (ARM) и какой-то пункт asm/atomic.h.

может быть, есть встроенная библиотека сборки, которая делает это для GCC (ICC поддерживает сборку gcc)?

Edit:

существует очень частичное решение только для операций добавления (позволяет реализовать atomic счетчик, но не блокировка free-структуры, которые требуют CAS):

если вы используете libstc++ (компилятор Intel использует libstdc++), то вы можете использовать __gnu_cxx::__exchange_and_add, что определено в <ext/atomicity.h> или <bits/atomicity.h>. Зависит от версии компилятора.

однако я все равно хотел бы увидеть что-то, что поддерживает CAS.

9 ответов


проекты используют этот:

http://packages.debian.org/source/sid/libatomic-ops

Если вы хотите простые операции, такие как CAS, не можете ли вы просто использовать конкретные реализации arch из ядра и выполнять проверки arch в пользовательском пространстве с помощью autotools/cmake? Что касается лицензирования, хотя ядро является GPL, я думаю, что можно утверждать, что встроенная сборка для этих операций предоставляется Intel/ AMD, а не то, что ядро имеет лицензию на их. Они просто находятся в легкодоступной форме в источнике ядра.


последние стандарты (с 2011 года) C & C++ теперь указывают атомарные операции:

независимо от того, ваша платформа или компилятор может не поддерживать эти новые заголовки и компоненты.


штопать. Я собиралась предложить примитивных членов ССЗ, но ты сказал, что они под запретом. :-)

в этом случае я бы сделал #ifdef для каждой архитектуры/компилятор комбинации и код в инлайн АСМ. И, может быть, проверить на __GNUC__ или какой-то подобный макрос и используйте примитивы GCC, если они доступны, потому что он чувствует себя намного более правильным использовать их. :-)

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

некоторые gotchas, которые кусали меня в прошлом: при использовании GCC, не забывайте"


Boost, который имеет неинтрузивную лицензию, и другие фреймворки уже предлагают портативные атомарные счетчики-до тех пор, пока они поддерживаются на целевой платформе.

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


недавно я сделал реализацию такой вещи, и я столкнулся с теми же трудностями, что и вы. Мое решение было в основном следующим образом:

  • попробуйте обнаружить встроенные GCC с функция макрос
  • если не доступно, просто реализуйте что-то вроде cmpxch с __asm__ для других архитектур (ARM немного сложнее, чем это). Просто сделайте это для одного возможного размера, e.g sizeof(int).
  • реализовать все остальные функции на в довершение всего один или два примитива с inline функции

здесь есть патч для GCC для поддержки атомных операций ARM. Не поможет вам на Intel, но вы можете изучить код - есть недавняя поддержка ядра для старых архитектур ARM, а новые имеют встроенные инструкции, поэтому вы должны иметь возможность создавать что-то, что работает.

http://gcc.gnu.org/ml/gcc-patches/2011-07/msg00050.html


__sync* конечно, поддерживается (и поддерживался) компилятором Intel, потому что GCC принял эти сборки оттуда. Прочитайте первый абзац на этой странице. Также см. "компилятор Intel® C++ для Linux * Intrinsics Reference", стр. 198. Это от 2006 и описывает именно эти встроенные модули.

Что касается поддержки ARM, для более старых процессоров ARM: это нельзя сделать полностью в пользовательском пространстве, но это можно сделать в kernelspace (отключив прерывает во время операции), и я думаю, что где-то читал, что он поддерживается уже довольно давно.

по данным эта ошибка PHP от 2011-10-08, __sync_* только не на

  • PA-RISC с чем-либо, кроме Linux
  • SPARCv7 и ниже
  • рукоятка с GCC
  • ARMv5 и ниже с чем-либо, кроме Linux
  • MIPS1

Так с GCC > 4.3 (и 4.7 текущий), у вас не должно быть проблем с ARMv6 и новее. У вас не должно быть никаких проблем с ARMv5 до тех пор, пока компиляция для Linux.


на Debian/Ubuntu рекомендуем...

sudo apt-get install libatomic-ops-dev

примеры:http://www.hpl.hp.com/research/linux/atomic_ops/example.php4

GCC & ICC совместимые.

по сравнению с Intel Thread Building Blocks (TBB), используя atomic, libatomic-ops-dev в два раза быстрее! (Компилятор Intel)

тестирование на Ubuntu i7 producer-consumer threads piping 10 миллионов ints вниз по кольцевому буферному соединению в 0.5 сек в отличие от 1.2 сек для TBB

и простой в использовании, например

Летучая голова AO_t;

AO_fetch_and_add1 (&head);


посмотреть: kernel_user_helpers.txt или entry-arm.c искать __kuser_cmpxchg. Как видно из комментариев других версий arm Linux,

kuser_cmpxchg

Location:       0xffff0fc0

Reference prototype:

  int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr);

Input:

  r0 = oldval
  r1 = newval
  r2 = ptr
  lr = return address

Output:

  r0 = success code (zero or non-zero)
  C flag = set if r0 == 0, clear if r0 != 0

Clobbered registers:

  r3, ip, flags

Definition:

  Atomically store newval in *ptr only if *ptr is equal to oldval.
  Return zero if *ptr was changed or non-zero if no exchange happened.
  The C flag is also set if *ptr was changed to allow for assembly
  optimization in the calling code.

Usage example:
 typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
 #define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0)

 int atomic_add(volatile int *ptr, int val)
 {
        int old, new;

        do {
                old = *ptr;
                new = old + val;
        } while(__kuser_cmpxchg(old, new, ptr));

        return new;
}

Примечания:

  • эта процедура уже включает барьеры памяти по мере необходимости.
  • действительны только в случае, если __программу kuser_помощник_версия >= 2 (с версии 2.6.12 ядра).

Это для использования с Linux с ARMv3 с помощью swp примитивно. У тебя должно быть очень древняя рука, чтобы не поддерживать это. Только данных отменить или отмена может привести к сбою вращения, поэтому ядро отслеживает этот адрес ~0xffff0fc0 и осуществляет пространство пользователя PC исправить-когда либо данных отменить или отмена происходит. Все библиотеки пользовательского пространства, поддерживающие ARMv5 и ниже, будут использовать это объект.

например, QtConcurrent использует это.