É possível armazenar ponteiros na memória compartilhada sem usar desvios?
-
21-09-2019 - |
Pergunta
Ao usar a memória compartilhada, cada processo pode Mmap a região compartilhada em uma área diferente de seu respectivo espaço de endereço. Isso significa que, ao armazenar indicadores na região compartilhada, você precisa Armazene -os como compensações do início da região compartilhada. Infelizmente, isso complica o uso de instruções atômicas (por exemplo, se você estiver tentando escrever um Bloquear o algoritmo livre). Por exemplo, digamos que você tenha um monte de nós contados de referência na memória compartilhada, criada por um único escritor. O escritor atualiza periodicamente atomicamente um ponteiro 'P' para apontar para um nó válido com contagem de referência positiva. Os leitores querem escrever atomicamente para 'P' porque aponta para o início de um nó (uma estrutura) cujo primeiro elemento é uma contagem de referência. Como P sempre aponta para um nó válido, incrementar a contagem de ref é seguro e torna seguro desreferenciar 'P' e acessar outros membros. No entanto, tudo isso só funciona quando tudo está no mesmo espaço de endereço. Se os nós e o ponteiro 'P' forem armazenados na memória compartilhada, os clientes sofrem uma condição de corrida:
- x = Leia P
- y = x + deslocamento
- Incremento refaco em y
Durante a etapa 2, p pode mudar e x pode mais apontar para um nó válido. A única solução alternativa em que consigo pensar é, de alguma forma, forçar todos os processos a concordar sobre onde mapear a memória compartilhada, para que ponteiros reais, em vez de compensações possam ser armazenados na região Mmap'd. Existe alguma maneira de fazer isso? Vejo map_fixed na documentação do MMAP, mas não sei como poderia escolher um endereço que seria seguro.
Editar: Usando a montagem embutida e o prefixo 'Lock' no x86, talvez seja possível construir um "incremento PTR X com deslocamento y pelo valor z"? Opções equivalentes em outras arquiteturas? Não escrevi muita montagem, não sei se existem instruções necessárias.
Solução
Em nível baixo, a inctrução atômica x86 pode fazer todas essas etapas de árvore de uma só vez:
- x = Leia P
- y = x + incremento de deslocamento
- Refcount em 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 //
Outras dicas
Isso é trivial em um sistema UNIX; Basta usar as funções de memória compartilhada:
shgmet, shmat, shmctl, shmdt
void *shmat (int shmid, const void *shmaddr, int shmflg);
O shmat () anexa o segmento de memória compartilhado identificado por shmid ao espaço de endereço do processo de chamada. O endereço de anexo é especificado por shmaddr com um dos seguintes critérios:
Se o shmaddr for nulo, o sistema escolhe um endereço adequado (não utilizado) para anexar o segmento.
Basta especificar seu próprio endereço aqui; por exemplo 0x20000000000
Se você shmget () usando a mesma chave e tamanho em todos os processos, obterá o mesmo segmento de memória compartilhada. Se você shmat () no mesmo endereço, os endereços virtuais serão os mesmos em todos os processos. O kernel não se importa com o intervalo de endereços que você usa, desde que não entre em conflito com onde normalmente atribui as coisas. (Se você deixar de fora o endereço, poderá ver a região geral que ela gosta de colocar as coisas; também, verifique os endereços na pilha e devolvidos de Malloc () / New [].).)
No Linux, verifique se a raiz define shmmax em/proc/sys/kernel/shmmax em um número grande o suficiente para acomodar seus segmentos de memória compartilhada (o padrão é de 32 MB).
Quanto às operações atômicas, você pode obtê -las da fonte do kernel Linux, por exemplo,
incluir/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));
}
Versão de 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));
}
Temos código semelhante à sua descrição do problema. Usamos um arquivo mapeado de memória, compensações e travamento de arquivos. Não encontramos uma alternativa.
Você não deve ter medo de compensar um endereço aleatoriamente, porque o kernel apenas rejeitará os endereços que não gosta (aquele que conflito). Veja meu shmat()
Resposta acima, usando 0x20000000000
Com Mmap:
void *mmap (void *addr, size_t comprimento, int prot, int sinalizadores, int fd, off_t offset);
Se Addr não for nulo, o kernel o leva como uma dica sobre onde colocar o mapeamento; No Linux, o mapeamento será criado no próximo limite de página superior. O endereço do novo mapeamento é retornado como resultado da chamada.
O argumento dos sinalizadores determina se as atualizações do mapeamento são visíveis para outros processos que o mapeamento da mesma região e se as atualizações são transportadas para o arquivo subjacente. Esse comportamento é determinado incluindo exatamente um dos seguintes valores em sinalizadores:
Map_shared Compartilhe este mapeamento. As atualizações do mapeamento são visíveis para outros processos que mapeiam esse arquivo e são transportados até o arquivo subjacente. O arquivo não pode realmente ser atualizado até que o MSYNC (2) ou o MUNMAP () seja chamado.
Erros
Einval Não gostamos de addr, comprimento ou deslocamento (por exemplo, eles são muito grandes ou não alinhados em um limite de página).
Adicionar o deslocamento ao ponteiro não cria o potencial de uma corrida, ele já existe. Como pelo menos nem o ARM nem o X86 podem ler atomicamente um ponteiro e acessar a memória que ele se refere a você precisar proteger o acesso ao ponteiro com uma fechadura, independentemente de você adicionar um deslocamento.