Come eseguire operazioni atomiche su Linux che funzionano su x86, arm, GCC e icc?

Ogni sistema operativo moderno fornisce oggi alcune operazioni atomiche:

  • Windows ha l’API Interlocked*
  • FreeBSD ha
  • Solaris ha
  • Mac OS X ha

Qualcosa del genere per Linux?

  • Ho bisogno che funzioni sulla maggior parte delle piattaforms supportate da Linux, tra cui: x86, x86_64 e arm .
  • Ho bisogno che funzioni almeno su GCC e Intel Compiler.
  • Non ho bisogno di usare la libreria 3rd Par come glib o qt.
  • Ho bisogno che funzioni in C ++ (C non richiesto)

Problemi:

  • I compilatori atomici GCC __sync_* non sono supportati su tutte le piattaforms (ARM) e non sono supportati dal compilatore Intel.
  • AFAIK non dovrebbe essere usato nello spazio utente e non l’ho usato con successo. Inoltre, non sono sicuro che funzioni con il compilatore Intel.

Eventuali suggerimenti?

So che ci sono molte domande correlate ma alcune di esse puntano a __sync* che non è fattibile per me (ARM) e alcuni puntano a asm/atomic.h .

Forse c’è una libreria di assembly inline che fa questo per GCC (ICC supporta l’assembly gcc)?

Modificare:

Esiste una soluzione molto parziale solo per le operazioni di aggiunta (consente di implementare il contatore atomico ma non bloccare le strutture libere che richiedono CAS):

Se si utilizza libstc++ (Intel Compiler usa libstdc++ ), allora è ansible utilizzare __gnu_cxx::__exchange_and_add definito in o . Dipende dalla versione del compilatore.

Comunque mi piacerebbe ancora vedere qualcosa che supporti CAS.

I progetti stanno usando questo:

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

Se vuoi operazioni semplici come CAS, non puoi semplicemente usare le implementazioni specifiche per l’arc del kernel e fare controlli di arch nel spazio dell’utente con autotools / cmake? Per quanto riguarda la licenza, sebbene il kernel sia GPL, penso che sia discutibile che l’assembly inline per queste operazioni sia fornito da Intel / AMD, non che il kernel abbia una licenza su di essi. Si trovano solo in una forma facilmente accessibile nel sorgente del kernel.

Gli standard recenti (dal 2011) di C & C ++ ora specificano le operazioni atomiche:

  • C11: stdatomic.h
  • C ++ 11: std::atomic

Indipendentemente da ciò, la tua piattaforma o il compilatore potrebbero non supportare queste intestazioni e funzionalità più recenti.

Darn. Stavo per suggerire i primitivi GCC, poi hai detto che erano off limits. 🙂

In tal caso, farei un #ifdef per ogni combinazione di architettura / compilatore che ti interessa e codifica l’asm inline. E forse controlla __GNUC__ o qualche macro simile e usa i primitivi GCC se sono disponibili, perché sembra molto più giusto usarli. 🙂

Avrai un sacco di duplicati e potrebbe essere difficile verificarne la correttezza, ma questo sembra essere il modo in cui molti progetti lo fanno, e ho ottenuto buoni risultati con esso.

Alcuni trucchi che mi hanno morso in passato: quando si utilizza GCC, non dimenticare ” asm volatile ” e clobbers per "memory" e "cc" , ecc.

Boost, che ha una licenza non intrusiva, e altri framework offrono già contatori atomici portatili – purché siano supportati sulla piattaforma di destinazione.

Le librerie di terze parti sono buone per noi. E se per strane ragioni la tua azienda ti proibisce di usarli, puoi comunque dare un’occhiata a come procede (purché la licenza lo consenta per il tuo uso) per implementare ciò che stai cercando.

Recentemente ho fatto un’implementazione di una cosa del genere e mi sono confrontato con le stesse difficoltà di te. La mia soluzione era sostanzialmente la seguente:

  • prova a rilevare i generatori gcc con la macro caratteristica
  • se non è disponibile, implementa qualcosa come cmpxch con __asm__ per le altre architetture (ARM è un po ‘più complicato di così). Basta farlo per una dimensione ansible, ad esempio sizeof(int) .
  • implementare tutte le altre funzionalità in cima a quella o due primitive con funzioni inline

C’è una patch per GCC qui per supportare le operazioni atomiche ARM. Non ti aiuterò su Intel, ma potresti esaminare il codice – c’è il recente supporto del kernel per le architetture ARM più vecchie, e quelle più recenti hanno le istruzioni incorporate, quindi dovresti essere in grado di build qualcosa che funzioni.

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

__sync* certamente è (ed è stato) supportato dal compilatore Intel, perché GCC ha adottato questi build-in da lì. Leggi il primo paragrafo in questa pagina . Vedi anche ” Riferimento per Intel® C ++ Compiler for Linux * Intrinsics “, pagina 198. È del 2006 e descrive esattamente quei built-in.

Per quanto riguarda il supporto ARM, per le vecchie CPU ARM: non può essere fatto interamente nello userspace, ma può essere fatto in kernelspace (disabilitando gli interrupt durante l’operazione), e penso di aver letto da qualche parte che è supportato da un po ‘di tempo.

Secondo questo bug di PHP , datato 2011-10-08, __sync_* non funzionerà

  • PA-RISC con qualcosa di diverso da Linux
  • SPARCv7 e inferiore
  • ARM con GCC <4.3
  • ARMv5 e versioni precedenti con qualcosa di diverso da Linux
  • mips1

Quindi con GCC> 4.3 (e 4.7 è quello attuale), non dovresti avere problemi con ARMv6 e successivi. Non dovresti avere alcun problema con ARMv5 fino alla compilazione per Linux.

Su Debian / Ubuntu consiglia …

sudo apt-get install libatomic-ops-dev

esempi: http://www.hpl.hp.com/research/linux/atomic_ops/example.php4

Compatibile con GCC e ICC.

rispetto a Intel Thread Building Blocks (TBB), usando atomico , libatomic-ops-dev è più veloce del doppio! (Compilatore Intel)

Test su Ubuntu i7, thread di produzione e consumer che collegano 10 milioni di ints a una connessione buffer ad anello in 0,5 secondi rispetto a 1.2sec per TBB

E facile da usare ad es

testa volatile AO_t;

AO_fetch_and_add1 (e testa);

Vedi: kernel_user_helpers.txt o entry-arm.c e cerca __kuser_cmpxchg . Come visto nei commenti di altre versioni di ARM Linux,

kuser_cmpxchg

 Posizione: 0xffff0fc0

 Prototipo di riferimento:

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

 Ingresso:

   r0 = oldval
   r1 = newval
   r2 = ptr
   lr = indirizzo di ritorno

 Produzione:

   r0 = codice di successo (zero o non zero)
   C flag = set if r0 == 0, clear if r0! = 0

 Registri Clobbered:

   r3, ip, bandiere

 Definizione:

   Memorizza atomicamente newval in * ptr solo se * ptr è uguale a oldval.
   Restituisce zero se * ptr è stato cambiato o diverso da zero se non è avvenuto alcuno scambio.
   Il flag C viene anche impostato se * ptr è stato modificato per consentire l'assemblaggio
   ottimizzazione nel codice chiamante.

 Esempio di utilizzo:
  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; } 

Gli appunti:

  • Questa routine include già le barriere di memoria necessarie.
  • Valido solo se __kuser_helper_version> = 2 (dalla versione del kernel 2.6.12).

Questo è per l’uso con Linux con ARMv3 usando la primitiva swp . Devi avere un ARM molto antico per non supportarlo. Solo una interruzione o interruzione dei dati può causare il fallimento della rotazione, quindi il kernel controlla questo indirizzo ~ 0xffff0fc0 ed esegue una correzione dello spazio utente PC quando si verifica un’interruzione di dati o un’interruzione . Tutte le librerie dello spazio utente che supportano ARMv5 e inferiore useranno questa funzione.

Ad esempio, QtConcurrent utilizza questo.