ti-enxame.com

Como verificar o tamanho de heap para um processo no Linux

Eu estava escrevendo algum código e ele continuou travando. Mais tarde, depois de cavar os despejos, percebi que estava superando o limite máximo de heap (a vida teria sido mais fácil se eu tivesse adicionado um cheque no malloc). Embora eu tenha consertado isso, há alguma maneira de aumentar meu tamanho de heap?

PS: Uma pergunta bastante similar aqui, mas a resposta não é clara para mim.

10
bashrc

O heap geralmente é tão grande quanto a memória virtual endereçável em sua arquitetura.

Você deve verificar os limites atuais do seu sistema com o comando ulimit -a e procurar esta linha max memory size (kbytes, -m) 3008828, esta linha no meu OpenSuse 11.4 x86_64 com ~ 3.5 GiB do RAM diz que eu tenho aproximadamente 3GB de ram por processo.

Então você pode realmente testar o seu sistema usando este programa simples para verificar a memória máxima utilizável por processo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char* argv[]){
        size_t oneHundredMiB=100*1048576;
        size_t maxMemMiB=0;
        void *memPointer = NULL;
        do{
                if(memPointer != NULL){
                        printf("Max Tested Memory = %zi\n",maxMemMiB);
                        memset(memPointer,0,maxMemMiB);
                        free(memPointer);
                }
                maxMemMiB+=oneHundredMiB;
                memPointer=malloc(maxMemMiB);
        }while(memPointer != NULL);
        printf("Max Usable Memory aprox = %zi\n",maxMemMiB-oneHundredMiB);
        return 0;
}

Este programa obtém memória em incrementos de 100MiB, apresenta a memória atualmente alocada, aloca 0s nela e depois libera a memória. Quando o sistema não pode fornecer mais memória, retorna NULL e exibe a quantidade máxima final utilizável de RAM.

A advertência é que seu sistema começará a trocar bastante a memória nos estágios finais. Dependendo da configuração do seu sistema, o kernel pode decidir matar alguns processos. Eu uso incrementos de 100 MiB para que haja algum espaço para respirar em alguns aplicativos e no sistema. Você deve fechar tudo o que você não quer deixar de funcionar.

Dito isto. No meu sistema onde estou escrevendo este nada caiu. E o programa acima reporta quase o mesmo que ulimit -a. A diferença é que ele realmente testou a memória e por meio de memset() confirmou que a memória foi dada e usada.

Para comparação em um Ubuntu 10.04x86 VM com 256 MiB de ram e 400MiB de swap o ulimit report foi memory size (kbytes, -m) unlimited e meu pequeno programa relatou 524.288.000 bytes, que é aproximadamente o ram combinado e swap, descontando ram usado por outros softwares e pelo kernel.

Edit: Como Adam Zalcman escreveu, ulimit -m não é mais aceito nos novos kernels 2.6 e up linux, então eu estou corrigido. Mas ulimit -v é honrado. Para resultados práticos, você deve substituir -m com -v e procurar por virtual memory (kbytes, -v) 4515440. Parece mero acaso que minha caixa do Suse tenha o valor -m coincidindo com o que minha pequena utilidade relatou. Você deve se lembrar que esta é uma memória virtual atribuída pelo kernel, se o RAM físico for insuficiente, ele ocupará espaço de swap para compensar isso.

Se você quiser saber quanta memória física está disponível sem perturbar nenhum processo ou sistema, você pode usar

long total_available_ram =sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) ;

isso excluirá o cache e a memória do buffer, portanto, esse número pode ser muito menor que a memória real disponível. Os caches do sistema operacional podem ser silenciosos e seu despejo pode fornecer a memória extra necessária, mas isso é tratado pelo kernel.

9
RedComet

O gerenciamento de heap e memória é um recurso fornecido por sua biblioteca C (provavelmente glibc). Ele mantém o heap e retorna pedaços de memória para você toda vez que você executa uma malloc(). Ele não sabe o limite de tamanho de heap: toda vez que você solicita mais memória do que o que está disponível no heap, ele simplesmente pede mais do kernel (usando sbrk() ou mmap()).

Por padrão, o kernel quase sempre lhe dará mais memória quando solicitado. Isso significa que malloc() sempre retornará um endereço válido. É somente quando você se refere a uma página alocada pela primeira vez que o kernel se incomodará em encontrar uma página para você. Se achar que não pode entregá-lo, ele executa um killer OOM que, de acordo com determinada medida chamada badness (que inclui os tamanhos de memória virtual do seu processo e seus filhos, nível Nice, tempo de execução geral etc) seleciona uma vítima e envia um SIGTERM. Esta técnica de gerenciamento de memória é chamada de overcommit e é usada pelo kernel quando /proc/sys/vm/overcommit_memory é 0 ou 1. Veja overcommit-accounting na documentação do kernel para detalhes.

Escrevendo 2 em /proc/sys/vm/overcommit_memory, você pode desativar o overcommit. Se você fizer isso, o kernel irá verificar se tem memória antes de prometê-lo. Isso resultará em malloc() retornando NULL se não houver mais memória disponível.

Você também pode definir um limite na memória virtual que um processo pode alocar com setrlimit() e RLIMIT_AS ou com o comando ulimit -v. Independentemente da configuração de supercomprometimento descrita acima, se o processo tentar alocar mais memória que o limite, o kernel irá recusá-lo e malloc() retornará NULL. Note que no kernel Linux moderno (incluindo toda a série 2.6.x) o limite no tamanho do residente (setrlimit() com RLIMIT_RSS ou ulimit -m command) é ineficaz.

A sessão abaixo foi executada no kernel 2.6.32 com 4GB RAM e 8GB swap.

$ cat bigmem.c
#include <stdlib.h>
#include <stdio.h>

int main() {
  int i = 0;
  for (; i < 13*1024; i++) {
    void* p = malloc(1024*1024);
    if (p == NULL) {
      fprintf(stderr, "malloc() returned NULL on %dth request\n", i);
      return 1;
    }
  }
  printf("Allocated it all\n");
  return 0;
}
$ cc -o bigmem bigmem.c
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ Sudo bash -c "echo 2 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
2
$ ./bigmem
malloc() returned NULL on 8519th request
$ Sudo bash -c "echo 0 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ ulimit -v $(( 1024*1024 ))
$ ./bigmem
malloc() returned NULL on 1026th request
$

No exemplo acima, a troca ou OOM kill nunca poderia ocorrer, mas isso mudaria significativamente se o processo realmente tentasse tocar em toda a memória alocada.

Para responder à sua pergunta diretamente: a menos que você tenha um limite de memória virtual explicitamente configurado com o comando ulimit -v, não há limite de tamanho de heap diferente dos recursos físicos da máquina ou limite lógico do seu espaço de endereço (relevante em sistemas de 32 bits). Seu glibc continuará alocando memória no heap e solicitará mais e mais do kernel à medida que seu heap cresce. Eventualmente você pode acabar trocando mal se toda a memória física estiver esgotada. Uma vez esgotado o espaço de swap, um processo aleatório será eliminado pelo killer da OOM do kernel.

Note, no entanto, que a alocação de memória pode falhar por muitos mais motivos do que a falta de memória livre, fragmentação ou atingir um limite configurado. As chamadas sbrk() e mmap() usadas pelo alocador de glib possuem suas próprias falhas, por exemplo, a quebra do programa atingiu outro endereço já alocado (por exemplo, memória compartilhada ou uma página previamente mapeada com mmap()) ou o número máximo de mapeamentos de memória do processo foi excedido.

15
Adam Zalcman

Eu acho que seu problema original era que malloc não conseguiu alocar a memória solicitada em seu sistema. 

Por que isso aconteceu é específico para o seu sistema. 

Quando um processo é carregado, é alocado memória até um determinado endereço, que é o ponto de interrupção do sistema para o processo. Além desse endereço, a memória não é mapeada para o processo. Então, quando o processo "atinge" o ponto "break", ele solicita mais memória do sistema e uma maneira de fazer isso é através da chamada do sistema sbrk
malloc faria isso sob o capô, mas no seu sistema, por algum motivo, ele falhou. 

Pode haver muitas razões para isso, por exemplo:
1) Eu acho que no Linux existe um limite para o tamanho máximo da memória. Eu acho que é ulimit e talvez você acerte isso. Verifique se está definido para um limite
2) Talvez o seu sistema estivesse muito carregado
3) Seu programa faz mau gerenciamento de memória e você acaba com a memória frageded, então malloc não pode obter o tamanho do pedaço que você requisitou.
4) Seu programa corrompe as estruturas de dados internas malloc, ou seja, uso incorreto do ponteiro
etc 

2
Cratylus

Eu gostaria de adicionar um ponto às respostas anteriores.

Apps tem a ilusão de que malloc () retorna blocos 'sólidos'; na realidade, um buffer pode existir espalhado, pulverizado, em muitas páginas de RAM. O fato crucial aqui é este: a memória virtual de um processo, contendo seu código ou contendo algo como um grande array, deve ser contíguo. Vamos até admitir que o código e os dados sejam separados; uma matriz grande, char str [universe_size], deve ser contígua.

Agora: pode um único aplicativo ampliar o heap arbitrariamente, alocar tal matriz?

A resposta poderia ser "sim" se não houvesse mais nada em execução na máquina. O heap pode ser ridiculamente grande, mas deve ter limites. Em algum momento, as chamadas para sbrk () (no Linux, a função que, em suma, 'amplia' o heap) devem tropeçar na área reservada para outro aplicativo.

Este link fornece alguns exemplos interessantes e esclarecedores, confira. Eu não encontrei a informação no Linux.

0
A Koscianski