Como posso forçar o bash a desalocar uma variável expandida de chave sem nome?

5

Para avaliação comparativa , executei o comando:

for i in {1..100000000}; do 
  echo "$i" line >> file
done

Bash expandiu as chaves e armazenou a lista 1 2 3 4 5 6 ... 100000000 na memória .

Eu pensei que isso seria de alguma forma desalocado em um ponto. Afinal, é uma variável temporária. Um par de dias já passou e o processo bash ainda ocupa 17.9GB de memória.

Posso forçar o bash a limpar essas variáveis temporárias? Não consigo usar unset , porque não sei o nome da variável. (obviamente unset i não ajuda)

Naturalmente, uma solução é fechar o shell e abrir um novo.

Também perguntei sobre isso na lista de discussão e recebi uma resposta útil de Chet Ramsey :

That's not a memory leak. Malloc implementations need not release memory back to the kernel; the bash malloc (and others) do so only under limited circumstances. Memory obtained from the kernel using mmap or sbrk and kept in a cache by a malloc implementation doesn't constitute a leak. A leak is memory for which there is no longer a handle, by the application or by malloc itself.

malloc() is basically a cache between the application and the kernel. It gets to decide when and how to give memory back to the kernel.

    
por Sebastian 10.09.2014 / 13:28

2 respostas

6

Então, eu fiz essa coisa nos testes e, sim, isso consome muita memória. Eu intencionalmente usei um número menor também. Eu posso imaginar que bash monopolizar esses recursos por dias a fio poderia ser um pouco irritante.

ps -Fp "$$"; : {1..10000000}; ps -Fp "$$"

UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241  0  3957  3756   4 08:28 pts/1    00:00:00 bash -l
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241 59 472722 1878712 4 08:28 pts/1   00:00:28 bash -l

Como você pode ver, há um impacto significativo nos recursos consumidos do processo. Bem, vou tentar esclarecer isso, mas - o mais próximo que eu possa dizer - será necessário substituir o processo do shell por outro.

Primeiro, vou definir uma variável de marcador apenas para mostrar que vem comigo. Nota: isso não é export ed.

var='just
testing
'\''
this stuff
'\'''

Em seguida, vou executar $0 . Esse é o mesmo tipo de coisa que os daemons de longa duração devem fazer ocasionalmente para atualizar seu estado. Faz sentido aqui.

PRIMEIRO MÉTODO: AQUI DOCUMENTO

Usarei o shell atual para criar um descritor de arquivo de entrada heredoc para o novo processo de shell exec ed que conterá todas as variáveis declaradas do shell atual. Provavelmente isso pode ser feito de forma diferente, mas eu não conheço todas as opções de linha de comando adequadas para bash .

O novo shell é invocado com a opção -l ogin - que fará com que seus arquivos profile/rc sejam originados por padrão - e quaisquer outras opções de shell atualmente configuradas e armazenadas no parâmetro especial shell $- . Se você acha que -l ogin não é uma maneira correta de usar, em vez disso, usar a opção -i deve pelo menos obter a execução do arquivo rc .

exec "${0#-}" "-l$-" 3<<ENV
$(set)
ENV

Ok. Isso levou apenas um segundo. Como isso funcionou?

. /dev/fd/3 2>/dev/null
echo "$var"; ps -Fp "$$"

just
testing
'
this stuff
'
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 32601  4241 12  4054  3800   5 08:28 pts/1    00:00:29 bash -lhimBH

Muito bem, como parece. <&3 trava na entrada do novo processo shell até que seja lido - então eu faço isso com . e a fonte. Ele provavelmente conterá algumas variáveis somente de leitura padrão que já foram definidas no novo shell por seus arquivos rc e etc e, portanto, haverá alguns erros - mas eu despejo isso para 2>/dev/null . Depois que eu fizer isso, como você pode ver, eu tenho todas as variáveis do processo do shell antigo aqui comigo - para incluir meu marcador $var .

SEGUNDO MÉTODO: VARIÁVEIS DE MEIO AMBIENTE

Depois de fazer um google ou dois sobre o assunto, acho que isso pode ser outra maneira que vale a pena considerar. Inicialmente, considerei isso, mas ( aparentemente erroneamente ) desconsiderou essa opção com base na crença de que havia um limite de tamanho arbitrário imposto pelo kernel para o valor de uma única variável de ambiente - algo como ARGLEN ou LINEMAX (o que provavelmente afetará isso) , mas menor para um valor único. O que eu estava certo, no entanto, é que uma chamada execve não funcionará quando o ambiente total é muito grande. E assim, acredito que isso deve ser preferido apenas no caso de você poder garantir que seu ambiente atual seja pequeno o suficiente para permitir uma chamada exec .

Na verdade, isso é diferente o suficiente para fazer tudo de novo de uma só vez.

ps -pF "$$"; : {1..10000000}; ps -pF "$$"

UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241  0  3957  3788   3 14:28 pts/1    00:00:00 bash -l
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 38 472722 1878740 3 14:28 pts/1   00:00:11 bash -l

Uma coisa que não consegui fazer no primeiro turno é migrar as funções do shell. Sem contar que você está acompanhando (o que é provavelmente o melhor caminho) , pelo que eu sei, não há nenhuma maneira de fazer isso. O bash permite isso, já que declare -f funciona da mesma maneira para funções que set faz para as variáveis do shell de forma portável. Para fazer isso também, com o primeiro método, basta adicionar ; declare -f a set no documento here.

Minha variável de marcador permanecerá a mesma, mas aqui está minha função de marcador:

chk () {
    printf '###%s:###\n%s\n' \
        \$VAR "${var-NOT SET}" \
        PSINFO "$(ps -Fp $$)" \
        ENV\ LEN "$(env | wc -c)"
}

E assim, ao invés de alimentar o novo shell com um descritor de arquivo, eu o entregarei em duas variáveis de ambiente:

varstate=$(set) fnstate=$(declare -f) exec "${0#-}" "-l$-"

Ok. Então eu acabei de substituir o shell em execução, e agora o que?

chk
bash: chk: command not found

Claro. Mas ...

{   echo '###EVAL/UNSET $FNSTATE###'
    eval "$fnstate"; unset fnstate
    chk
    echo '###EVAL/UNSET $VARSTATE###'
    eval "$varstate"; unset varstate
    chk
}

OUTPUT

###EVAL/UNSET $FNSTATE###
###$VAR:###
NOT SET
###PSINFO:###
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 10  3991  3736   1 14:28 pts/1    00:00:12 bash -lhimBH
###ENV LEN:###
6813
###EVAL/UNSET $VARSTATE###
bash: BASHOPTS: readonly variable
bash: BASH_VERSINFO: readonly variable
bash: EUID: readonly variable
bash: PPID: readonly variable
bash: SHELLOPTS: readonly variable
bash: UID: readonly variable
###$VAR:###
just
testing
'
this stuff
'
###PSINFO:###
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
mikeserv 26296  4241 10  4056  3772   1 14:28 pts/1    00:00:12 bash -lhimBH
###ENV LEN:###
2839
    
por 10.09.2014 / 17:37
0

Não. O Bash nunca retornará a memória alocada para qualquer finalidade no sistema operacional. (Mas por favor me corrija se eu estiver errado.)

No entanto, o bash irá reutilizar a memória para outros propósitos, se necessário, e se não, o kernel irá trocá-la, então não estará na RAM.

    
por 10.09.2014 / 14:20