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:

  1. x = Leia P
  2. y = x + deslocamento
  3. 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.

Foi útil?

Solução

Em nível baixo, a inctrução atômica x86 pode fazer todas essas etapas de árvore de uma só vez:

  1. x = Leia P
  2. y = x + incremento de deslocamento
  3. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top