Как выполнять атомарные операции в 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 счетчик, но не блокировка свободных структур, которые требуют CAS):

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

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

9 55

9 ответов:

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

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

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

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

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

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

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

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

некоторые готы, которые укусили меня в прошлом: при использовании GCC, не забывайте "asm volatile" и ударяет по "memory" и "cc" и т. д.

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

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

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

  • попробуйте обнаружить встроенные GCC с помощью функция макрос
  • если не доступно просто реализовать что-то вроде cmpxch с __asm__ для других архитектур (ARM немного сложнее, чем это). Просто сделайте это для одного возможного размера, например 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 is текущий), у вас не должно быть проблем с 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 производитель-потребитель потоки трубопроводов 10 миллионов дюймов вниз кольцевой буфер соединения в 0.5 сек в отличие от 1.2 сек для ТББ

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

volatile ao_t head;

AO_fetch_and_add1(&head);

посмотреть: kernel_user_helpers.txt или запись-рукой.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_helper_version >= 2 (начиная с версии ядра 2.6.12).

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

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