E 'possibile memorizzare i puntatori in memoria condivisa senza l'utilizzo di compensazioni?
-
21-09-2019 - |
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:
- x = lettura p
- y = x + offset
- 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.
Soluzione
Sul basso livello x86 inctruction atomica può fare tutto questo passi albero in una sola volta:
- x = lettura p
- y = x + offset Incremento
- 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.