Question

Lors de l'utilisation de la mémoire partagée, chaque processus peut mmapper la région commune dans une zone différente de son espace d'adresse respectif. Cela signifie que lors de l'enregistrement des pointeurs dans la région partagée, vous devez les stocker sous forme de compensation du début de la zone partagée. Malheureusement, ce qui complique l'utilisation d'instructions atomiques (par exemple si vous essayez d'écrire un verrouiller algorithme libre ). Par exemple, supposons que vous avez un groupe de noeuds compté de référence dans la mémoire partagée, créée par un seul auteur. L'auteur met à jour périodiquement atomiquement un pointeur « p » pour pointer vers un nœud valide avec nombre de références positives. Les lecteurs veulent atomiquement écrire « p » parce qu'il pointe vers le début d'un noeud (a struct) dont le premier élément est un compte de référence. Comme p pointe toujours vers un noeud valide, incrémenter le compteur ref est sûr, et il est sûr de déréférencer « p » et accéder à d'autres membres. Cependant, cela fonctionne tout seul quand tout est dans le même espace d'adressage. Si les noeuds et le pointeur « p » sont stockés dans la mémoire partagée, les clients souffrent d'une condition de course:

  1. x = lecture p
  2. y = x + offset
  3. Incrémenter refcount à y

Au cours de l'étape 2, p peut changer et x peut ne pointent plus vers un noeud valide. La seule solution que je peux penser est en quelque sorte contraint tous les processus d'accord sur la carte où la mémoire partagée, de sorte que les vrais pointeurs plutôt que des compensations peuvent être stockées dans la région mmap'd. Y'a-t'il un quelconque moyen d'y arriver? Je vois MAP_FIXED dans la documentation mmap, mais je ne sais pas comment je pourrais choisir une adresse qui serait en sécurité.

Edit: Utilisation de l'assemblage en ligne et le préfixe de verrouillage 'sur x86 peut-être il est possible de construire un « X avec incrément ptr décalage Y par la valeur Z »? Les options équivalentes sur d'autres architectures? Je n'ai pas écrit beaucoup de montage, ne sais pas si les instructions nécessaires existent.

Était-ce utile?

La solution

Le faible niveau x86 inctruction atomique peut faire toutes ces étapes d'arbre à la fois:

  1. x = lecture p
  2. y = x + décalage incrément
  3. refcount à y
//
      mov  edi, Destination
      mov  edx, DataOffset
      mov  ecx, NewData
 @Repeat:
      mov  eax, [edi + edx]    //load OldData
//Here you can also increment eax and save to [edi + edx]          
      lock cmpxchg dword ptr [edi + edx], ecx
      jnz  @Repeat
//

Autres conseils

Ceci est trivial sur un système UNIX; il suffit d'utiliser les fonctions de mémoire partagée:

shgmet, shmat, shmctl, shmdt

void * shmat (int shmid, const void * shmaddr, int shmflg);

  

shmat () attache la mémoire partagée   le segment identifié par shmid au   l'espace d'adressage du processus d'appel.   L'adresse est spécifiée par la fixation   shmaddr avec un des éléments suivants   critères:

     

Si shmaddr est NULL, le système choisit   une adresse convenable (non utilisé) au cours de laquelle   pour fixer le segment.

Il suffit de spécifier votre adresse ici; par exemple. 0x20000000000

Si vous shmget () en utilisant la même clé et la taille dans tous les processus, vous obtiendrez le même segment de mémoire partagée. Si vous shmat () à la même adresse, les adresses virtuelles seront les mêmes dans tous les processus. Le noyau ne se soucie pas plage d'adresses que vous utilisez, aussi longtemps qu'il ne se heurte pas partout où il affecte normalement les choses. (Si vous quittez l'adresse, vous pouvez voir la région générale qu'il aime mettre les choses;. Également, vérifier les adresses sur la pile et retour de malloc () / new [])

Sur Linux, assurez-vous que la racine définit SHMMAX dans / proc / sys / kernel / shmmax à un nombre assez grand pour accueillir vos segments de mémoire partagée (valeur par défaut est 32 Mo).

En ce qui concerne les opérations atomiques, vous pouvez les obtenir tous de la source du noyau Linux, par exemple

  

include / asm-x86 / atomic_64.h

/*
 * Make sure gcc doesn't try to be clever and move things around
 * on us. We need to use _exactly_ the address the user gave us,
 * not some alias that contains the same information.
 */
typedef struct {
        int counter;
} atomic_t;

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
#define atomic_read(v)          ((v)->counter)

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */
#define atomic_set(v, i)                (((v)->counter) = (i))


/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic_add(int i, atomic_t *v)
{
        asm volatile(LOCK_PREFIX "addl %1,%0"
                     : "=m" (v->counter)
                     : "ir" (i), "m" (v->counter));
}

version 64 bits:

typedef struct {
        long counter;
} atomic64_t;

/**
 * atomic64_add - add integer to atomic64 variable
 * @i: integer value to add
 * @v: pointer to type atomic64_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic64_add(long i, atomic64_t *v)
{
        asm volatile(LOCK_PREFIX "addq %1,%0"
                     : "=m" (v->counter)
                     : "er" (i), "m" (v->counter));
}

Nous avons un code qui est similaire à la description du problème. Nous utilisons un fichier mappé en mémoire, les décalages et le verrouillage des fichiers. Nous n'avons pas trouvé une solution de rechange.

Vous ne devriez pas avoir peur de faire une adresse au hasard, parce que le noyau vient de rejeter les adresses qu'il n'aime pas (ceux qui sont en conflit). Voir ma réponse ci-dessus shmat(), en utilisant 0x20000000000

Avec mmap:

  

void * mmap (void * addr, longueur size_t, int prot, drapeaux int, int fd, off_t offset);

     

Si addr est NULL, le noyau   il prend comme une indication sur l'endroit où   placer le mappage; sur Linux, le   la cartographie sera créé à la prochaine   plus limite de page. L'adresse de la   le nouveau mappage est renvoyée comme   résultat de l'appel.

     

L'argument flags détermine si   mises à jour de la cartographie sont visibles   d'autres procédés du même cartographie   région, et si les mises à jour sont   porté par le sous-jacent   fichier. Ce comportement est déterminé par   y compris exactement un des éléments suivants   valeurs drapeaux:

     

MAP_SHARED Partager cette cartographie.   Les mises à jour du mappage sont visibles   d'autres processus utilisant   fichier, et sont portés par la   fichier sous-jacent. Le fichier ne peut pas   effectivement être mis à jour jusqu'à ce que msync (2) ou   munmap () est appelée.

     

ERREURS

     

EINVAL Nous n'aimons pas adr, la longueur ou   décalage (par exemple, ils sont trop grandes, ou   pas aligné sur une limite de page).

Ajout du décalage au pointeur ne crée pas le potentiel pour une course, il existe déjà. Depuis au moins ni ARM ni x86 peuvent lire un pointeur atomiquement alors l'accès de la mémoire, il se réfère à vous devez protéger l'accès du pointeur avec un verrou indépendamment du fait que vous ajoutez un décalage.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top