bibliotecas não-reentrantes na memória compartilhada?

2

Eu encontrei este Q & A dizendo bibliotecas compartilhadas pode ser compartilhado entre processos usando memória compartilhada. Parece impossível, no entanto, compartilhar código entre processos sem algumas restrições bastante severas sobre o tipo de código que pode ser compartilhado. Estou pensando em bibliotecas com funções C não-reentrantes cuja saída depende dos valores de variáveis globais ou variáveis estáticas dentro de seu corpo de definição. Como este.

int really_really_nonreentrant(void x)
{
    static int i = 0;
    i++;
    return i;
}

Uma biblioteca com uma função como essa retornará sequências crescentes separadas para cada processo que a utiliza, então definitivamente parece que o código não está sendo compartilhado entre processos. Real_really_nonreentrant () está sendo separado das funções de reentrada, ou está sendo mantido na maioria das vezes com as outras funções com apenas static int i sendo separado? Ou a biblioteca inteira está sendo mantida fora da memória compartilhada porque essa função é não-refulgente?

Em última análise, quanto é necessário fazer para garantir que a biblioteca de uma pessoa seja alocada para a memória compartilhada?

    
por enigmaticPhysicist 11.03.2016 / 11:34

1 resposta

3

Acredito que a resposta realmente curta é que os compiladores do Linux organizam o código em partes, pelo menos, um deles é apenas código puro e, portanto, pode ser mapeado na memória em mais de um espaço de endereço do processo. Todos os globals são mapeados de tal forma que cada processo recebe sua própria cópia.

Você pode ver isso usando readelf ou objdump , mas readelf dá uma imagem mais clara, eu acho.

Aqui está uma saída de readelf -e /usr/lib/libc.so.6 . Essa é a biblioteca C, provavelmente mapeada em quase todos os processos. A parte relevante de readelf output (embora tudo seja interessante) é o cabeçalho do programa:

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00140 0x00140 R E 0x4
  INTERP         0x164668 0x00164668 0x00164668 0x00017 0x00017 R   0x4
      [Requesting program interpreter: /usr/lib/ld-linux.so.2]
  LOAD           0x000000 0x00000000 0x00000000 0x1adfc4 0x1adfc4 R E 0x1000
  LOAD           0x1ae220 0x001af220 0x001af220 0x02c94 0x057c4 RW  0x1000
  DYNAMIC        0x1afd90 0x001b0d90 0x001b0d90 0x000f8 0x000f8 RW  0x4
  NOTE           0x000174 0x00000174 0x00000174 0x00044 0x00044 R   0x4
  TLS            0x1ae220 0x001af220 0x001af220 0x00008 0x00048 R   0x4
  GNU_EH_FRAME   0x164680 0x00164680 0x00164680 0x06124 0x06124 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x1ae220 0x001af220 0x001af220 0x01de0 0x01de0 R   0x1

As duas linhas LOAD são as únicas partes do arquivo que são mapeadas diretamente na memória. O primeiro cabeçalho LOAD mapeia uma parte de /usr/lib/libc.so.6 na memória com permissões R e E: leia e execute. Esse é o código. Os recursos de hardware impedem que um programa grave nessa parte da memória, de modo que todos os programas podem compartilhar as mesmas páginas de memória física real. O kernel pode configurar o hardware para mapear a mesma memória física em todos os processos.

O segundo cabeçalho LOAD está marcado como RW - ler e gravar. Esta é a parte com variáveis globais que a biblioteca C usa. Cada processo obtém sua própria cópia na memória física, mapeada no espaço de endereço desse processo com as permissões de hardware configuradas para permitir leitura e gravação. Essa seção não é compartilhada.

Você pode ver esses mapeamentos de memória em um processo em execução usando o sistema de arquivos /proc . Um bom comando para ilustrar: cat /proc/self/maps . Isso lista todos os mapeamentos de memória que o processo cat possui e de quais arquivos o kernel os obteve.

No que diz respeito a quanto você precisa fazer para garantir que sua função seja alocada para a memória que é mapeada em diferentes processos, é praticamente tudo para as sinalizações que você dá ao compilador. Código destinado a bibliotecas compartilhadas ".so" é compilado "independente de posição". O código independente de posição faz coisas como referir-se a localizações de memória de variáveis com desvios em relação à instrução atual e salta ou ramifica para localizações relativas à instrução atual, em vez de carregar de ou escrevendo para endereços absolutos e saltar para endereços absolutos. Isso significa que a parte "RE" LOAD de /usr/lib/libc.so e a peça "RW" só precisam ser carregadas em endereços que estejam à mesma distância em cada processo. Em seu código de exemplo, a variável static sempre será pelo menos um múltiplo de um tamanho de página à parte do código que a referencia, e sempre será carregada a distância no espaço de endereço de um processo devido à maneira como a LOAD Cabeçalhos ELF são dados.

Apenas uma nota sobre o termo "memória compartilhada": Há um sistema de memória compartilhada no nível do usuário, associado ao "Sistema de comunicação entre processos do System V". Essa é uma forma de mais de um processo compartilhar explicitamente uma parte da memória. É bastante complicado e obscuro para configurar e ficar correto. A memória compartilhada da qual estamos falando aqui é mais ou menos completamente invisível para qualquer processo do usuário. Seu código de exemplo não saberá a diferença se estiver sendo executado como código independente de posição compartilhado entre vários processos ou se for a única cópia de sempre.

    
por 11.03.2016 / 15:30