Limitando o Uso de Memória de Programas com Cgroups no Linux
, ilustrado por [Robert Grossman](https://www.robertgrossman.com/). Um computador comendo um disquete](/images/2023-05/computer-eating-memory.jpg#center)
Um computador faminto comendo um disquete. Fonte: Internet Archive, revista ROM, 1977 , ilustrado por Robert Grossman .
Tentando fazer com ulimit
Enquanto tentava resolver o desafio 07 do Os Programadores (e validando soluções de outros), eu precisava verificar se meu programa (e de outros) usava menos de 512 megabytes de memória.
Meu primeiro instinto foi usar o comando ulimit
:
ulimit -m 512000 && ./meu_programa
Continuei executando o programa com isso, mas não parecia limitar nada. O programa ultrapassou os 512MB sem problema algum. Então fui pesquisar o que estava acontecendo.
E aparentemente, há bastante confusão sobre ulimit
e memória. Da sua man page
:
-m O tamanho máximo do conjunto residente (muitos sistemas não respeitam este limite)
Então, a flag -m
“deveria” limitar o RSS
(Resident Set Size), que é basicamente a memória física realmente alocada para um processo. Mas muitos sistemas não respeitam este limite. Era isso que estava acontecendo no meu Ubuntu 20.04, onde o kernel estava completamente ignorando este limite.
E sobre a flag -v
?
-v A quantidade máxima de memória virtual disponível para o shell e seus filhos, em KiB
Isso limita a memória virtual, que inclui arquivos mapeados em memória, bibliotecas compartilhadas, etc. Não é realmente o que queremos limitar.
Existem também outras flags como -d
(segmento de dados) e -s
(tamanho da pilha), mas nenhuma delas fornece uma maneira confiável de limitar o uso total de memória de um programa em diferentes sistemas.
Usando cgroups v1
Como ulimit
não estava funcionando de forma confiável, decidi usar cgroups (grupos de controle) para limitar o uso de memória.
Primeiro, vamos criar um novo cgroup:
# Criar um novo cgroup para nosso programa
sudo mkdir /sys/fs/cgroup/memory/meu_programa
# Definir o limite de memória para 512MB (536870912 bytes)
echo 536870912 | sudo tee /sys/fs/cgroup/memory/meu_programa/memory.limit_in_bytes
# Adicionar nosso processo ao cgroup (faremos isso quando executarmos o programa)
Agora, para executar nosso programa dentro deste limite de memória:
# Executar o programa e adicioná-lo ao nosso cgroup
./meu_programa &
PID=$!
echo $PID | sudo tee /sys/fs/cgroup/memory/meu_programa/cgroup.procs
wait $PID
Esta abordagem tem algumas vantagens:
- Realmente funciona - cgroups são respeitados pelo kernel
- Controle preciso - podemos definir limites exatos de memória
- Abrangente - limita todos os tipos de uso de memória (RSS, cache, swap, etc.)
No entanto, é um pouco trabalhoso configurar manualmente toda vez.
Usando cgroups v2 (systemd-run)
Uma abordagem mais moderna e conveniente é usar systemd-run
, que pode criar cgroups temporários para nós:
# Executar o programa com limite de 512MB de memória usando systemd-run
systemd-run --scope --user --property=MemoryMax=512M ./meu_programa
Isso é muito mais limpo! O comando systemd-run
:
- Cria uma unidade de escopo temporária
- Define o limite de memória usando cgroups v2
- Executa nosso programa dentro desse escopo
- Limpa automaticamente quando o programa termina
Se você quiser executá-lo em todo o sistema (não apenas para seu usuário), pode omitir a flag --user
, mas precisará de sudo
:
sudo systemd-run --scope --property=MemoryMax=512M ./meu_programa
O que acontece quando o limite é excedido?
Quando um processo tenta alocar mais memória do que o limite do cgroup permite:
- Primeiro, o kernel tentará liberar memória escrevendo páginas sujas no disco
- Se isso não for suficiente, começará a fazer swap de páginas para o disco (se swap estiver disponível)
- Como último recurso, o OOM (Out of Memory) killer terminará o processo com um
SIGKILL
Você pode monitorar o que está acontecendo verificando o uso de memória do cgroup:
# Para cgroups v1
cat /sys/fs/cgroup/memory/meu_programa/memory.usage_in_bytes
# Para systemd-run, você pode usar systemctl
systemctl status <nome-da-unidade>
Conclusão
Embora ulimit
possa parecer a escolha óbvia para limitar o uso de memória, cgroups fornecem uma solução muito mais confiável. A abordagem com systemd-run
é particularmente conveniente para execuções pontuais de programas onde você precisa garantir que os limites de memória sejam respeitados.
Este método tem sido inestimável para testar programas com restrições de memória e garantir que se comportem corretamente sob limitações de recursos.
Referências: