Chaque système d'exploitation moderne fournit aujourd'hui certaines opérations atomiques:
Interlocked*
API<machine/atomic.h>
<atomic.h>
<libkern/OSAtomic.h>
Quelque chose comme ça pour Linux?
Problèmes:
__sync_*
ne sont pas prises en charge sur toutes les plateformes (ARM) et ne sont pas prises en charge par le compilateur Intel.<asm/atomic.h>
ne doit pas être utilisé dans l'espace utilisateur et je ne l'ai pas utilisé avec succès du tout. De plus, je ne suis pas sûr que cela fonctionne avec le compilateur Intel.Aucune suggestion?
Je sais qu'il y a beaucoup de questions connexes mais certaines d'entre elles portent sur __sync*
, ce qui n'est pas faisable pour moi (ARM) et d'autres sur asm/atomic.h
.
Peut-être y a-t-il une bibliothèque d'assemblage intégrée qui fait cela pour GCC (ICC supporte l'assemblage de gcc)?
Modifier:
Il existe une solution très partielle pour les opérations d'ajout uniquement (permet d'implémenter un compteur atomique mais pas de verrouiller les structures libres qui requièrent un CAS):
Si vous utilisez libstc++
(Intel Compiler utilise libstdc++
), vous pouvez utiliser __gnu_cxx::__exchange_and_add
celui défini dans <ext/atomicity.h>
ou <bits/atomicity.h>
. Dépend de la version du compilateur.
Cependant, j'aimerais quand même voir quelque chose qui supporte CAS.
Les projets utilisent ceci:
http://packages.debian.org/source/sid/libatomic-ops
Si vous voulez des opérations simples telles que CAS, ne pouvez-vous pas simplement utiliser les implémentations spécifiques d'Arch en dehors du noyau, et vérifier Arch dans l'espace utilisateur avec autotools/cmake? En ce qui concerne les licences, bien que le noyau soit en GPL, je pense qu’il est discutable que l’assemblage en ligne de ces opérations soit fourni par Intel/AMD et non que le noyau dispose d’une licence. Ils se trouvent simplement sous une forme facilement accessible dans la source du noyau.
Les normes récentes (à partir de 2011) de C & C++ spécifient maintenant les opérations atomiques:
stdatomic.h
std::atomic
Quoi qu'il en soit, votre plate-forme ou votre compilateur peut ne pas prendre en charge ces nouveaux en-têtes et fonctionnalités.
Zut. J'allais suggérer les primitives GCC, puis vous avez dit qu'elles étaient interdites. :-)
Dans ce cas, je ferais un #ifdef
pour chaque combinaison architecture/compilateur qui vous tient à coeur et coderais le fichier asm en ligne. Et peut-être vérifier __GNUC__
ou une macro similaire et utiliser les primitives GCC si elles sont disponibles, car il semble beaucoup plus approprié de les utiliser. :-)
Vous allez avoir beaucoup de doublons et il sera peut-être difficile de vérifier l'exactitude, mais cela semble être la façon dont beaucoup de projets procèdent, et j'ai obtenu de bons résultats.
Quelques pièges qui m'ont mordu dans le passé: lors de l'utilisation de GCC, n'oubliez pas "asm volatile
" et clobbers pour "memory"
et "cc"
, etc.
Il y a un correctif pour GCC ici pour supporter les opérations atomiques ARM. Je ne vous aiderai pas sur Intel, mais vous pourrez examiner le code - le noyau actuel prend en charge les architectures plus anciennes ARM, et les nouvelles architectures ont les instructions intégrées.
Boost, qui possède une licence non intrusive, et d'autres frameworks proposent déjà des compteurs atomiques portables, à condition qu'ils soient pris en charge sur la plate-forme cible.
Les bibliothèques tierces sont bonnes pour nous. Et si, pour des raisons étranges, votre société vous interdit de les utiliser, vous pouvez quand même regarder comment elles procèdent (tant que la licence le permet pour votre usage) et mettre en œuvre ce que vous recherchez.
__sync*
est certainement (et a été) pris en charge par le compilateur Intel, car GCC a adopté ces versions à partir de là. Lire le premier paragraphe sur cette page . Voir aussi " Compilateur Intel® C++ pour Linux * Référence Intrinsics ", page 198. Il s’agit de 2006 et décrit exactement ces éléments intégrés.
En ce qui concerne la prise en charge de ARM, pour les anciens processeurs ARM: cela ne peut pas être fait entièrement en espace utilisateur, mais cela peut être fait en espace noyau (en désactivant les interruptions pendant l'opération), et je pense avoir lu quelque part que c'est pris en charge depuis un certain temps maintenant.
Selon ce bogue PHP , daté du 2011-10-08, __sync_*
n'échouera que le
Donc, avec GCC> 4,3 (et 4,7 est l'actuel), vous ne devriez pas avoir de problème avec ARMv6 ou plus récent. Vous ne devriez pas avoir de problème avec ARMv5 aussi longtemps que la compilation pour Linux.
J'ai récemment mis en œuvre une telle chose et j'ai été confronté aux mêmes difficultés que vous. Ma solution était fondamentalement la suivante:
cmpxch
avec __asm__
pour les autres architectures (ARM est un peu plus complexe que cela) Il suffit de faire cela pour une taille possible, par exemple sizeof(int)
.inline
Sur Debian/Ubuntu, recommandez ...
Sudo apt-get installez libatomic-ops-dev
exemples: http://www.hpl.hp.com/research/linux/atomic_ops/example.php4
Compatible GCC et ICC.
comparé aux blocs de construction Intel Thread (TBB), utilisant atomic <T>, libatomic-ops-dev est deux fois plus rapide! (Compilateur Intel)
Test sur les threads producteur-consommateur Ubuntu i7, en connectant 10 millions de pouce dans une connexion de mémoire tampon en 0,5 seconde au lieu de 1,2 seconde pour TBB
Et facile à utiliser, par exemple.
tête AO_t volatile;
AO_fetch_and_add1 (& head);
Voir: kernel_user_helpers.txt ou entry-arm.c et cherchez __kuser_cmpxchg
. Comme indiqué dans les commentaires de other ARM versions Linux,
Emplacement: 0xffff0fc0 Prototype de référence: int __kuser_cmpxchg (int32_t oldval, int32_t newval, volatile int32_t * ptr); Entrée: r0 = oldval r1 = newval r2 = ptr lr = adresse de retour Sortie: r0 = code de succès (zéro ou différent de zéro) Indicateur C = défini si r0 == 0, efface si r0! = 0 Registres obstrués: r3, ip, flags Définition: Stockez atomiquement newval dans * ptr uniquement si * ptr est égal à oldval . Retourne zéro si * ptr a été changé ou différent de zéro si aucun échange n'a eu lieu . Le drapeau C est également défini si * ptr a été modifié pour permettre l’assemblage optimisation dans le code d'appel . Exemple d'utilisation:
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;
}
Remarques:
Ceci est destiné à être utilisé avec Linux avec ARMv3 en utilisant la primitive swp
. Vous devez avoir un très ancien ARM = pour ne pas supporter cela. Seul un {annulation des données} ou interruption peut entraîner l'échec de la rotation. Le noyau surveille donc cette adresse ~ 0xffff0fc0 et effectue un espace utilisateur. _ PC
corrige quand un abandon de données ou un interruption se produit. Toutes les bibliothèques d’espace utilisateur prenant en charge ARMv5 et versions antérieures utiliseront cette fonctionnalité.
Par exemple, QtConcurrent l'utilise.