Domanda

Quando si utilizza la memoria condivisa, ogni processo può mmap regione condivisa in una zona diversa del rispettivo spazio di indirizzi. Ciò significa che quando si ripone puntatori all'interno della regione condiviso, è necessario memorizzarli come offset dell'inizio della regione condivisa. Purtroppo, questo complica l'uso di istruzioni atomiche (ad esempio, se si sta cercando di scrivere un bloccare algoritmo gratuito ). Per esempio, supponiamo di avere un gruppo di riferimento contato i nodi di memoria condivisa, creati da un singolo scrittore. Lo scrittore aggiorna periodicamente atomicamente un puntatore 'p' per puntare ad un nodo valido con conteggio di riferimento positivo. Lettori vogliono scrivere atomicamente a 'p' perché segnala l'inizio di un nodo (struct) il cui primo elemento è un conteggio di riferimento. Poiché p punta sempre a un nodo valido, incrementando il conteggio ref è sicuro, e rende sicuro di dereference 'p' e accesso altri membri. Tuttavia, tutto questo funziona solo quando tutto è nello stesso spazio di indirizzi. Se i nodi e il puntatore 'p' sono memorizzati nella memoria condivisa, quindi i clienti soffrono una condizione di competizione:

  1. x = lettura p
  2. y = x + offset
  3. Incremento refcount a y

Durante il passaggio 2, p può cambiare e x non possono più scegliere un nodo valido. L'unica soluzione che posso pensare è in qualche modo costringendo tutti i processi d'accordo su dove mappare la memoria condivisa, in modo che i puntatori reali piuttosto che offset possono essere memorizzati nella regione mmap'd. Esiste un modo per farlo? Vedo MAP_FIXED nella documentazione mmap, ma io non so come ho potuto scegliere un indirizzo che sarebbe sicuro.

Modifica: Utilizzo di assemblaggio in linea e il prefisso 'lock' su x86 forse è possibile costruire un "incremento PTR X con lunghezza variabile Y per valore Z"? opzioni equivalenti in altre architetture? Non ho scritto un sacco di assemblaggio, non so se esistono le istruzioni necessarie.

È stato utile?

Soluzione

Sul basso livello x86 inctruction atomica può fare tutto questo passi albero in una sola volta:

  1. x = lettura p
  2. y = x + offset Incremento
  3. refcount a 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
//

Altri suggerimenti

Questa è banale su un sistema UNIX; basta utilizzare le funzioni della memoria condivisa:

shgmet, shmat, shmctl, shmdt

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

  

shmat () attribuisce la memoria condivisa   segmento identificato dal shmid al   spazio degli indirizzi del processo chiamante.   L'indirizzo fissaggio è specificato da   shmaddr con uno dei seguenti   criteri:

     

Se shmaddr è NULL, il sistema sceglie   un opportuno indirizzo (inutilizzato) in cui   per fissare il segmento.

Basta specificare tuo indirizzo qui; per esempio. 0x20000000000

Se shmget () utilizzando la stessa chiave e le dimensioni in ogni processo, si otterrà lo stesso segmento di memoria condivisa. Se shmat () allo stesso indirizzo, gli indirizzi virtuali saranno gli stessi in tutti i processi. Il kernel non si preoccupa che cosa intervallo di indirizzi che si utilizza, a patto che non sia in conflitto con ovunque assegna normalmente le cose. (Se si lascia l'indirizzo, è possibile vedere la regione in generale che piace mettere le cose;. Inoltre, controlla gli indirizzi sullo stack e restituito da malloc () / new [])

In Linux, assicurarsi radice imposta SHMMAX in / proc / sys / kernel / shmmax a un gran numero sufficiente per ospitare i segmenti di memoria condivisa (di default è 32 MB).

Per quanto riguarda le operazioni atomiche, è possibile ottenere tutto dalla sorgente del kernel di Linux, per esempio

  

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));
}

versione a 64 bit:

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));
}

Abbiamo il codice che è simile alla descrizione problema. Usiamo un file memory-mapped, offset, e il blocco dei file. Non abbiamo trovato un'alternativa.

Non si deve aver paura di fare un indirizzo a caso, perché il kernel sarà solo rifiutare indirizzi che non gli piace (che sono in conflitto quelli). Vedere la mia risposta shmat() sopra, usando 0x20000000000

Con mmap:

  

void * mmap (void * addr, lunghezza size_t, int Prot, bandiere int, int fd, off_t offset);

     

Se addr non è NULL, allora il kernel   prende come un suggerimento su dove   posizionare la mappatura; su Linux, il   mappatura verrà creato al prossimo   più alto limite di pagina. L'indirizzo del   la nuova mappatura viene restituito come   risultato della chiamata.

     

L'argomento flags determina se   aggiornamenti alla mappatura sono visibili a   altri processi mappatura stesso   regione, e se gli aggiornamenti sono   trasportato fino alla sottostante   file. Questo comportamento è determinato dalla   compreso esattamente una delle seguenti opzioni   valori in bandiere:

     

MAP_SHARED Condividi questa mappatura.   Gli aggiornamenti per la mappatura sono visibili a   altri processi che mappano questo   file e sono portati fino alla   file sottostante. Il file non può   in realtà essere aggiornato fino msync (2) o   munmap () è chiamato.

     

ERRORI

     

EINVAL Non ci piace addr, lunghezza o   offset (per esempio, essi sono troppo grandi, o   Non allineati su un limite di pagina).

L'aggiunta l'offset al puntatore non crea il potenziale per una gara, esiste già. Dal momento che almeno due rami né x86 possono atomicamente leggere un puntatore poi accedere alla memoria si riferisce è necessario proteggere l'accesso puntatore con una serratura indipendentemente dal fatto che si aggiunge un offset.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top