Resource limits: ulimit e cgroups
1. Introdução aos Resource Limits em Sistemas Unix
Em sistemas Unix e Linux, o gerenciamento de recursos é fundamental para garantir estabilidade, segurança e previsibilidade de execução. Resource limits são mecanismos que restringem o consumo de recursos do sistema por processos, prevenindo que um único programa consuma toda a memória, CPU ou descritores de arquivo, causando negação de serviço ou falhas catastróficas.
Dois mecanismos principais existem para esse fim: ulimit (user limits) e cgroups (control groups). O ulimit opera no nível de processo individual, herdado por processos filhos, e é configurado via shell (ulimit -n) ou chamadas de sistema C. Já os cgroups operam em grupos hierárquicos de processos, permitindo limites agregados e dinâmicos, sendo a base de tecnologias como Docker e LXC.
Os recursos controláveis incluem: memória virtual e residente, tempo de CPU, número de arquivos abertos, tamanho de pilha, número de processos, core dumps e prioridade de agendamento.
2. Trabalhando com ulimit via API C: getrlimit e setrlimit
A API C para manipular limites de recursos utiliza a estrutura struct rlimit, definida em <sys/resource.h>:
struct rlimit {
rlim_t rlim_cur; /* Soft limit (limite atual) */
rlim_t rlim_max; /* Hard limit (limite máximo) */
};
O soft limit pode ser aumentado até o hard limit por qualquer processo; o hard limit só pode ser reduzido (a menos que se tenha privilégios de root ou CAP_SYS_RESOURCE). As funções principais são:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlp);
int setrlimit(int resource, const struct rlimit *rlp);
Exemplo prático: alterar o limite de arquivos abertos em um processo filho:
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
struct rlimit rl;
// Obtém o limite atual de arquivos abertos
if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
perror("getrlimit");
exit(EXIT_FAILURE);
}
printf("Limite atual de arquivos: soft=%lu, hard=%lu\n",
(unsigned long)rl.rlim_cur, (unsigned long)rl.rlim_max);
// Aumenta o soft limit para 4096 (se hard limit permitir)
rl.rlim_cur = 4096;
if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
perror("setrlimit");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == 0) {
// Processo filho herda os limites
getrlimit(RLIMIT_NOFILE, &rl);
printf("Filho: limite de arquivos = %lu\n",
(unsigned long)rl.rlim_cur);
exit(0);
}
return 0;
}
3. Limites de Recursos Comuns e Seus Impactos
RLIMIT_NOFILE: Controla o número máximo de descritores de arquivo abertos. Um servidor web que não fecha conexões pode esgotar esse limite, impedindo novas conexões. Exemplo de verificação:
struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);
printf("Max open files: %lu\n", (unsigned long)rl.rlim_cur);
RLIMIT_AS e RLIMIT_DATA: Limitam o tamanho da memória virtual e do segmento de dados, respectivamente. Essenciais para prevenir estouro de memória (OOM) em processos que alocam dinamicamente sem controle. Quando o limite é excedido, malloc() retorna NULL.
struct rlimit rl;
rl.rlim_cur = 100 * 1024 * 1024; // 100 MB
rl.rlim_max = 200 * 1024 * 1024; // 200 MB
setrlimit(RLIMIT_AS, &rl);
RLIMIT_CPU: Limita o tempo de CPU em segundos. Ao atingir o soft limit, o processo recebe SIGXCPU; ao atingir o hard limit, recebe SIGKILL. Útil para loops infinitos acidentais.
RLIMIT_NPROC: Restringe o número de processos que um usuário pode criar, prevenindo fork bombs.
4. Limites Específicos: Stack, Core Dump e Prioridade
RLIMIT_STACK: Controla o tamanho máximo da pilha (stack). Útil para evitar stack overflow em programas com recursão profunda. O valor padrão é tipicamente 8 MB no Linux. Quando excedido, o processo recebe SIGSEGV.
struct rlimit rl;
rl.rlim_cur = 2 * 1024 * 1024; // 2 MB de pilha
rl.rlim_max = 4 * 1024 * 1024;
setrlimit(RLIMIT_STACK, &rl);
RLIMIT_CORE: Controla o tamanho máximo de core dumps. Definir como 0 desabilita core dumps, importante em ambientes de produção para evitar vazamento de dados sensíveis em disco. Definir como RLIM_INFINITY permite core dumps completos para debug.
RLIMIT_NICE e RLIMIT_RTPRIO: Controlam a prioridade de agendamento. RLIMIT_NICE define o valor máximo que nice() pode aumentar (prioridade mais baixa). RLIMIT_RTPRIO limita a prioridade máxima de tempo real.
5. Introdução ao cgroups (Control Groups) em C
cgroups é um mecanismo do kernel Linux que organiza processos em grupos hierárquicos e aplica limites de recursos a esses grupos. Diferente do ulimit, que é por processo, cgroups opera em coleções de processos, permitindo limites agregados.
A interface é baseada no sistema de arquivos virtual montado em /sys/fs/cgroup/. Cada subsistema (cpu, memory, pids, etc.) possui seu próprio diretório com arquivos de controle. Em C, manipulamos esses arquivos com funções padrão de I/O:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void write_cgroup_file(const char *path, const char *value) {
int fd = open(path, O_WRONLY);
if (fd == -1) {
perror("open cgroup file");
exit(EXIT_FAILURE);
}
if (write(fd, value, strlen(value)) == -1) {
perror("write cgroup file");
close(fd);
exit(EXIT_FAILURE);
}
close(fd);
}
6. Gerenciando Memória e CPU com cgroups via C
Exemplo completo: criar um cgroup filho, limitar memória e CPU, e anexar o processo atual:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void write_cgroup(const char *path, const char *value) {
FILE *f = fopen(path, "w");
if (!f) { perror(path); exit(1); }
fprintf(f, "%s", value);
fclose(f);
}
int main() {
// Criar diretório do cgroup filho
mkdir("/sys/fs/cgroup/memory/meu_grupo", 0755);
mkdir("/sys/fs/cgroup/cpu/meu_grupo", 0755);
// Limitar memória a 50 MB
write_cgroup("/sys/fs/cgroup/memory/meu_grupo/memory.max", "52428800");
// Limitar CPU a 50% de um core (50000 = 50ms em 100ms)
write_cgroup("/sys/fs/cgroup/cpu/meu_grupo/cpu.max", "50000 100000");
// Anexar o processo atual ao cgroup
char pid_str[32];
snprintf(pid_str, sizeof(pid_str), "%d", getpid());
write_cgroup("/sys/fs/cgroup/memory/meu_grupo/cgroup.procs", pid_str);
write_cgroup("/sys/fs/cgroup/cpu/meu_grupo/cgroup.procs", pid_str);
printf("Processo %d anexado ao cgroup meu_grupo\n", getpid());
// Simular alocação de memória (deve falhar após 50 MB)
void *p = malloc(60 * 1024 * 1024);
if (!p) printf("malloc falhou: limite de memória atingido!\n");
else free(p);
return 0;
}
7. Diferenças Práticas entre ulimit e cgroups
| Característica | ulimit | cgroups |
|---|---|---|
| Escopo | Por processo | Por grupo de processos |
| Herança | Filhos herdam do pai | Filhos permanecem no grupo |
| Dinamicidade | Apenas reduz hard limit | Pode ser alterado a qualquer momento |
| Hierarquia | Simples (pai-filho) | Hierarquia aninhada arbitrária |
| Subsistemas | Limitados a ~15 recursos | Dezenas de subsistemas (blkio, hugetlb, etc.) |
| Permissão | Root ou usuário (soft limit) | Root obrigatório |
Casos de uso: ulimit é ideal para scripts shell e programas simples que precisam de limites básicos. cgroups são essenciais para contêineres (Docker usa cgroups v2), ambientes multi-tenant e sistemas que exigem isolamento granular entre serviços.
8. Boas Práticas e Erros Comuns ao Gerenciar Limites
Verifique erros sempre: getrlimit e setrlimit retornam -1 em caso de falha. Use errno para diagnóstico:
#include <errno.h>
if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
if (errno == EPERM) {
printf("Permissão negada: hard limit não pode ser aumentado\n");
} else if (errno == EINVAL) {
printf("Recurso inválido ou rlim_cur > rlim_max\n");
}
}
Permissões: Para aumentar hard limits, o processo deve ter CAP_SYS_RESOURCE ou ser executado como root. Em cgroups, escrever em arquivos de controle geralmente requer root.
Testando limites: Para detectar violações, monitore sinais como SIGXCPU (tempo de CPU), SIGSEGV (stack overflow) ou verifique o retorno de malloc() (NULL quando RLIMIT_AS é excedido).
Erro comum: Esquecer que fork() herda os limites do pai, mas exec() mantém os limites. Ajuste antes de criar processos filhos.
Referências
- Linux man page: getrlimit(2) — Documentação oficial das chamadas de sistema getrlimit e setrlimit, com todos os recursos e códigos de erro.
- Linux man page: cgroups(7) — Visão completa do sistema cgroups no Linux, incluindo hierarquia, subsistemas e interface de arquivos.
- Red Hat: Resource Management Guide - cgroups — Guia prático da Red Hat sobre gerenciamento de recursos com cgroups v1 e v2.
- GNU C Library: Resource Usage and Limits — Seção da documentação da glibc sobre limites de recursos e funções relacionadas.
- Kernel.org: Control Group v2 — Documentação oficial do kernel sobre cgroups v2, com detalhes de implementação e todos os arquivos de controle.