Questa pagina illustra l'interazione tra kswapd e lmkd su Android,
mostrando come collaborano per gestire la pressione della memoria.
Sfondo
Android utilizza due meccanismi principali per recuperare memoria quando il sistema è in esaurimento:
kswapd: un daemon del kernel Linux standard che recupera le pagine rimuovendo le pagine pulite supportate da file o scambiando le pagine anonime con ZRAM.lmkd: il daemon Low Memory Killer dello spazio utente di Android. Quandokswapdnon riesce a soddisfare le richieste di memoria e la pressione sul sistema aumenta (come segnalato da PSI),lmkdinterviene per terminare i processi e liberare blocchi di memoria più grandi.
kswapd e lmkd sono due sistemi di recupero della memoria complementari. Entrambi lavorano per raggiungere lo stesso obiettivo (creare memoria libera), ma con mezzi diversi. Per confrontare e contrapporre:
- Recuperabile e distruttivo:
kswapdelimina le pagine di memoria che possono essere lette dallo spazio di archiviazione o scambia le pagine di memoria con altre partizioni di memoria o con uno spazio di archiviazione che può essere letto on demand.lmkdtermina i processi, distruggendo lo stato non volatile. - Larghezza di banda di recupero:
kswapdutilizza più CPU per recuperare una determinata quantità di memoria libera rispetto almkde impiega più tempo per recuperare la memoria.lmkdpuò recuperare grandi quantità di memoria in brevi periodi di tempo con un impegno relativamente ridotto. Tieni presente che sui dispositivi mobili, l'utilizzo della CPU si traduce non solo in latenza, ma anche in consumo della batteria.
Bucket di punteggi OOM
Android assegna un aggiustamento del punteggio Out-Of-Memory (OOM) (oom_score_adj) ai processi in base alla loro importanza per l'utente. I processi con punteggi più alti vengono terminati per primi.
I bucket più comuni includono:
- Memorizzati nella cache (punteggio >= 900): processi non necessari al momento, conservati per un riavvio più rapido.
- Servizio (punteggio 500): processi che ospitano i servizi avviati.
- Servizio in primo piano (punteggio 200): processi che ospitano servizi in primo piano.
- In primo piano (punteggio 0): l'app con cui l'utente sta interagendo al momento.
Approfondimento: attivazione di PSI e LMKD
Che cos'è PSI?
Pressure Stall Information (PSI) è una funzionalità del kernel Linux che fornisce un modo canonico per misurare le carenze di risorse. Tiene traccia del tempo di ritardo delle attività a causa di carenze di CPU, memoria e I/O.
PSI della memoria: "some" e "full"
Il PSI della memoria monitora in modo specifico i ritardi causati da carenze di memoria (ad es. attesa di recupero o errori di pagina). Fornisce due metriche:
some: la percentuale di tempo durante la quale almeno un'attività è stata bloccata nella memoria. Ciò indica che la carenza di memoria sta influendo sulle prestazioni, ma alcune attività sono ancora in corso.full: la percentuale di tempo durante la quale tutte le attività non inattive sono state bloccate contemporaneamente. Questo indica uno stato critico in cui il sistema è completamente bloccato in attesa di memoria.
In che modo PSI attiva LMKD
Il daemon userspace lmkd si registra con il kernel per ricevere notifiche quando vengono superate le soglie PSI (monitorando /proc/pressure/memory). Quando il sistema subisce un'elevata pressione della memoria (ad es. un blocco di full che supera una durata configurata), il kernel attiva lmkd. lmkd valuta quindi lo stato del sistema e decide se terminare i processi e quali, in base ai punteggi OOM e alle dimensioni.
Ottimizzazione LMKD
Il comportamento di LMKD può essere ottimizzato utilizzando le proprietà di sistema.
Trovare i valori di ottimizzazione su un dispositivo
Puoi trovare i valori di ottimizzazione LMKD correnti su un dispositivo connesso eseguendo:
adb shell getprop | grep lmk
Proprietà LMKD comuni
Ecco alcune delle proprietà principali utilizzate per ottimizzare LMKD (consulta la documentazione di Android per l'elenco completo):
ro.lmk.use_psi: imposta sutrueper utilizzare i monitor PSI per il rilevamento della pressione della memoria (impostazione predefinita su Android 10 e versioni successive).ro.lmk.psi_partial_stall_ms: la soglia per la durata del blocco (parziale) disomein millisecondi per attivare un'interruzione (valore predefinito 70 ms sui dispositivi normali, 200 ms sui dispositivi con poca RAM).ro.lmk.psi_complete_stall_ms: la soglia per la durata del blocco difullin millisecondi per attivare un'interruzione (valore predefinito 700 ms).ro.lmk.kill_heaviest_task: setrue, LMKD termina il processo che utilizza più memoria tra i candidati nello stesso bucket OOM.
Valori di ottimizzazione pertinenti sul dispositivo di test (Google Pixel 8 Pro)
(Nota: valori predefiniti tipici per i dispositivi di fascia alta con Android 15)
ro.lmk.use_psi:truero.lmk.psi_partial_stall_ms:70(valore tipico)ro.lmk.psi_complete_stall_ms:700(valore tipico)ro.lmk.kill_heaviest_task:true
Verifica di ZRAM e swap
La capacità di ZRAM influisce notevolmente sulla velocità di riempimento dello swap e sull'attivazione delle terminazioni di LMKD. Puoi controllare lo stato di ZRAM e swap sul dispositivo utilizzando questi comandi:
# Check ZRAM sizing and usage
adb shell free -m
# Check swap partition details
adb shell cat /proc/swaps
Esercitazione pratica: attivazione di kswapd e lmkd
In questo esercizio utilizzerai l'app di esempio KswapdLmkdLab per creare
processi in diversi bucket OOM e poi consumare memoria nel processo principale
per attivare kswapd e lmkd.
L'app è configurata per generare automaticamente sottoprocessi e allocare memoria per renderli target interessanti per LMKD.
1. Crea sottoprocessi
All'avvio, l'app genera automaticamente i seguenti processi secondari:
- Processo memorizzato nella cache (oom_score_adj >= 900)
- Processo di servizio (oom_score_adj 500)
- Processo di servizio in primo piano (oom_score_adj 200)
Verifica i processi e i relativi punteggi OOM utilizzando ADB:
adb shell dumpsys activity processes | grep com.android.kswapdlmkdlab
Snippet di output di esempio:
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)
Le abbreviazioni dello stato del processo corrispondono a:
fg: primo piano (punteggio OOM 0)prcp: Perceptible (servizio in primo piano, punteggio OOM 200)svc: servizio avviato (punteggio OOM 500)cch: processo memorizzato nella cache (punteggio OOM 900)
2. Attiva la pressione della memoria
L'app inizierà automaticamente ad allocare memoria nel processo principale. Per rendere il test efficace sui dispositivi con RAM elevata, l'app ordina anche ai sottoprocessi di allocare memoria (ad esempio 500 MB ciascuno) per renderli target migliori per LMKD.
Monitora l'interruzione del sottoprocesso
L'app monitora la terminazione di questi sottoprocessi utilizzando il meccanismo IBinder.linkToDeath:
- Il processo principale si associa a un servizio nel processo di destinazione utilizzando
bindService. - Al momento della connessione, ottiene l'oggetto
IBinderche rappresenta il servizio. - Registra un'implementazione di
IBinder.DeathRecipientchiamandolinkToDeathsul binder. - Quando il processo di destinazione termina (ad esempio, quando viene terminato da LMKD), il sistema chiama il metodo
binderDied(), consentendo all'app di registrare l'evento di terminazione in modo asincrono.
Per evitare che questa associazione aumenti la priorità dei processi di destinazione
(il che vanificherebbe lo scopo dell'esercizio), l'app utilizza il
flag Context.BIND_WAIVE_PRIORITY.
App di esempio in azione
Ecco uno screenshot dell'app dopo il completamento di un'esecuzione di test su un Google Pixel 8 Pro (che ha circa 12 GB di RAM totale).
Analisi della traccia di Perfetto
Per acquisire l'interazione, utilizza la seguente configurazione di Perfetto per acquisire la pianificazione, i contatori di memoria, i kill di LMK e le informazioni sulla pressione (PSI).
Configurazione di Perfetto
Acquisisci l'interazione eseguendo Perfetto con pianificazione, contatori di memoria, terminazioni LMK e informazioni sulla pressione di stallo (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
Esamina la traccia
Lo screenshot seguente è tratto da una traccia che mostra il periodo di tempo che intercorre tra poco prima dell'interruzione del processo :cached (più terminabile) e l'interruzione del processo :foreground (meno terminabile).
Puoi vedere che MemFree è già piuttosto basso ed è sotto pressione costante.
kswapd è costantemente in funzione e riduce la pressione eliminando mem.rss.file
pagine e spostando mem.rss.anon in mem.swap da tutti i kswapdlmkdlab
processi (il processo principale e i sottoprocessi) e probabilmente da altri
processi non mostrati in questo screenshot. Allo stesso tempo, l'app kswapdlmkdlab continua ad allocare memoria, aumentando la pressione più velocemente di quanto kswapd possa recuperare.
Quando la pressione diventa troppo elevata, come si vede quando psi.mem è alto per un periodo di tempo sufficiente, lmkd si attiva e termina alcuni processi. Puoi vedere che lmkd rispetta i punteggi OOM, terminando prima kswapdlmkdlab:cached, poi :service e infine :foreground.
Esamina la sequenza temporale degli eventi e rifletti su quanto segue:
- Come
kswapdelmkdsi completano a vicenda:kswapdè il primo a rispondere alla pressione della memoria, malmkdaiuta quando la larghezza di banda di recupero dikswapdnon soddisfa la domanda di allocazione. - La frequenza di liberazione della memoria: osserva il tempo di CPU impiegato in
kswapde la memoria recuperata eliminandomem.rss.fileo scambiandomem.rss.anonconmem.swap. Confronta questo dato con le brevi ondate di attività inlmkdche comportano il rilascio di grandi quantità di memoria privata del processo.
Query
Esegui queste query nell'interfaccia utente di Perfetto (Query SQL) per estrarre i dati dalla traccia.
1. Attività kswapd
Per scoprire quando il daemon di swap del kernel era attivo e in esecuzione, esegui:
SELECT ts, dur, state
FROM thread_state
JOIN thread USING (utid)
WHERE thread.name LIKE 'kswapd%'
AND state = 'Running'
LIMIT 5;
Esempio di output:
| ts | dur | stato |
|---|---|---|
| 30317633052969 | 159180 | In esecuzione |
| 30317633398347 | 3999186 | In esecuzione |
| 30317638448111 | 1847941 | In esecuzione |
| 30317641455354 | 1031006 | In esecuzione |
| 30317643272615 | 888387 | In esecuzione |
2. Kill LMKD
LMKD scrive le interruzioni per memoria insufficiente nella tabella instant. Per eseguire una query su tutti i kill di LMKD:
SELECT ts, name
FROM instant
WHERE name LIKE 'lmk,%'
ORDER BY ts ASC;
Il formato del nome dell'evento è:
lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>.
kill_reason: un numero intero che indica il motivo per cuilmkdha deciso di terminare il processo (definito insystem/memory/lmkd/statslog.h):0:PRESSURE_AFTER_KILL1:NOT_RESPONDING2:LOW_SWAP_AND_THRASHING(lo spazio di swap ZRAM è estremamente ridotto e si è verificato un thrashing)3:LOW_MEM_AND_SWAP(il sistema ha poca memoria libera e poco spazio di scambio 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: la percentuale massima di thrashing della pagina al momento dell'interruzione.
Esempio di output:
| ts | nome |
|---|---|
| 30326148679967 | lmk,2308,3,900,201,0 |
| 30330439763710 | lmk,2309,3,500,201,0 |
| 30334985999837 | lmk,2310,2,200,0,307 |
Visualizzazione
Per visualizzare i dati, puoi utilizzare il seguente script gnuplot per tracciare la memoria libera del sistema, l'attività kswapd, i kill lmkd e il PSI su una singola serie temporale. Lo script presuppone che venga eseguito dalla directory principale della build Android.
Esempio di grafico di output
Il grafico seguente è stato generato utilizzando lo script precedente con dati campionati rappresentativi dell'esecuzione del test.
Spiegazione della trama:
Questo grafico illustra l'interazione dinamica tra i diversi componenti di gestione della memoria durante lo stress test:
- Calo della memoria libera: man mano che l'app principale alloca memoria, la memoria libera del sistema (linea viola) diminuisce costantemente.
- Attivazione di kswapd: quando la memoria libera scende al di sotto di determinati watermark,
kswapdsi attiva (impulsi blu) per recuperare memoria eseguendo lo swap su ZRAM. Questo è visibile come picchi intermittenti. - Picchi di PSI: poiché
kswapdfatica a tenere il passo con l'elevata frequenza di allocazione, i thread iniziano a bloccarsi sull'allocazione della memoria, causando un picco di PSI (Pressure Stall Information) (linea verde). - Interruzioni di LMKD: quando il PSI raggiunge livelli critici (ad esempio, superando concettualmente la soglia di
ro.lmk.psi_complete_stall_ms) e la memoria libera è estremamente bassa,lmkdinterviene interrompendo i processi con priorità bassa (impulsi arancioni al minuto 30 e 45) per liberare grandi blocchi di memoria. Noterai che, subito dopo un kill, la memoria libera aumenta e il PSI diminuisce, a dimostrazione dell'efficacia del kill nell'alleviare la pressione. ________________________________________________________________________________
← Recupero della memoria: espulsione e swap | ↑ Su | Restituisci le pagine di memoria al sistema operativo →