Esta página aborda a interação entre o kswapd e o lmkd no Android,
demonstrando como eles funcionam juntos para gerenciar a pressão de memória.
Contexto
O Android usa dois mecanismos principais para recuperar memória quando o sistema está com pouca:
kswapd: um daemon padrão do kernel do Linux que recupera páginas removendo páginas limpas com suporte de arquivos ou trocando páginas anônimas para ZRAM.lmkd: daemon do Low Memory Killer do espaço do usuário do Android. Quando okswapdnão consegue acompanhar as demandas de memória e a pressão do sistema aumenta (conforme sinalizado pelo PSI), olmkdintervém para encerrar processos e liberar blocos maiores de memória.
kswapd e lmkd são dois sistemas complementares de recuperação de memória. Eles trabalham para
alcançar o mesmo objetivo (criar memória livre), mas por meios diferentes. Para comparar:
- Recuperável x destrutivo: o
kswapddescarta páginas de memória que podem ser lidas novamente do armazenamento ou troca páginas de memória para outras partições de memória ou armazenamento que podem ser lidas novamente sob demanda. Olmkdencerra processos, o que destrói o estado não volátil. - Largura de banda de recuperação: o
kswapdgasta mais CPU para recuperar uma determinada quantidade de memória livre do que olmkde leva mais tempo para recuperar essa memória. Olmkdpode recuperar grandes quantidades de memória em curtos períodos de esforço relativamente pequeno. Em dispositivos móveis, o esforço da CPU se traduz não apenas em latência, mas também em consumo de bateria.
Buckets de pontuação de OOM
O Android atribui um ajuste de pontuação de falta de memória (OOM, na sigla em inglês) (oom_score_adj) aos
processos com base na importância deles para o usuário. Os processos com pontuações mais altas
são encerrados primeiro.
Os buckets comuns incluem:
- Em cache (pontuação >= 900): processos que não são necessários no momento, mantidos para uma reinicialização mais rápida.
- Serviço (pontuação 500): processos que hospedam serviços iniciados.
- Serviço em primeiro plano (pontuação 200): processos que hospedam serviços em primeiro plano.
- Primeiro plano (pontuação 0): o app com que o usuário está interagindo no momento.
Investigação: acionamento de PSI e LMKD
O que é o PSI?
As informações de parada de pressão (PSI, na sigla em inglês) são um recurso do kernel do Linux que fornece uma maneira canônica de medir a escassez de recursos. Ele rastreia o tempo em que as tarefas são atrasadas devido à falta de CPU, memória e E/S.
PSI de memória: "some" versus "full"
O PSI de memória rastreia especificamente atrasos causados por falta de memória (por exemplo, espera por recuperação de página ou novas falhas). Ele fornece duas métricas:
some: a porcentagem de tempo em que pelo menos uma tarefa foi interrompida na memória. Isso indica que a falta de memória está afetando o desempenho, mas algum trabalho ainda está em andamento.full: a porcentagem de tempo em que todas as tarefas não ociosas foram interrompidas simultaneamente. Isso indica um estado crítico em que o sistema está completamente bloqueado aguardando memória.
Como o PSI aciona o LMKD
O daemon lmkd do espaço do usuário se registra no kernel para receber notificações
quando os limites de PSI são excedidos (monitorando /proc/pressure/memory). Quando
o sistema passa por uma alta pressão de memória (por exemplo, uma parada de full que excede uma
duração configurada), o kernel ativa o lmkd. Em seguida, o lmkd avalia o
estado do sistema e decide se vai encerrar processos e quais deles, com base
nas pontuações de OOM e no tamanho.
Ajuste do LMKD
O comportamento do LMKD pode ser ajustado usando propriedades do sistema.
Como encontrar valores de ajuste em um dispositivo
Para encontrar os valores de ajuste atuais do LMKD em um dispositivo conectado, execute:
adb shell getprop | grep lmk
Propriedades comuns do LMKD
Confira algumas das principais propriedades usadas para ajustar o LMKD (consulte a documentação do Android para ver a lista completa):
ro.lmk.use_psi: defina comotruepara usar monitores de PSI na detecção de pressão de memória (padrão no Android 10+).ro.lmk.psi_partial_stall_ms: o limite para a duração da parada (parcial)someem milissegundos para acionar um encerramento (padrão de 70 ms em dispositivos normais, 200 ms em dispositivos com pouca RAM).ro.lmk.psi_complete_stall_ms: o limite de duração da interrupção defullem milissegundos para acionar um encerramento (padrão de 700 ms).ro.lmk.kill_heaviest_task: se fortrue, o LMKD encerra o processo que usa mais memória entre os candidatos no mesmo bucket de OOM.
Valores de ajuste relevantes no dispositivo de teste (Google Pixel 8 Pro)
(Observação: padrões típicos para dispositivos de última geração com o Android 15)
ro.lmk.use_psi:truero.lmk.psi_partial_stall_ms:70(normal)ro.lmk.psi_complete_stall_ms:700(normal)ro.lmk.kill_heaviest_task:true
Verificação de ZRAM e swap
A capacidade da ZRAM afeta muito a velocidade com que a troca é preenchida e aciona as eliminações do LMKD. É possível verificar o status da ZRAM e da troca no dispositivo usando estes comandos:
# Check ZRAM sizing and usage
adb shell free -m
# Check swap partition details
adb shell cat /proc/swaps
Exercício prático: acionar kswapd e lmkd
Neste exercício, você vai usar o app de exemplo KswapdLmkdLab para criar
processos em diferentes buckets de OOM e, em seguida, consumir memória no processo principal
para acionar kswapd e lmkd.
O app é configurado para gerar subprocessos automaticamente e alocar memória para torná-los alvos atraentes para o LMKD.
1. Criar subprocessos
O app gera automaticamente os seguintes subprocessos ao ser iniciado:
- Processo em cache (oom_score_adj >= 900)
- Processo de serviço (oom_score_adj 500)
- Processo de serviço em primeiro plano (oom_score_adj 200)
Verifique os processos e as pontuações de OOM usando o ADB:
adb shell dumpsys activity processes | grep com.android.kswapdlmkdlab
Exemplo de snippet de saída:
Proc # 0: fg T/A/TOP LCMNFUATI t: 0 18959:com.android.kswapdlmkdlab/u0a358 (top-activity)
Proc # 1: prcp b/ /FGS -------TI t: 0 19050:com.android.kswapdlmkdlab:foreground/u0a358 (fg-service)
Proc # 2: svc b/ /SVC -------TI t: 0 19077:com.android.kswapdlmkdlab:service/u0a358 (started-services)
Proc # 3: cch b/ /CACC -------TI t: 0 19014:com.android.kswapdlmkdlab:cached/u0a358 (cch-client-act)
As abreviações de estado do processo correspondem a:
fg: primeiro plano (pontuação de OOM 0)prcp: perceptível (serviço em primeiro plano, pontuação de OOM 200)svc: serviço iniciado (pontuação de OOM 500)cch: processo em cache (pontuação de OOM 900)
2. Acionar pressão da memória
O app vai começar a alocar memória automaticamente no processo principal. Para que o teste seja eficaz em dispositivos com muita RAM, o app também comanda os subprocessos para alocar memória (por exemplo, 500 MB cada) para que eles sejam melhores alvos para o LMKD.
Monitorar a morte de subprocessos
O app monitora a morte desses subprocessos usando o
mecanismo IBinder.linkToDeath:
- O processo principal se vincula a um serviço no processo de destino usando
bindService. - Após a conexão, ele recebe o objeto
IBinderque representa o serviço. - Ele registra uma implementação de
IBinder.DeathRecipientchamandolinkToDeathno binder. - Quando o processo de destino é encerrado (por exemplo, quando é encerrado pelo LMKD), o sistema
chama o método
binderDied(), permitindo que o app registre o evento de encerramento de forma assíncrona.
Para evitar que essa vinculação eleve a prioridade dos processos de destino,
o que anularia o propósito do exercício, o app usa a
flag Context.BIND_WAIVE_PRIORITY.
App de exemplo em ação
Esta é uma captura de tela do app após a conclusão de um teste em um Google Pixel 8 Pro (que tem aproximadamente 12 GB de RAM total).
Análise de trace do Perfetto
Para capturar a interação, use a seguinte configuração do Perfetto para capturar programação, contadores de memória, encerramentos do LMK e informações de parada de pressão (PSI, na sigla em inglês).
Configuração do Perfetto
Capture a interação executando o Perfetto com programação, contadores de memória, encerramento de LMK e informações de parada de pressão (PSI, na sigla em inglês):
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/kswapd_lmkd.perfetto-trace <<EOF
buffers: { size_kb: 131072 }
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "lowmemorykiller/lowmemory_kill"
ftrace_events: "vmscan/mm_vmscan_kswapd_wake"
ftrace_events: "vmscan/mm_vmscan_kswapd_sleep"
ftrace_events: "psi/psi_event"
}
}
}
duration_ms: 180000
EOF
Analisar o trace
A captura de tela abaixo foi tirada de um rastro que mostra o período
pouco antes de o processo :cached (mais eliminável) ser encerrado até o
processo :foreground (menos eliminável) ser encerrado.
É possível notar que o MemFree já está bem baixo e sob pressão constante.
O kswapd está trabalhando constantemente e alivia a pressão descartando páginas
mem.rss.file e movendo mem.rss.anon para mem.swap de todos os processos
kswapdlmkdlab (o processo principal e os subprocessos) e provavelmente de outros
processos não mostrados nesta captura de tela. Ao mesmo tempo, o app kswapdlmkdlab
continua alocando memória, aumentando a pressão mais rápido do que o kswapd pode
recuperar.
Quando a pressão fica muito alta, como visto em psi.mem sendo alta por um
período suficiente, o lmkd é ativado e encerra alguns processos. É possível ver
que lmkd respeita as pontuações de OOM, encerrando primeiro kswapdlmkdlab:cached, depois
:service e, em seguida, :foreground.
Analise a linha do tempo dos eventos e reflita sobre o seguinte:
- Como
kswapdelmkdse complementam:kswapdé o primeiro a responder à pressão de memória, maslmkdajuda quando a largura de banda de recuperação dekswapdnão está atendendo à demanda de alocação. - A taxa de memória que está sendo liberada: observe o tempo de CPU gasto em
kswapde a memória que está sendo recuperada ao descartarmem.rss.fileou trocarmem.rss.anonpormem.swap. Compare isso com as curtas intermitências de atividade nolmkd, que resultam na liberação de grandes quantidades de memória privada do processo.
Consultas
Execute estas consultas na interface do Perfetto (Query SQL) para extrair dados do trace.
1. Atividade do kswapd
Para descobrir quando o daemon de troca do kernel estava ativo e em execução, execute:
SELECT ts, dur, state
FROM thread_state
JOIN thread USING (utid)
WHERE thread.name LIKE 'kswapd%'
AND state = 'Running'
LIMIT 5;
Exemplo de resposta:
| ts | dur | estado |
|---|---|---|
| 30317633052969 | 159180 | Em execução |
| 30317633398347 | 3999186 | Em execução |
| 30317638448111 | 1847941 | Em execução |
| 30317641455354 | 1031006 | Em execução |
| 30317643272615 | 888387 | Em execução |
2. Encerramentos do LMKD
O LMKD grava as eliminações por falta de memória na tabela instant. Para consultar todas as eliminações do LMKD:
SELECT ts, name
FROM instant
WHERE name LIKE 'lmk,%'
ORDER BY ts ASC;
O formato do nome do evento é:
lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>.
kill_reason: um número inteiro que indica por que olmkddecidiu encerrar o processo (definido emsystem/memory/lmkd/statslog.h):0:PRESSURE_AFTER_KILL1:NOT_RESPONDING2:LOW_SWAP_AND_THRASHING(o espaço de troca do ZRAM está extremamente baixo e ocorreu thrashing)3:LOW_MEM_AND_SWAP(o sistema está com pouca memória livre e troca de ZRAM)4:LOW_MEM_AND_THRASHING5:DIRECT_RECL_AND_THRASHING6:LOW_MEM_AND_SWAP_UTIL7:LOW_FILECACHE_AFTER_THRASHING8:LOW_MEM9:DIRECT_RECL_STUCK
max_thrashing: a porcentagem máxima de thrashing de página no momento do encerramento.
Exemplo de resposta:
| ts | nome |
|---|---|
| 30326148679967 | lmk,2308,3,900,201,0 |
| 30330439763710 | lmk,2309,3,500,201,0 |
| 30334985999837 | lmk,2310,2,200,0,307 |
Visualização
Para visualizar os dados, use o seguinte script gnuplot para traçar a memória livre do sistema, a atividade kswapd, as eliminações do lmkd e o PSI em uma única série temporal. O script pressupõe que ele é executado no diretório superior do build do Android.
Exemplo de gráfico de saída
O gráfico a seguir foi gerado usando o script anterior com dados de amostra representativos da execução de teste.
Explicação do gráfico:
Este gráfico ilustra a interação dinâmica entre os diferentes componentes de gerenciamento de memória durante o teste de estresse:
- Queda na memória livre: à medida que o app principal aloca memória, a memória livre do sistema (linha roxa) diminui de forma constante.
- Ativação do kswapd: quando a memória livre fica abaixo de determinados limites,
o
kswapdé ativado (impulsos azuis) para recuperar memória ao trocar para a ZRAM. Isso é visível como picos intermitentes. - Picos de PSI: como o
kswapdtem dificuldade para acompanhar a alta taxa de alocação, as linhas de execução começam a travar na alocação de memória, fazendo com que o PSI (informações de travamento por pressão, na sigla em inglês) (linha verde) tenha um pico. - Encerramentos do LMKD: quando o PSI atinge níveis críticos (por exemplo, excedendo o
limite de
ro.lmk.psi_complete_stall_msconceitualmente) e a memória livre está extremamente baixa, olmkdintervém encerrando processos de baixa prioridade (impulsos laranja nos tempos 30 e 45) para liberar grandes blocos de memória. Observe que, imediatamente após um encerramento, a memória livre aumenta e o PSI cai, mostrando a eficácia do encerramento em aliviar a pressão. ________________________________________________________________________________
← Recuperação de memória: remoção e troca | ↑ Voltar | Retornar páginas de memória ao SO →