Mateus Mercer

Limitando o Uso de Memória de Programas com Cgroups no Linux

Um computador comendo um disquete

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:

  1. Realmente funciona - cgroups são respeitados pelo kernel
  2. Controle preciso - podemos definir limites exatos de memória
  3. 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:

  1. Primeiro, o kernel tentará liberar memória escrevendo páginas sujas no disco
  2. Se isso não for suficiente, começará a fazer swap de páginas para o disco (se swap estiver disponível)
  3. 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:

{ }