Descobrir quanta memória RAM um processo Linux usa não é uma questão simples, especialmente quando a memória compartilhada precisa ser considerada. Felizmente, o pmap
comando ajuda você a entender tudo.
Índice
Mapeamento de memória
Em sistemas operacionais modernos , cada processo vive em sua própria região alocada de memória ou espaço de alocação . Os limites da região alocada não são mapeados diretamente para endereços de hardware físico. O sistema operacional cria um espaço de memória virtual para cada processo e atua como uma camada de abstração mapeando a memória virtual para a memória física.
O kernel mantém uma tabela de tradução para cada processo, e esta é acessada pela CPU . Quando o kernel altera o processo em execução em um núcleo de CPU específico , ele atualiza a tabela de conversão que une processos e núcleos de CPU.
Os benefícios da abstração
Há benefícios para este esquema. O uso da memória é um pouco encapsulado e protegido por sandbox para cada processo na área de usuário. Um processo apenas “vê” a memória em termos de endereços de memória virtual. Isso significa que ele só pode trabalhar com a memória que lhe foi dada pelo sistema operacional. A menos que tenha acesso a alguma memória compartilhada, ele não conhece nem tem acesso à memória alocada para outros processos.
A abstração da memória física baseada em hardware em endereços de memória virtual permite que o kernel altere o endereço físico para o qual alguma memória virtual está mapeada. Ele pode trocar a memória para o disco alterando o endereço real para o qual uma região de memória virtual aponta. Ele também pode adiar o fornecimento de memória física até que seja realmente necessário.
Contanto que as solicitações de leitura ou gravação de memória sejam atendidas conforme solicitado, o kernel está livre para fazer malabarismos com a tabela de mapeamento como achar melhor.
RAM sob demanda
A tabela de mapeamento e o conceito de “RAM sob demanda” abrem a possibilidade de memória compartilhada . O kernel tentará evitar carregar a mesma coisa na memória mais de uma vez. Por exemplo, ele carregará uma biblioteca compartilhada na memória uma vez e a mapeará para os diferentes processos que precisam usá-la. Cada um dos processos terá seu próprio endereço exclusivo para a biblioteca compartilhada, mas todos apontarão para o mesmo local real.
Se a região compartilhada da memória for gravável, o kernel usa um esquema chamado copy-on-write. Se um processo grava na memória compartilhada e os outros processos que compartilham essa memória não devem ver as alterações, uma cópia da memória compartilhada é criada no ponto da solicitação de gravação.
O kernel Linux 2.6.32, lançado em dezembro de 2009, deu ao Linux um recurso chamado “Kernel SamePage Merging”. Isso significa que o Linux pode detectar regiões idênticas de dados em diferentes espaços de endereço. Imagine uma série de máquinas virtuais rodando em um único computador e todas as máquinas virtuais rodando no mesmo sistema operacional. Usando um modelo de memória compartilhada e copy-on-write, a sobrecarga no computador host pode ser drasticamente reduzida.
Tudo isso torna o manuseio de memória no Linux sofisticado e o mais otimizado possível. Mas essa sofisticação torna difícil olhar para um processo e saber qual é realmente seu uso de memória.
O utilitário pmap
O kernel expõe muito do que está fazendo com a RAM através de dois pseudo-arquivos no pseudo-sistema de informações do sistema “/proc”. Existem dois arquivos por processo, nomeados para o ID do processo ou PID de cada processo: “/proc/maps” e “/proc//smaps”.
A pmap
ferramenta lê as informações desses arquivos e exibe os resultados na janela do terminal. Será óbvio que precisamos fornecer o PID do processo em que estamos interessados sempre que usarmos pmap
.
Encontrando o ID do processo
Existem várias maneiras de encontrar o PID de um processo . Aqui está o código-fonte de um programa trivial que usaremos em nossos exemplos. Ele está escrito em C. Tudo o que ele faz é imprimir uma mensagem na janela do terminal e esperar que o usuário pressione a tecla “Enter”.
#include <stdio.h> int main(int argc, char *argv[]) { printf("Programa de teste Geek How-To."); getc(stdin); } // fim do principal
O programa foi compilado para um executável chamado pm
usando o gcc
compilador:
gcc -o pm pm.c
Como o programa vai esperar que o usuário aperte “Enter”, ele vai ficar rodando o tempo que quisermos.
./PM
O programa é iniciado, imprime a mensagem e aguarda o pressionamento de tecla. Agora podemos procurar seu PID. O ps
comando lista os processos em execução. A -e
opção (mostrar todos os processos) ps
lista todos os processos. Vamos canalizar a saída grep
e filtrar as entradas que têm “pm” em seu nome.
ps -e | grep pm
Isso lista todas as entradas com “pm” em qualquer lugar em seus nomes.
Podemos ser mais específicos usando o pidof
comando. Damos pidof
o nome do processo em que estamos interessados na linha de comando e ele tenta encontrar uma correspondência. Se uma correspondência for encontrada, pidof
imprime o PID do processo de correspondência.
pidof pm
O pidof
método é mais organizado quando você sabe o nome do processo, mas o ps
método funcionará mesmo se souber apenas parte do nome do processo.
Usando pmap
Com nosso programa de teste em execução, e uma vez que identificamos seu PID, podemos usar o pmap assim:
pmap 40919
Os mapeamentos de memória para o processo são listados para nós.
Aqui está a saída completa do comando:
40919: ./pm 000056059f06c000 4K r---- pm 000056059f06d000 4K rx-- pm 000056059f06e000 4K r---- pm 000056059f06f000 4K r---- pm 000056059f070000 4K rw --- pm 000056059fc39000 132K rw--- [anônimo] 00007f97a3edb000 8K rw --- [ anon ] 00007f97a3edd000 160K r---- libc.so.6 00007f97a3f05000 1616K rx-- libc.so.6 00007f97a4099000 352K r---- libc.so.6 00007f97a40f1000 4K ----- libc.so.6 00007f97a40f2000 16K r---- libc.so.6 00007f97a40f6000 8K rw --- libc.so.6 00007f97a40f8000 60K rw --- [ anon ] 00007f97a4116000 4K r---- ld-linux-x86-64.so.2 00007f97a4117000 160K rx--ld-linux-x86-64.so.2 00007f97a413f000 40K r---- ld-linux-x86-64.so.2 00007f97a4149000 8K r---- ld-linux-x86-64.so.2 00007f97a414b000 8K rw --- ld-linux-x86-64.so.2 00007ffca0e7e000 132K rw --- [ pilha ] 00007ffca0fe1000 16K r---- [anônimo] 00007ffca0fe5000 8K rx-- [anônimo] ffffffffff600000 4K --x-- [ anon ] total de 2.756 mil
A primeira linha é o nome do processo e seu PID. Cada uma das outras linhas mostra um endereço de memória mapeado e a quantidade de memória nesse endereço, expressa em kilobytes. Os próximos cinco caracteres de cada linha são chamados de permissões de memória virtual . As permissões válidas são:
- r : A memória mapeada pode ser lida pelo processo.
- w : A memória mapeada pode ser escrita pelo processo.
- x : O processo pode executar qualquer instrução contida na memória mapeada.
- s : A memória mapeada é compartilhada e as alterações feitas na memória compartilhada são visíveis para todos os processos que compartilham a memória.
- R : Não há reserva de espaço de troca para esta memória mapeada.
A informação final em cada linha é o nome da fonte do mapeamento. Pode ser um nome de processo, nome de biblioteca ou nome de sistema, como pilha ou heap.
A Exibição Estendida
A -x
opção (estendida) fornece duas colunas extras.
pmap -x 40919
As colunas recebem títulos. Já vimos as colunas “Endereço”, “Kbytes”, “Modo” e “Mapeamento”. As novas colunas são chamadas de “RSS” e “Dirty”.
Aqui está a saída completa:
40919: ./pm Endereço Kbytes RSS Mapeamento de modo sujo 000056059f06c000 4 4 0 r---- pm 000056059f06d000 4 4 0 rx-- pm 000056059f06e000 4 4 0 r---- pm 000056059f06f000 4 4 4 r---- pm 000056059f070000 4 4 4 rw --- pm 000056059fc39000 132 4 4 rw --- [ anon ] 00007f97a3edb000 8 4 4 rw --- [ anon ] 00007f97a3edd000 160 160 0 r---- libc.so.6 00007f97a3f05000 1616 788 0 rx-- libc.so.6 00007f97a4099000 352 64 0 r---- libc.so.6 00007f97a40f1000 4 0 0 ----- libc.so.6 00007f97a40f2000 16 16 16 r---- libc.so.6 00007f97a40f6000 8 8 8 rw --- libc.so.6 00007f97a40f8000 60 28 28 rw --- [ anon ] 00007f97a4116000 4 4 0 r---- ld-linux-x86-64.so.2 00007f97a4117000 160 160 0 rx-- ld-linux-x86-64.so.2 00007f97a413f000 40 40 0 r---- ld-linux-x86-64.so.2 00007f97a4149000 8 8 8 r---- ld-linux-x86-64.so.2 00007f97a414b000 8 8 8 rw--- ld-linux-x86-64.so.2 00007ffca0e7e000 132 12 12 rw --- [ pilha ] 00007ffca0fe1000 16 0 0 r ---- [ anon ] 00007ffca0fe5000 8 4 0 rx-- [anônimo] ffffffffff600000 4 0 0 --x-- [ anon ] ----- ------- ------- ------- KB total 2756 1328 96
- RSS : Este é o tamanho do conjunto residente . Ou seja, a quantidade de memória que está atualmente na RAM e não foi trocada.
- Suja : A memória “suja” foi alterada desde o início do processo e do mapeamento.
Mostre-me tudo
O -X
(ainda mais do que estendido) adiciona colunas adicionais à saída. Observe o “X” maiúsculo. Outra opção chamada -XX
(ainda mais que -X
) mostra que tudo pmap
pode ser obtido do kernel. Como -X
é um subconjunto de -XX
, descreveremos a saída de -XX
.
pmap -XX 40919
A saída envolve horrivelmente em uma janela de terminal e é praticamente indecifrável. Aqui está a saída completa:
40919: ./pm Endereço Perm Offset Dispositivo Inode Tamanho KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenciado Anônimo LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Bloqueado THPeligible VmFlags Mapping 56059f06c000 r--p 00000000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm 56059f06d000 r-xp 00001000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex sr mw me dw sd pm 56059f06e000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm 56059f06f000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd pm 56059f070000 rw-p 00003000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr sr mw me dw ac sd pm 56059fc39000 rw-p 00000000 00:00 0 132 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd [heap] 7f97a3edb000 rw-p 00000000 00:00 0 8 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 7f97a3edd000 r--p 00000000 08:03 264328 160 4 4 160 4 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6 7f97a3f05000 r-xp 00028000 08:03 264328 1616 4 4 788 32 788 0 0 0 788 0 0 0 0 0 0 0 0 0 0 0 rd ex sr mw me sd libc.so.6 7f97a4099000 r--p 001bc000 08:03 264328 352 4 4 64 1 64 0 0 0 64 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6 7f97a40f1000 ---p 00214000 08:03 264328 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mr mw me sd libc.so.6 7f97a40f2000 r--p 00214000 08:03 264328 16 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac sd libc.so.6 7f97a40f6000 rw-p 00218000 08:03 264328 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr sr mw me ac sd libc.so.6 7f97a40f8000 rw-p 00000000 00:00 0 60 4 4 28 28 0 0 0 28 28 28 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 7f97a4116000 r--p 00000000 08:03 264305 4 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd sr mw me dw sd ld-linux-x86-64.so.2 7f97a4117000 r-xp 00001000 08:03 264305 160 4 4 160 11 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd ex sr mw me dw sd ld-linux-x86-64.so.2 7f97a413f000 r--p 00029000 08:03 264305 40 4 4 40 1 40 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2 7f97a4149000 r--p 00032000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd sr mw me dw ac sd ld-linux-x86-64.so.2 7f97a414b000 rw-p 00034000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr sr mw me dw ac sd ld-linux-x86-64.so.2 7ffca0e7e000 rw-p 00000000 00:00 0 132 4 4 12 12 0 0 0 12 12 12 0 0 0 0 0 0 0 0 0 0 rd wr sr mw me gd ac [stack] 7ffca0fe1000 r--p 00000000 00:00 0 16 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 rd mr pf io de dd sd [vvar] 7ffca0fe5000 r-xp 00000000 00:00 0 8 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me de sd [vdso] ffffffffff600000 --xp 00000000 00:00 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ex [vsyscall] ==== ========================= ==== === ============ == ================================================== ===== ======== ===================================== === ============================= ==== ============= = ========== 2756 92 92 1328 157 1220 0 12 96 1328 96 0 0 0 0 0 0 0 0 0 0 KB
Tem muita informação aqui. Isto é o que as colunas mantêm:
- Endereço : O endereço inicial deste mapeamento. Isso usa endereçamento de memória virtual.
- Perm : As permissões da memória.
- Offset : Se a memória for baseada em arquivo, o deslocamento desse mapeamento dentro do arquivo.
- Dispositivo : O número do dispositivo Linux, fornecido em números maiores e menores. Você pode ver os números dos dispositivos em seu computador executando o
lsblk
comando. - Inode : O inode do arquivo ao qual o mapeamento está associado. Por exemplo, em nosso exemplo, este poderia ser o inode que contém informações sobre o programa pm.
- Tamanho : O tamanho da região mapeada na memória.
- KernelPageSize : O tamanho da página usado pelo kernel.
- MMUageSize : O tamanho da página usado pela unidade de gerenciamento de memória.
- Rss : Este é o tamanho do conjunto residente . Ou seja, a quantidade de memória que está atualmente na RAM e não foi trocada.
- Pss : Este é o tamanho do compartilhamento proporcional . Este é o tamanho compartilhado privado adicionado ao (tamanho compartilhado dividido pelo número de compartilhamentos.)
- Shared_Clean : A quantidade de memória compartilhada com outros processos que não foi alterada desde que o mapeamento foi criado. Observe que, mesmo que a memória seja compartilhável, se não tiver sido compartilhada, ainda é considerada memória privada.
- Shared_Dirty : A quantidade de memória compartilhada com outros processos que foi alterada desde que o mapeamento foi criado.
- Private_Clean : A quantidade de memória privada—não compartilhada com outros processos—que não foi alterada desde que o mapeamento foi criado.
- Private_Dirty : A quantidade de memória privada que foi alterada desde que o mapeamento foi criado.
- Referenciado : A quantidade de memória atualmente marcada como referenciada ou acessada.
- Anônimo : Memória que não possui um dispositivo para o qual trocar. Ou seja, não é suportado por arquivo.
- LazyFree : Páginas que foram sinalizadas como
MADV_FREE
. Essas páginas foram marcadas como disponíveis para serem liberadas e recuperadas, mesmo que possam ter alterações não escritas nelas. No entanto, se ocorrerem alterações subsequentes após oMADV_FREE
ter sido definido no mapeamento de memória, oMADV_FREE
sinalizador será removido e as páginas não serão recuperadas até que as alterações sejam gravadas. - AnonHugePages : Estas são páginas de memória “enormes” não suportadas por arquivo (maiores que 4 KB).
- ShmemPmdMapped : Memória compartilhada associada a páginas enormes. Eles também podem ser usados por sistemas de arquivos que residem inteiramente na memória.
- FilePmdMapped : O Page Middle Directory é um dos esquemas de paginação disponíveis para o kernel. Este é o número de páginas com suporte de arquivo apontadas pelas entradas PMD.
- Shared_Hugetlb : Translation Lookaside Tables, ou TLBs, são caches de memória usados para otimizar o tempo necessário para acessar os locais de memória do espaço do usuário. Essa figura é a quantidade de RAM usada em TLBs que estão associadas a páginas de memória enorme compartilhadas.
- Private_Hugetlb : Este valor é a quantidade de RAM usada em TLBs que estão associadas a páginas de memória enorme privada.
- Swap : A quantidade de swap que está sendo usada.
- SwapPss : O tamanho do compartilhamento proporcional de swap . Essa é a quantidade de troca composta de páginas de memória privada trocadas adicionadas ao (tamanho compartilhado dividido pelo número de compartilhamentos).
- Bloqueado : os mapeamentos de memória podem ser bloqueados para impedir que o sistema operacional faça a paginação da memória heap ou fora do heap.
- THPeligible : Este é um sinalizador que indica se o mapeamento é elegível para alocar páginas transparentes enormes . 1 significa verdadeiro, 0 significa falso. Páginas enormes transparentes é um sistema de gerenciamento de memória que reduz a sobrecarga de pesquisas de página TLB em computadores com uma grande quantidade de RAM.
- VmFlags : Veja a lista de sinalizadores abaixo.
- Mapeamento : O nome da origem do mapeamento. Pode ser um nome de processo, nome de biblioteca ou nomes de sistema, como pilha ou heap.
Os VmFlags — sinalizadores de memória virtual — serão um subconjunto da lista a seguir.
- rd : legível.
- wr : gravável.
- ex : executável.
- sh : Compartilhado.
- sr : Pode ler.
- mw : Pode escrever.
- eu : Pode executar.
- ms : Pode compartilhar.
- gd : O segmento de pilha cresce para baixo.
- pf : intervalo de números de quadros de página puros. Os números de quadro de página são uma lista das páginas de memória física.
- dw : Gravação desabilitada no arquivo mapeado.
- lo : As páginas estão bloqueadas na memória.
- io : Área de E/S mapeada na memória.
- sr : Aconselhamento de leitura sequencial fornecido (pela
madvise()
função.) - rr : Aconselhamento de leitura aleatória fornecido.
- dc : Não copie esta região de memória se o processo for bifurcado.
- de : Não expanda esta região de memória no remapeamento.
- ac : A área é responsável.
- nr : O espaço de troca não está reservado para a área.
- ht : A área usa páginas TLB enormes.
- sf : Falha de página síncrona.
- ar : sinalizador específico da arquitetura.
- wf : Limpe esta região de memória se o processo for bifurcado.
- dd : não inclua esta região de memória em dumps principais.
- sd : Sinalizador sujo suave.
- mm : Área de mapa mista.
- hg : Sinalizador de aviso de página enorme.
- nh : Nenhum sinalizador de aviso de página enorme.
- mg : Sinalizador de aviso mesclável.
- bt : página protegida por instabilidade de temperatura de polarização ARM64.
- mt : ARM64 As tags de extensão de marcação de memória estão habilitadas.
- um : Userfaultfd faltando rastreamento.
- uw : Userfaultfd wr-protect tracking.
O gerenciamento de memória é complicado
E trabalhar de trás para frente a partir de tabelas de dados para entender o que realmente está acontecendo é difícil. Mas pelo menos pmap
lhe dá uma visão completa para que você tenha a melhor chance de descobrir o que precisa saber.
É interessante notar que nosso programa de exemplo compilou para um executável binário de 16 KB e ainda está usando (ou compartilhando) cerca de 2756 KB de memória, quase inteiramente devido a bibliotecas de tempo de execução.
Um truque legal final é que você pode usar pmap
e os pidof
comandos juntos, combinando as ações de encontrar o PID do processo e passá-lo pmap
em um comando:
pmap $(pidof pm)