Carregamento de bibliotecas compartilhadas e uso de RAM


40

Eu estou pensando sobre como o Linux gerencia bibliotecas compartilhadas. (na verdade estou falando de Maemo Fremantle, uma distribuição baseada no Debian lançada em 2009 com 256 MB de RAM).

Vamos supor que temos dois executáveis ​​vinculados ao libQtCore.so.4 e usando seus símbolos (usando suas classes e funções). Por uma questão de simplicidade, vamos chamá-los ae b. Assumimos que ambos os executáveis ​​estão vinculados às mesmas bibliotecas.

Primeiro nós lançamos a. A biblioteca deve ser carregada. Ele é carregado na íntegra ou na memória apenas na parte necessária (como não usamos cada classe, apenas o código referente às classes usadas está sendo carregado)?

Então nós lançamos b. Assumimos que aainda está em execução. blinks para libQtCore.so.4 também e usa algumas das classes que ausa, mas também algumas que não são usadas por a. A biblioteca será carregada duas vezes (separadamente ae separadamente b)? Ou eles usarão o mesmo objeto já na RAM. Se bnão usar novos símbolos e ajá estiver em execução, a RAM usada pelas bibliotecas compartilhadas aumentará? (Ou a diferença será insignificante)

Respostas:


53

NOTA: Eu vou assumir que sua máquina possui uma unidade de mapeamento de memória (MMU). Existe uma versão do Linux (µClinux) que não requer uma MMU e esta resposta não se aplica a ela.

O que é uma MMU? É hardware - parte do processador e / ou controlador de memória. Para entender o link da biblioteca compartilhada, você não precisa entender exatamente como uma MMU funciona, apenas que uma MMU permite que haja uma diferença entre os endereços de memória lógica (os usados ​​pelos programas) e os físicos.endereços de memória (aqueles realmente presentes no barramento de memória). A memória é dividida em páginas, geralmente com tamanho de 4K no Linux. Com páginas de 4k, os endereços lógicos 0 a 4095 são a página 0, os endereços lógicos 4096 a 8191 são a página 1 etc. A MMU os mapeia para páginas físicas da RAM, e cada página lógica pode ser tipicamente mapeada para 0 ou 1 páginas físicas. Uma determinada página física pode corresponder a várias páginas lógicas (é assim que a memória é compartilhada: várias páginas lógicas correspondem à mesma página física). Observe que isso se aplica independentemente do sistema operacional; é uma descrição do hardware.

Na troca de processo, o kernel altera os mapeamentos de página da MMU, para que cada processo tenha seu próprio espaço. O endereço 4096 no processo 1000 pode ser (e geralmente é) completamente diferente do endereço 4096 no processo 1001.

Praticamente sempre que você vê um endereço, é um endereço lógico. Os programas de espaço do usuário quase nunca lidam com endereços físicos.

Agora, existem várias maneiras de construir bibliotecas também. Digamos que um programa chama a função foo()na biblioteca. A CPU não sabe nada sobre símbolos ou chamadas de função - apenas sabe como pular para um endereço lógico e executar o código que encontrar lá. Existem várias maneiras de fazer isso (e coisas semelhantes se aplicam quando uma biblioteca acessa seus próprios dados globais, etc.):

  1. Poderia codificar algum endereço lógico para chamá-lo. Isso requer que a biblioteca sempre seja carregada exatamente no mesmo endereço lógico. Se duas bibliotecas exigirem o mesmo endereço, a vinculação dinâmica falhará e você não poderá iniciar o programa. As bibliotecas podem exigir outras bibliotecas, portanto, basicamente, exige que todas as bibliotecas do sistema tenham endereços lógicos exclusivos. É muito rápido, no entanto, se funcionar. (Foi assim que a.out fez as coisas, e o tipo de configuração que a pré-ligação faz, mais ou menos).
  2. Ele pode codificar um endereço lógico falso e solicitar ao vinculador dinâmico que edite no endereço apropriado ao carregar a biblioteca. Isso custa um pouco de tempo ao carregar as bibliotecas, mas depois disso é muito rápido.
  3. Poderia adicionar uma camada de indireção: use um registro da CPU para manter o endereço lógico no qual a biblioteca está carregada e, em seguida, acesse tudo como um deslocamento desse registro. Isso impõe um custo de desempenho em cada acesso.

Praticamente ninguém mais usa o número 1, pelo menos não em sistemas de uso geral. Manter essa lista de endereços lógica exclusiva é impossível em sistemas de 32 bits (não há o suficiente para circular) e um pesadelo administrativo em sistemas de 64 bits. No entanto, a pré-vinculação faz isso por sistema.

O uso de # 2 ou # 3 depende se a biblioteca foi criada com a opção GCC -fPIC(código independente de posição). # 2 está sem, # 3 está com. Geralmente, as bibliotecas são construídas com -fPIC, então o número 3 é o que acontece.

Para obter mais detalhes, consulte Como escrever bibliotecas compartilhadas (PDF) , de Ulrich Drepper .

Então, finalmente, sua pergunta pode ser respondida:

  1. Se a biblioteca é construída com -fPIC (como quase certamente deveria ser), a grande maioria das páginas é exatamente a mesma para todos os processos que a carregam. Seus processos ae bpode muito bem carregar a biblioteca em endereços lógicos diferentes, mas aqueles irá apontar para as mesmas páginas físicas: a memória será compartilhada. Além disso, os dados na RAM correspondem exatamente ao que está no disco, para que possam ser carregados somente quando necessário pelo manipulador de falhas da página.
  2. Se a biblioteca for criada sem -fPIC , então a maioria das páginas da biblioteca precisará de edições de link e será diferente. Portanto, eles devem ser páginas físicas separadas (pois contêm dados diferentes). Isso significa que eles não são compartilhados. As páginas não correspondem ao que está no disco, portanto, não ficaria surpreso se a biblioteca inteira estiver carregada. Obviamente, pode ser posteriormente trocado para o disco (no arquivo de troca).

Você pode examinar isso com a pmapferramenta ou diretamente, verificando vários arquivos /proc. Por exemplo, aqui está uma saída (parcial) de pmap -xdois s diferentes recém-gerados bc. Observe que os endereços mostrados pelo pmap são, como típicos, endereços lógicos:

pmap -x 14739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f81803ac000     244     176       0 r-x-- libreadline.so.6.2
00007f81803e9000    2048       0       0 ----- libreadline.so.6.2
00007f81805e9000       8       8       8 r---- libreadline.so.6.2
00007f81805eb000      24      24      24 rw--- libreadline.so.6.2


pmap -x 17739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f784dc77000     244     176       0 r-x-- libreadline.so.6.2
00007f784dcb4000    2048       0       0 ----- libreadline.so.6.2
00007f784deb4000       8       8       8 r---- libreadline.so.6.2
00007f784deb6000      24      24      24 rw--- libreadline.so.6.2

Você pode ver que a biblioteca está carregada em várias partes e pmap -xfornece detalhes sobre cada uma separadamente. Você notará que os endereços lógicos são diferentes entre os dois processos; você esperaria razoavelmente que eles sejam os mesmos (já que é o mesmo programa em execução e os computadores geralmente são previsíveis assim), mas existe um recurso de segurança chamado randomização de layout do espaço de endereço que os randomiza intencionalmente.

Você pode ver pela diferença no tamanho (Kbytes) e no tamanho residente (RSS) que o segmento inteiro da biblioteca não foi carregado. Finalmente, você pode ver que, para os mapeamentos maiores, sujo é 0, o que significa que corresponde exatamente ao que está no disco.

Você pode executar novamente pmap -XX, e isso mostrará a você - dependendo da versão do kernel em execução, já que a saída -XX varia de acordo com a versão do kernel - que o primeiro mapeamento possui Shared_Clean176, o que corresponde exatamente ao RSS. Sharedmemória significa que as páginas físicas são compartilhadas entre vários processos e, uma vez que corresponde ao RSS, significa que toda a biblioteca que está na memória é compartilhada (consulte o Veja também abaixo para obter mais explicações sobre compartilhado versus privado):

pmap -XX 17739
         Address Perm   Offset Device   Inode  Size  Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked                   VmFlagsMapping
    7f784dc77000 r-xp 00000000  fd:00 1837043   244  176  19          176            0             0             0        176         0             0    0              4           4      0       rd ex mr mw me sd  libreadline.so.6.2
    7f784dcb4000 ---p 0003d000  fd:00 1837043  2048    0   0            0            0             0             0          0         0             0    0              4           4      0             mr mw me sd  libreadline.so.6.2
    7f784deb4000 r--p 0003d000  fd:00 1837043     8    8   8            0            0             0             8          8         8             0    0              4           4      0       rd mr mw me ac sd  libreadline.so.6.2
    7f784deb6000 rw-p 0003f000  fd:00 1837043    24   24  24            0            0             0            24         24        24             0    0              4           4      0    rd wr mr mw me ac sd  libreadline.so.6.2


Veja também


Isso significa que a pré-ligação não tem mais utilidade (e que o -fPICuso mudou completamente há algum tempo)?
Hauke ​​Laging

@crisron Obrigado pelas correções. Para sua informação, o Markdown contará para você - a saída renderizada do meu repetido 1. estava correta. Além disso, fiz algumas alterações no que você fez - "endereço inicial" é um jargão técnico, provavelmente causei confusão ao colocar "lógico" no meio. Eu mudei para me livrar do jargão. Além disso, as páginas são equivalentes a esses endereços. AFAIK não é possível que esses endereços sejam uma página diferente. Tentei novamente, trocando a ordem, espero que seja mais claro.
derobert 26/02

caramba, agora isso é uma resposta !!!
Evan Carroll
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.