Não sou desenvolvedor de kernel, mas passei anos filosofando sobre esse assunto, porque me deparei com isso muitas vezes. Na verdade, eu criei uma metáfora para toda a situação, então deixe-me dizer uma coisa. Vou assumir na minha história que coisas como "swap" não existem. De qualquer forma, a troca não faz muito sentido com 32 GB de RAM atualmente.
Imagine um bairro em que a água esteja conectada a cada prédio por meio de canos e as cidades precisem gerenciar a capacidade. Vamos supor que você tenha apenas uma produção de 100 unidades de água por segundo (e toda a capacidade não utilizada será desperdiçada porque você não possui tanques de reservatório). Cada residência (residência = um pequeno aplicativo, um terminal, o widget de relógio etc.) requer uma unidade de água por segundo. Tudo isso é bom e bom porque sua população tem cerca de 90 anos e todo mundo recebe água suficiente.
Agora, o prefeito (= você) decide que deseja abrir um restaurante grande (= navegador). Este restaurante abrigará vários cozinheiros (= guias do navegador). Cada cozinheiro precisa de 1 unidade de água por segundo. Você começa com 10 cozinheiros, portanto o consumo total de água para todo o bairro é de 100 unidades de água, o que ainda é bom.
Agora começa a coisa divertida: você contrata outro cozinheiro em seu restaurante, o que faz com que as necessidades totais de água sejam 101 que obviamente você não tem. Você precisa fazer alguma coisa.
O gerenciamento de água (= kernel) tem 3 opções.
1. A primeira opção é simplesmente desconectar o serviço para as casas que não usaram a água recentemente. Isso é bom, mas se a casa desconectada quiser usar a água novamente, precisará passar pelo longo processo de registro novamente. A gerência pode desconectar várias casas para liberar mais recursos hídricos. Na verdade, eles desconectam todas as casas que não usaram água recentemente, mantendo sempre uma certa quantidade de água livre sempre disponível.
Embora sua cidade continue funcionando, a desvantagem é que o progresso é interrompido. A maior parte do seu tempo é gasta na espera pelo gerenciamento da água para restabelecer seu serviço.
Isto é o que o kernel faz com as páginas suportadas por arquivo. Se você executar um executável grande (como o chrome), seu arquivo será copiado na memória. Quando estiver com pouca memória ou se houver partes que não foram acessadas recentemente, o kernel poderá descartá-las porque pode recarregá-las do disco de qualquer maneira. Se isso for feito excessivamente, isso interromperá a área de trabalho, pois tudo estará aguardando a E / S do disco. Observe que o kernel também eliminará muitas das páginas usadas menos recentemente quando você começar a fazer muitas IO. É por isso que leva séculos para mudar para um aplicativo em segundo plano depois que você copia vários arquivos grandes, como imagens de DVD.
Esse é o comportamento mais irritante para mim, porque eu odeio vadias e você não tem controle sobre isso. Seria bom poder desligá-lo. Estou pensando em algo ao longo das linhas de
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
e então você pode definir vm_swappiness como -1 para desativar isso. Isso funcionou muito bem nos meus pequenos testes, mas infelizmente não sou desenvolvedor de kernel, então não enviei para ninguém (e, obviamente, a pequena modificação acima não está completa).
2)A gerência poderia negar o pedido de água do novo cozinheiro. Isso inicialmente parece uma boa ideia. No entanto, existem duas desvantagens. Primeiro, existem empresas que solicitam muitas assinaturas de água, mesmo que não as usem. Uma razão possível para fazer isso é evitar toda a sobrecarga de conversar com o gerenciamento de água sempre que eles precisarem de um pouco de água. O uso da água sobe e desce dependendo da hora do dia. Por exemplo, no caso do restaurante, a empresa precisa de muito mais água ao meio-dia em comparação à meia-noite. Portanto, eles solicitam toda a água possível que possam usar, mas isso desperdiça alocações de água durante a meia-noite. O problema é que nem todas as empresas podem prever seu pico de uso corretamente, de modo que solicitam muito mais, na esperança de que nunca precisem se preocupar em solicitar mais.
É isso que a máquina virtual do Java faz: aloca um monte de memória na inicialização e depois trabalha com isso. Por padrão, o kernel alocará a memória apenas quando seu aplicativo Java realmente começar a usá-lo. No entanto, se você desativar a confirmação excessiva, o kernel levará a reserva a sério. Só permitirá que a alocação seja bem-sucedida se ela realmente tiver os recursos para isso.
No entanto, há outro problema mais sério com essa abordagem. Digamos que uma empresa comece a solicitar uma única unidade de água todos os dias (e não nas etapas de 10). Eventualmente, você chegará a um estado em que possui 0 unidades gratuitas. Agora, esta empresa não poderá alocar mais. Tudo bem, quem se importa com as grandes empresas de qualquer maneira. Mas o problema é que as pequenas casas também não poderão solicitar mais água! Você não poderá construir pequenos banheiros públicos para lidar com o repentino fluxo de turistas. Você não poderá fornecer água de emergência para o incêndio na floresta próxima.
Em termos de computador: em situações de pouca memória sem confirmação excessiva, você não poderá abrir um novo xterm, não poderá conectar-se à sua máquina, não poderá abrir uma nova guia para procurar possíveis Conserta. Em outras palavras, desativar o overcommit também torna a área de trabalho inútil quando a memória está baixa.
3. Agora, aqui está uma maneira interessante de lidar com o problema quando uma empresa começa a usar muita água. A gestão da água explode! Literalmente: vai ao local do restaurante, joga dinamites nele e espera até que exploda. Isso reduzirá instantaneamente as necessidades de água da cidade para que novas pessoas possam se mudar, você pode criar banheiros públicos etc. Você, como prefeito, pode reconstruir o restaurante na esperança de que desta vez exija menos água. Por exemplo, você dirá às pessoas para não irem aos restaurantes se já houver muitas pessoas dentro (por exemplo, você abrirá menos guias do navegador).
Na verdade, é isso que o kernel faz quando fica sem todas as opções e precisa de memória: ele chama o OOM killer. Ele pega um aplicativo grande (baseado em muitas heurísticas) e o mata, liberando muita memória, mas mantendo uma área de trabalho responsiva. Na verdade, o kernel do Android faz isso de forma ainda mais agressiva: mata o aplicativo menos usado recentemente quando a memória está baixa (em comparação com o kernel padrão que faz isso apenas como último recurso). Isso é chamado de Viking Killer no Android.
Eu acho que essa é uma das soluções mais simples para o problema: não é como se você tivesse mais opções do que isso; por que não superar isso mais cedo ou mais tarde, certo? O problema é que o kernel às vezes faz bastante trabalho para evitar a chamada do killer do OOM. É por isso que você vê que sua área de trabalho é muito lenta e o kernel não está fazendo nada a respeito. Mas, felizmente, existe uma opção para invocar o assassino da OOM! Primeiro, verifique se a tecla sysrq mágica está ativada (por exemplo echo 1 | sudo tee
/proc/sys/kernel/sysrq
) e, sempre que sentir que o kernel está com pouca memória, basta pressionar Alt + SysRQ, Alt + f.
OK, então tudo isso é legal, mas você quer experimentar? A situação de pouca memória é muito simples de reproduzir. Eu tenho um aplicativo muito simples para isso. Você precisará executá-lo duas vezes. A primeira execução determinará a quantidade de RAM livre disponível; a segunda execução criará a situação de pouca memória. Observe que este método pressupõe que você tenha desabilitado o swap (por exemplo, faça a sudo swapoff -a
). Código e uso a seguir:
// gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int limit = 123456789;
if (argc >= 2) {
limit = atoi(argv[1]);
}
setbuf(stdout, NULL);
for (int i = 1; i <= limit; i++) {
memset(malloc(1 << 20), 1, 1 << 20);
printf("\rAllocated %5d MiB.", i);
}
sleep(10000);
return 0;
}
E aqui está como você o usa:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
A primeira chamada detectou que temos 31.118 MiB de RAM livre. Então eu disse ao aplicativo para alocar 31.110 MiB de RAM para que o kernel não o mate, mas consome quase toda a minha memória. Meu sistema congelou: até o ponteiro do mouse não se mexeu. Pressionei Alt + SysRQ, Alt + f e isso acabou com meu processo eatmem e o sistema foi restaurado.
Embora tenhamos coberto nossas opções, o que fazer em uma situação de pouca memória, a melhor abordagem (como qualquer outra situação perigosa) é evitá-la em primeiro lugar. Há muitas maneiras de fazer isso. Uma maneira comum que eu vi é colocar os aplicativos que se comportam mal (como navegadores) em contêineres diferentes do resto do sistema. Nesse caso, o navegador não poderá afetar sua área de trabalho. Mas a prevenção em si está fora do escopo da pergunta, então não vou escrever sobre isso.
TL; DR: embora atualmente não haja como evitar totalmente a paginação, você pode atenuar a interrupção total do sistema desativando a confirmação excessiva. Mas seu sistema continuará inutilizável durante situações de pouca memória, mas de uma maneira diferente. Independentemente do exposto, em uma situação de pouca memória, pressione Alt + SysRQ, Alt + f para interromper um grande processo de escolha do kernel. Seu sistema deve restaurar sua capacidade de resposta após alguns segundos. Isso pressupõe que você tenha a chave sysrq mágica ativada (não é por padrão).