Подтвердить что ты не робот

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

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

  • Windows имеет Interlocked* API
  • FreeBSD имеет <machine/atomic.h>
  • Solaris имеет <atomic.h>
  • Mac OS X имеет <libkern/OSAtomic.h>

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

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

Вопросы:

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

Любые предложения?

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

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

Изменить:

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Согласно эта ошибка PHP, датированная 2011-10-08, __sync_* будет терпеть неудачу на

  • PA-RISC с чем-либо, кроме Linux
  • SPARCv7 и ниже
  • ARM с GCC < 4,3
  • ARMv5 и ниже с чем-либо, кроме Linux
  • MIPS1

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

Ответ 8

В 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), используя атомный <T> , libatomic-ops-dev в два раза быстрее! (Компилятор Intel)

Тестирование на потоках производителей-потребителей Ubuntu i7 10 миллионов ints вниз по кольцевому буферному соединению в 0,5 сек. вместо 1,2 сек для TBB

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

volatile AO_t head;

AO_fetch_and_add1 (& головка);

Ответ 9

Смотрите: 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_helper_version >= 2 (из версии 2.6.12 ядра).

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

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