En esta página, se explica la interacción entre kswapd y lmkd en Android, y se muestra cómo funcionan juntos para administrar la presión de memoria.
Fondo
Android usa dos mecanismos principales para recuperar memoria cuando el sistema se queda sin espacio:
kswapd: Es un daemon estándar del kernel de Linux que recupera páginas expulsando páginas limpias respaldadas por archivos o intercambiando páginas anónimas a ZRAM.lmkd: Es el daemon de optimización de poca memoria del espacio de usuario de Android. Cuandokswapdno puede satisfacer las demandas de memoria y aumenta la presión del sistema (como lo indica la PSI),lmkdinterviene para finalizar los procesos y liberar fragmentos de memoria más grandes.
kswapd y lmkd son dos sistemas complementarios de recuperación de memoria. Ambos trabajan para lograr el mismo objetivo (crear memoria libre), pero por diferentes medios. Para comparar y contrastar:
- Recuperable frente a destructivo:
kswapddescarta las páginas de memoria que se pueden volver a leer desde el almacenamiento o intercambia páginas de memoria con otras particiones de memoria o almacenamiento que se pueden volver a leer a pedido.lmkdfinaliza los procesos, lo que destruye el estado no volátil. - Ancho de banda de recuperación:
kswapdconsume más CPU para recuperar una cantidad determinada de memoria libre quelmkdy tarda más en recuperar dicha memoria.lmkdpuede recuperar grandes cantidades de memoria en ráfagas cortas de esfuerzo relativamente bajo. Ten en cuenta que, en los dispositivos móviles, el esfuerzo de la CPU se traduce no solo en latencia, sino también en energía de la batería.
Buckets de puntuación de OOM
Android asigna un ajuste de puntuación de falta de memoria (OOM) (oom_score_adj) a los procesos según su importancia para el usuario. Los procesos con puntuaciones más altas son los primeros en cerrarse.
Entre los buckets comunes, se incluyen los siguientes:
- En caché (puntuación >= 900): Son procesos que no se necesitan en el momento y se conservan para un reinicio más rápido.
- Servicio (puntuación de 500): Son procesos que alojan servicios iniciados.
- Servicio en primer plano (puntuación de 200): Son los procesos que alojan servicios en primer plano.
- Primer plano (puntuación 0): Es la app con la que el usuario interactúa en ese momento.
Análisis detallado: Activación de PSI y LMKD
¿Qué es PSI?
La información de bloqueo por presión (PSI) es una función del kernel de Linux que proporciona una forma canónica de medir la escasez de recursos. Realiza un seguimiento del tiempo que se retrasan las tareas debido a la escasez de CPU, memoria y E/S.
PSI de memoria: "some" frente a "full"
La PSI de memoria hace un seguimiento específico de los retrasos causados por la falta de memoria (p.ej., la espera de la recuperación de páginas o los errores de página). Proporciona dos métricas:
some: Es el porcentaje de tiempo durante el cual se detuvo al menos una tarea en la memoria. Esto indica que la falta de memoria afecta el rendimiento, pero que aún se está realizando trabajo.full: Es el porcentaje de tiempo durante el cual todas las tareas no inactivas se detuvieron de forma simultánea. Esto indica un estado crítico en el que el sistema está completamente bloqueado esperando memoria.
Cómo PSI activa LMKD
El daemon lmkd del espacio del usuario se registra con el kernel para recibir notificaciones
cuando se superan los umbrales de PSI (mediante la supervisión de /proc/pressure/memory). Cuando
el sistema experimenta una alta presión de memoria (p.ej., una detención de full que supera una
duración configurada), el kernel activa lmkd. Luego, lmkd evalúa el estado del sistema y decide si debe cerrar procesos y cuáles, según sus puntuaciones de OOM y su tamaño.
Ajuste de LMKD
El comportamiento de LMKD se puede ajustar con las propiedades del sistema.
Cómo encontrar valores de ajuste en un dispositivo
Para encontrar los valores de ajuste actuales de LMKD en un dispositivo conectado, ejecuta lo siguiente:
adb shell getprop | grep lmk
Propiedades comunes de LMKD
Estas son algunas de las propiedades clave que se usan para ajustar el LMKD (consulta la documentación de Android para obtener la lista completa):
ro.lmk.use_psi: Se establece entruepara usar los monitores de PSI para la detección de presión de memoria (predeterminado en Android 10 y versiones posteriores).ro.lmk.psi_partial_stall_ms: Es el umbral de duración de la detención (parcial) desomeen milisegundos para activar una finalización (el valor predeterminado es 70 ms en dispositivos normales y 200 ms en dispositivos con poca RAM).ro.lmk.psi_complete_stall_ms: Es el umbral de duración de la detención defullen milisegundos para activar una finalización (el valor predeterminado es 700 ms).ro.lmk.kill_heaviest_task: Si estrue, LMKD finaliza el proceso que usa la mayor cantidad de memoria entre los candidatos del mismo bucket de OOM.
Valores de ajuste relevantes en el dispositivo de prueba (Google Pixel 8 Pro)
(Nota: Estos son los valores predeterminados típicos para los dispositivos de alta gama que ejecutan Android 15)
ro.lmk.use_psi:truero.lmk.psi_partial_stall_ms:70(típico)ro.lmk.psi_complete_stall_ms:700(típico)ro.lmk.kill_heaviest_task:true
Verificación de ZRAM y swap
La capacidad de ZRAM afecta en gran medida la rapidez con la que se llena el intercambio y activa las finalizaciones de LMKD. Puedes verificar el estado de ZRAM y el intercambio en el dispositivo con estos comandos:
# Check ZRAM sizing and usage
adb shell free -m
# Check swap partition details
adb shell cat /proc/swaps
Ejercicio práctico: Activa kswapd y lmkd
En este ejercicio, usarás la app de ejemplo KswapdLmkdLab para crear procesos en diferentes buckets de OOM y, luego, consumir memoria en el proceso principal para activar kswapd y lmkd.
La app está configurada para generar subprocesos automáticamente y asignar memoria para que sean objetivos atractivos para LMKD.
1. Crea subprocesos
La app genera automáticamente los siguientes subprocesos cuando se inicia:
- Proceso almacenado en caché (oom_score_adj >= 900)
- Proceso de servicio (oom_score_adj 500)
- Proceso de servicio en primer plano (oom_score_adj 200)
Verifica los procesos y sus puntuaciones de OOM con ADB:
adb shell dumpsys activity processes | grep com.android.kswapdlmkdlab
Fragmento de resultado de muestra:
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)
Las abreviaturas de los estados del proceso corresponden a lo siguiente:
fg: En primer plano (puntuación de OOM de 0)prcp: Perceptible (servicio en primer plano, puntuación de OOM de 200)svc: Servicio iniciado (puntuación de OOM de 500)cch: Proceso almacenado en caché (puntuación de OOM de 900)
2. Activa la presión de la memoria
La app comenzará a asignar memoria automáticamente en el proceso principal. Para que la prueba sea eficaz en dispositivos con mucha RAM, la app también les ordena a los subprocesos que asignen memoria (p.ej., 500 MB cada uno) para que sean mejores objetivos para el LMKD.
Supervisa la finalización de subprocesos
La app supervisa la finalización de estos subprocesos con el mecanismo IBinder.linkToDeath:
- El proceso principal se vincula a un servicio en el proceso de destino con
bindService. - Cuando se conecta, obtiene el objeto
IBinderque representa el servicio. - Registra una implementación de
IBinder.DeathRecipientllamando alinkToDeathen el binder. - Cuando el proceso de destino finaliza (por ejemplo, cuando lo finaliza LMKD), el sistema llama al método
binderDied(), lo que permite que la app registre el evento de finalización de forma asíncrona.
Para evitar que esta vinculación eleve la prioridad de los procesos de destino (lo que anularía el propósito del ejercicio), la app usa la marca Context.BIND_WAIVE_PRIORITY.
App de ejemplo en acción
Esta es una captura de pantalla de la app después de que se completó una ejecución de prueba en un Google Pixel 8 Pro (que tiene aproximadamente 12 GB de RAM total).
Análisis de seguimiento de Perfetto
Para capturar la interacción, usa la siguiente configuración de Perfetto para capturar la programación, los contadores de memoria, las finalizaciones de LMK y la información de detención de presión (PSI).
Configuración de Perfetto
Captura la interacción ejecutando Perfetto con programación, contadores de memoria, finalizaciones de LMK y PSI:
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
Revisa el seguimiento
La siguiente captura de pantalla se toma de un registro que muestra el período poco antes de que se finalice el proceso :cached (más propenso a finalizarse) hasta que se finalice el proceso :foreground (menos propenso a finalizarse).
Puedes ver que MemFree ya es bastante bajo y está bajo presión constante.
kswapd trabaja constantemente y alivia la presión descartando páginas de mem.rss.file
y moviendo mem.rss.anon a mem.swap de todos los procesos de kswapdlmkdlab (el proceso principal y los procesos secundarios) y, probablemente, de otros
procesos que no se muestran en esta captura de pantalla. Al mismo tiempo, la app de kswapdlmkdlab
sigue asignando memoria, lo que aumenta la presión más rápido de lo que kswapd puede
recuperar.
Cuando la presión es demasiado alta, como se ve en el valor alto de psi.mem durante un período suficiente, lmkd se activa y finaliza algunos procesos. Puedes ver
que lmkd respeta las puntuaciones de OOM, ya que finaliza primero kswapdlmkdlab:cached, luego
:service y, por último, :foreground.
Revisa la cronología de los eventos y reflexiona sobre lo siguiente:
- Cómo se complementan
kswapdylmkd:kswapdes el primero en responder a la presión de memoria, perolmkdayuda cuando el ancho de banda de recuperación dekswapdno cumple con la demanda de asignación. - La tasa de memoria que se libera: observa el tiempo de CPU que se dedica a
kswapdy la memoria que se recupera descartandomem.rss.fileo intercambiandomem.rss.anonpormem.swap. Compara esto con las ráfagas breves de actividad enlmkdque generan la liberación de grandes cantidades de memoria privada del proceso.
Consultas
Ejecuta estas consultas en la IU de Perfetto (Query SQL) para extraer datos de tu registro.
1. Actividad de kswapd
Para saber cuándo estuvo activo y en ejecución el daemon de intercambio del kernel, ejecuta lo siguiente:
SELECT ts, dur, state
FROM thread_state
JOIN thread USING (utid)
WHERE thread.name LIKE 'kswapd%'
AND state = 'Running'
LIMIT 5;
Resultado de muestra:
| ts | dur | state |
|---|---|---|
| 30317633052969 | 159180 | En ejecución |
| 30317633398347 | 3999186 | En ejecución |
| 30317638448111 | 1847941 | En ejecución |
| 30317641455354 | 1031006 | En ejecución |
| 30317643272615 | 888387 | En ejecución |
2. Eliminaciones de LMKD
LMKD escribe las finalizaciones por poca memoria en la tabla instant. Para consultar todas las finalizaciones de LMKD, haz lo siguiente:
SELECT ts, name
FROM instant
WHERE name LIKE 'lmk,%'
ORDER BY ts ASC;
El formato del nombre del evento es el siguiente:
lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>.
kill_reason: Es un número entero que indica por quélmkddecidió finalizar el proceso (definido ensystem/memory/lmkd/statslog.h):0:PRESSURE_AFTER_KILL1:NOT_RESPONDING2:LOW_SWAP_AND_THRASHING(el espacio de intercambio de ZRAM es extremadamente bajo y se produjo una sobrecarga)3:LOW_MEM_AND_SWAP(el sistema tiene poca memoria libre y poco espacio de intercambio 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: Es el porcentaje máximo de hiperpaginación en el momento de la finalización.
Resultado de muestra:
| ts | nombre |
|---|---|
| 30326148679967 | lmk,2308,3,900,201,0 |
| 30330439763710 | lmk,2309,3,500,201,0 |
| 30334985999837 | lmk,2310,2,200,0,307 |
Visualización
Para visualizar los datos, puedes usar la siguiente secuencia de comandos de gnuplot para trazar la memoria libre del sistema, la actividad de kswapd, las finalizaciones de lmkd y el PSI en una sola serie temporal. La secuencia de comandos supone que se ejecuta desde el directorio superior de la compilación de Android.
Gráfico de resultados de muestra
El siguiente gráfico se generó con la secuencia de comandos anterior y datos de muestra representativos de la ejecución de prueba.
Explicación del gráfico:
En este gráfico, se ilustra la interacción dinámica entre los diferentes componentes de la administración de la memoria durante la prueba de esfuerzo:
- Caída de la memoria libre: A medida que la app principal asigna memoria, la memoria libre del sistema (línea morada) disminuye de forma constante.
- Activación de kswapd: Cuando la memoria libre cae por debajo de ciertos límites,
kswapdse activa (impulsos azules) para recuperar memoria intercambiando a ZRAM. Esto se puede ver como picos intermitentes. - Picos de PSI: A medida que
kswapdtiene dificultades para mantenerse al día con la alta tasa de asignación, los subprocesos comienzan a detenerse en la asignación de memoria, lo que hace que la PSI (información de detención de presión) (línea verde) aumente. - Finalizaciones de LMKD: Cuando la PSI alcanza niveles críticos (p.ej., supera el umbral de
ro.lmk.psi_complete_stall_msconceptualmente) y la memoria libre es extremadamente baja,lmkdinterviene finalizando los procesos de baja prioridad (impulsos naranjas en los momentos 30 y 45) para liberar grandes fragmentos de memoria. Observa que, inmediatamente después de una finalización, la memoria libre aumenta y el PSI disminuye, lo que demuestra la eficacia de la finalización para aliviar la presión. ________________________________________________________________________________
← Recuperación de memoria: expulsión e intercambio | ↑ Arriba | Devuelve páginas de memoria al SO →