O desktop congelou: quando um futex lockup vira caso de Cgroups v2#
Quem utiliza a estação de trabalho diariamente com múltiplos terminais, navegadores com dezenas de abas e containers sabe que a linha entre produtividade máxima e um desktop congelado pode ser tênue. Dias atrás, me deparei com uma lentidão extrema na minha estação (Linux Mint com Cinnamon): o ponteiro do mouse movia com atraso, as janelas pararam de responder e o sistema operava aos soluços.
Em vez de forçar um desligamento no botão de energia, abri um terminal tty, coletei métricas e investiguei a causa raiz até o nível do kernel.
Abaixo, compartilho o diagnóstico detalhado, as ferramentas utilizadas e as soluções de contenção aplicadas para blindar o ambiente.
1. O sintoma e o diagnóstico inicial via htop#
Ao abrir o htop, o cenário era crítico:
- Load Average:
8.96, 6.81, 4.78em uma CPU de 4 núcleos. - Uso de CPU: ~78% sustentado em todos os cores.
- Memória e Swap: De 9.17 GB de RAM física, 4.36 GB estavam alocados e 1.02 GB / 12.0 GB de Swap estavam em uso ativo.
- Processos no Topo: Múltiplas instâncias de
cinnamon --replaceconsumindo entre 14% e 21% de CPU cada, acompanhadas por renderers do Chromium/Brave e terminais Electron (tabby) com chamadas ativas de--crashpad-handler.
Dois problemas principais ficaram evidentes:
- Memory Pressure e Swap Thrashing: A paginação ativa em swap causava page faults constantes ao alternar entre janelas.
- Crash Loop no Window Manager: O Cinnamon estava preso em um loop de renderização aguardando recursos de I/O de GPU/Xorg.
2. Descendo ao kernel: strace e contenção de futex#
Para entender o que o processo principal do Cinnamon (cinnamon --replace) estava executando, fiz um tracing de chamadas de sistema:
strace -p <PID_CINNAMON> -c -- sleep 10
O resultado revelou uma patologia clássica de concorrência:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99,37 0,230989 106 2173 113 futex
0,18 0,000414 3 120 write
0,16 0,000372 372 1 execve
...
100,00 0,232457 98 2349 118 total
Mais de 99,3% do tempo de CPU estava preso na syscall futex (Fast Userspace Mutex), com mais de 100 erros do tipo ETIMEDOUT/EAGAIN. O ambiente gráfico estava em Busy Waiting, tentando adquirir travas de sincronização de threads que nunca eram liberadas.
3. Investigando o subsistema do kernel: vmstat e context switching#
Para avaliar o impacto geral no agendador de tarefas (scheduler), executei o vmstat:
vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd livre buff cache si so bi bo in cs us sy id wa st gu
8 0 1063936 2155216 133344 3756084 38 138 465 688 3614 17 34 12 52 2 0 0
10 1 1063936 2164608 133344 3749040 0 0 0 0 4249 12005 20 38 41 0 0 0
9 0 1063936 2162456 133344 3746108 0 0 0 0 5491 12703 37 33 29 1 0 0
8 0 1063936 2155848 133352 3746100 0 0 0 132 5590 13532 29 55 16 1 0 0
Os dados mostraram:
- Context Switches (
cs): Picos superiores a 13.500 trocas/s. - Interrupções (
in): Acima de 5.500/s. - CPU System (
sy): Chegou a 55%.
Quando o consumo de CPU em modo Kernel (sy) supera o espaço de usuário (us), o sistema operacional está gastando mais energia gerenciando a fila de processos e travas de sincronização do que executando tarefas reais.
4. OOM score e processos órfãos do chromium#
Ao tentar inspecionar as threads do Cinnamon via ps -Lp, o processo já havia sido terminado, mas a carga continuava alta. Para identificar quais processos estavam mais vulneráveis ao encerramento pelo kernel:
printf "PID\tOOM_SCORE\tCOMMAND\n"
for proc in $(ls /proc | grep -E '^[0-9]+'); do
if [ -f /proc/$proc/oom_score ]; then
printf "$proc\t$(cat /proc/$proc/oom_score)\t$(cat /proc/$proc/comm)\n"
fi
done | sort -k2 -nr | head -n 10
Resultado:
PID OOM_SCORE COMMAND
3547404 876 brave
3547749 874 brave
3547682 874 brave
3547658 872 brave
Vários subprocessos do navegador estavam com oom_score acima de 870 (escala de 0 a 1000) e consumindo entre 30% e 40% de CPU cada. Com a queda do Window Manager, esses processos filhos perderam a comunicação com o servidor gráfico e entraram em spinlocks no espaço de usuário.
5. I/o stalls: o spike silencioso do flatpak#
Após finalizar os renderers travados (pkill -9) e zerar o swap, a memória normalizou (6 GB livres), mas o sistema ainda apresentava micro-travamentos.
Rodei o iotop em modo batch para verificar o tráfego de disco:
iotop -o -b -n 2
Total DISK WRITE: 29.84 M/s
TID PRIO USER DISK WRITE COMMAND
3683027 be/4 sr00t3d 3.04 M/s flatpak-update-worker
640 be/3 root 54.11 K/s [jbd2/sda7-8]
Um processo em background (flatpak-update-worker) começou a escrever quase 30 MB/s exatamente no momento em que o sistema tentava se reestruturar. O processo jbd2 (journaling do ext4) precisou realizar sucessivos commits de transações, competindo por I/O.
Inspecionando os indicadores de PSI (Pressure Stall Information):
cat /proc/pressure/memory
# some avg10=0.00 avg60=0.00 avg300=0.00 (Sem pressão de RAM)
cat /proc/pressure/io
# some avg10=2.25 avg60=3.64 avg300=3.16
# full avg10=1.38 avg60=2.44 avg300=2.17
A métrica full avg10=1.38 confirmou que durante 1.38% do tempo todas as tarefas ativas ficaram bloqueadas aguardando a liberação da fila de I/O do disco.
6. A armadilha do zramctl --reset#
Durante a tentativa de limpar os blocos em cache do swap comprimido, executei:
swapoff /dev/zram0
zramctl --reset /dev/zram0
swapon /dev/zram0 -p 100
O comando retornou o erro: swapon: não foi possível abrir /dev/zram0: Arquivo ou diretório inexistente
Causa Raiz: O comando zramctl --reset não apenas limpa a memória, mas destrói o dispositivo de bloco virtual no kernel. Para recriá-lo corretamente:
# 1. Aloca um novo bloco virtual zram de 4GB
zramctl --find --size 4G
# 2. Formata como swap
mkswap /dev/zram0
# 3. Ativa com alta prioridade
swapon /dev/zram0 -p 100
7. Solução definitiva: isolando aplicações com Cgroups v2#
Para evitar que navegadores ou clientes Electron sequestrem a RAM e arrastem o desktop inteiro junto em caso de vazamento de memória, implementei uma contenção via Systemd Slice.
1. Criando a fatia de recursos#
Crie o arquivo ~/.config/systemd/user/apps-limited.slice:
[Unit]
Description=Slice limitada para Apps pesados
Before=slices.target
[Slice]
MemoryMax=4G
MemoryHigh=3.5G
CPUWeight=50
IOWeight=50
2. Tratando o socket de d-bus ao rodar via automações#
Ao rodar systemctl --user daemon-reload a partir de uma sessão administrativa, o erro de D-Bus ocorre porque o shell não herda as variáveis da sessão gráfica. Aponte o socket explicitamente:
USER_UID=$(id -u $USER)
XDG_RUNTIME_DIR=/run/user/$USER_UID DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$USER_UID/bus systemctl --user daemon-reload
3. Wrapper de execução automática#
Para garantir que o navegador sempre inicialize dentro da fatia limitada, criei /usr/local/bin/brave:
#!/bin/bash
TARGET_USER="$USER"
USER_ID=$(id -u $TARGET_USER)
export XDG_RUNTIME_DIR="/run/user/$USER_ID"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus"
systemd-run --user --slice=apps-limited.slice --scope /usr/bin/brave-browser "$@"
chmod +x /usr/local/bin/brave
Com isso, ao atingir 3.5 GB (MemoryHigh), o kernel aplica throttling suave; ao atingir 4.0 GB (MemoryMax), o kernel encerra apenas a aba agressora dentro do cgroup, preservando a interface gráfica intacta.
Considerações práticas de estabilidade#
Problemas de desempenho raramente surgem de forma isolada: a saturação de memória levou ao uso excessivo de swap, gerando contenção de travas (futex) no compositor do Cinnamon, que por sua vez colapsou e deixou renderers órfãos disputando CPU com atualizações em lote de disco.
Tratar travamentos recorrentes não é uma questão de reiniciar a máquina a cada congelamento, mas de compreender a cadeia de eventos no kernel, isolar aplicações pesadas via Cgroups v2 e gerenciar o agendador de memória de forma previsível.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários