Interaktion zwischen kswapd und lmkd

Auf dieser Seite wird die Interaktion zwischen kswapd und lmkd unter Android beschrieben und gezeigt, wie sie zusammenarbeiten, um die Speicherauslastung zu verwalten.

Hintergrund

Android verwendet zwei primäre Mechanismen, um Arbeitsspeicher freizugeben, wenn der Speicher knapp wird:

  1. kswapd: Ein Standard-Linux-Kernel-Daemon, der Seiten freigibt, indem er saubere dateibasierte Seiten entfernt oder anonyme Seiten in ZRAM auslagert.
  2. lmkd: Der Low Memory Killer-Daemon im Android-Nutzerbereich. Wenn kswapd die Speicheranforderungen nicht mehr erfüllen kann und die Systemauslastung steigt (wie durch PSI signalisiert), greift lmkd ein, um Prozesse zu beenden und größere Speicherbereiche freizugeben.

kswapd und lmkd sind zwei sich ergänzende Systeme zur Speicherfreigabe. Sie arbeiten auf dasselbe Ziel hin (freien Speicherplatz schaffen), aber mit unterschiedlichen Mitteln. Vergleich:

  • Wiederherstellbar im Vergleich zu destruktiv: kswapd verwirft Speicherseiten, die aus dem Speicher zurückgelesen werden können, oder tauscht Speicherseiten mit anderen Speicherpartitionen oder Speichern aus, die bei Bedarf zurückgelesen werden können. lmkd beendet Prozesse, wodurch der nichtflüchtige Zustand zerstört wird.
  • Bandbreite zurückgewinnen: kswapd benötigt mehr CPU-Leistung als lmkd, um eine bestimmte Menge an verfügbarem Arbeitsspeicher zurückzugewinnen, und es dauert länger, diesen Arbeitsspeicher zurückzugewinnen. lmkd kann große Mengen an Arbeitsspeicher in kurzen Intervallen mit relativ geringem Aufwand freigeben. Beachten Sie, dass sich die CPU-Leistung auf Mobilgeräten nicht nur auf die Latenz, sondern auch auf die Akkuleistung auswirkt.

OOM-Score-Buckets

Android weist Prozessen basierend auf ihrer Bedeutung für den Nutzer eine OOM-Anpassung (Out-Of-Memory) (oom_score_adj) zu. Prozesse mit höheren Werten werden zuerst beendet.

Häufige Buckets sind:

  • Im Cache gespeichert (Wert >= 900): Prozesse, die derzeit nicht benötigt werden und für einen schnelleren Neustart aufbewahrt werden.
  • Dienst (Wert 500): Prozesse, die gestartete Dienste hosten.
  • Dienst im Vordergrund (Wert 200): Prozesse, die Dienste im Vordergrund hosten.
  • Vordergrund (Wert 0): Die App, mit der der Nutzer gerade interagiert.

Im Detail: PSI- und LMKD-Triggering

Was ist PSI?

Pressure Stall Information (PSI) ist ein Linux-Kernel-Feature, das eine kanonische Methode zum Messen von Ressourcenengpässen bietet. Es wird die Zeit erfasst, um die Aufgaben aufgrund von CPU-, Speicher- und E/A-Engpässen verzögert werden.

Memory-PSI: „some“ im Vergleich zu „full“

Der PSI für den Arbeitsspeicher erfasst insbesondere Verzögerungen, die durch Arbeitsspeichermangel verursacht werden (z.B. Warten auf die Freigabe von Seiten oder Seitenfehler). Es werden zwei Messwerte bereitgestellt:

  • some: Der Prozentsatz der Zeit, in der mindestens eine Aufgabe aufgrund von Arbeitsspeichermangel blockiert war. Dies weist darauf hin, dass sich der Speichermangel auf die Leistung auswirkt, aber einige Aufgaben noch ausgeführt werden.
  • full: Der Prozentsatz der Zeit, in der alle nicht inaktiven Aufgaben gleichzeitig blockiert waren. Dies weist auf einen kritischen Zustand hin, in dem das System vollständig blockiert ist und auf Speicher wartet.

Wie PSI LMKD auslöst

Der Userspace-Daemon lmkd registriert sich beim Kernel, um Benachrichtigungen zu erhalten, wenn PSI-Schwellenwerte überschritten werden (durch Überwachung von /proc/pressure/memory). Wenn das System unter hohem Speicherdruck steht (z.B. full-Stall, der eine konfigurierte Dauer überschreitet), weckt der Kernel lmkd auf. lmkd wertet dann den Status des Systems aus und entscheidet anhand der OOM-Werte und der Größe, ob und welche Prozesse beendet werden sollen.

LMKD-Abstimmung

Das LMKD-Verhalten kann mithilfe von Systemeigenschaften angepasst werden.

Abstimmungswerte auf einem Gerät finden

Die aktuellen LMKD-Abstimmungswerte auf einem verbundenen Gerät können Sie mit folgendem Befehl abrufen:

adb shell getprop | grep lmk

Allgemeine LMKD-Eigenschaften

Hier sind einige der wichtigsten Eigenschaften, die zum Optimieren von LMKD verwendet werden. Eine vollständige Liste finden Sie in der Android-Dokumentation:

  • ro.lmk.use_psi: Auf true setzen, um PSI-Monitore für die Erkennung von Speicherdruck zu verwenden (Standardeinstellung unter Android 10 und höher).
  • ro.lmk.psi_partial_stall_ms: Der Schwellenwert für die Dauer eines (teilweisen) some-Stalls in Millisekunden, um einen Kill auszulösen (Standard: 70 ms auf normalen Geräten, 200 ms auf Geräten mit wenig RAM).
  • ro.lmk.psi_complete_stall_ms: Der Schwellenwert für die Dauer des full-Stalls in Millisekunden, um einen Kill auszulösen (Standardwert: 700 ms).
  • ro.lmk.kill_heaviest_task: Wenn true, beendet LMKD den Prozess, der den meisten Arbeitsspeicher unter den Kandidaten im selben OOM-Bucket verwendet.

Relevante Abstimmungswerte auf dem Testgerät (Google Pixel 8 Pro)

(Hinweis: Typische Standardwerte für High-End-Geräte mit Android 15)

  • ro.lmk.use_psi: true
  • ro.lmk.psi_partial_stall_ms: 70 (typisch)
  • ro.lmk.psi_complete_stall_ms: 700 (typisch)
  • ro.lmk.kill_heaviest_task: true

ZRAM- und Swap-Überprüfung

Die ZRAM-Kapazität hat einen großen Einfluss darauf, wie schnell der Swap-Speicher voll wird und LMKD-Kills auslöst. Mit den folgenden Befehlen können Sie den ZRAM- und Swap-Status auf dem Gerät prüfen:

# Check ZRAM sizing and usage
adb shell free -m

# Check swap partition details
adb shell cat /proc/swaps

Praktische Übung: kswapd und lmkd auslösen

In dieser Übung verwenden Sie die Beispiel-App KswapdLmkdLab, um Prozesse in verschiedenen OOM-Buckets zu erstellen und dann im Hauptprozess Arbeitsspeicher zu belegen, um kswapd und lmkd auszulösen.

Die App ist so konfiguriert, dass sie automatisch Unterprozesse erstellt und Arbeitsspeicher zuweist, um sie zu attraktiven Zielen für LMKD zu machen.

1. Unterprozesse erstellen

Die App startet beim Start automatisch die folgenden Unterprozesse:

  • Im Cache gespeicherter Prozess (oom_score_adj >= 900)
  • Dienstprozess (oom_score_adj 500)
  • Dienst im Vordergrund (oom_score_adj 200)

Prüfen Sie die Prozesse und ihre OOM-Scores mit ADB:

adb shell dumpsys activity processes | grep com.android.kswapdlmkdlab

Beispiel für ein Ausgabe-Snippet:

    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)

Die Abkürzungen für den Prozessstatus entsprechen Folgendem:

  • fg: Vordergrund (OOM-Wert 0)
  • prcp: Wahrnehmbar (Dienst im Vordergrund, OOM-Wert 200)
  • svc: Dienst gestartet (OOM-Wert 500)
  • cch: Im Cache gespeicherter Prozess (OOM-Wert 900)

2. Speicherauslastung auslösen

Die App beginnt automatisch damit, Arbeitsspeicher im Hauptprozess zuzuweisen. Damit der Test auf Geräten mit viel RAM effektiv ist, weist die App die Unterprozesse außerdem an, Arbeitsspeicher zuzuweisen (z.B. jeweils 500 MB), damit sie bessere Ziele für den LMKD sind.

Beendigung von Unterprozessen überwachen

Die App überwacht das Beenden dieser Unterprozesse mithilfe des IBinder.linkToDeath-Mechanismus:

  1. Der Hauptprozess wird mit bindService an einen Dienst im Zielprozess gebunden.
  2. Nach dem Herstellen der Verbindung wird das IBinder-Objekt abgerufen, das den Dienst darstellt.
  3. Sie registriert eine IBinder.DeathRecipient-Implementierung, indem sie linkToDeath auf dem Binder aufruft.
  4. Wenn der Zielprozess beendet wird (z. B. durch LMKD), ruft das System die Methode binderDied() auf, sodass die App das Beendigungsereignis asynchron aufzeichnen kann.

Damit diese Bindung die Priorität der Zielprozesse nicht erhöht (was den Zweck der Übung zunichtemachen würde), verwendet die App das Flag Context.BIND_WAIVE_PRIORITY.

Beispiel-App in Aktion

Hier ist ein Screenshot der App nach einem Testlauf auf einem Google Pixel 8 Pro (mit insgesamt etwa 12 GB RAM).

Screenshot der Beispiel-App nach einem abgeschlossenen Testlauf.
Abbildung 1. Beispiel-App nach einem abgeschlossenen Testlauf auf einem Pixel 8 Pro.

Perfetto-Trace-Analyse

Verwenden Sie die folgende Perfetto-Konfiguration, um die Interaktion zu erfassen, einschließlich Planung, Speicherzähler, LMK-Kills und PSI (Pressure Stall Information).

Perfetto-Konfiguration

Erfasse die Interaktion, indem du Perfetto mit Planung, Speicherzählern, LMK-Kills und PSI (Pressure Stall Information) ausführst:

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

Trace prüfen

Der folgende Screenshot stammt aus einem Trace, der den Zeitraum kurz vor dem Beenden des Prozesses :cached (am ehesten beendbar) bis zum Beenden des Prozesses :foreground (am wenigsten beendbar) zeigt.

Screenshot der Perfetto-Benutzeroberfläche mit kswapd- und lmkd-Aktivität.
Abbildung 2. Perfetto-Trace mit kswapd-Freigabe und lmkd-Beendigungen.

Sie sehen, dass MemFree bereits ziemlich niedrig ist und ständig unter Druck steht. kswapd arbeitet ständig und entlastet den Druck, indem es mem.rss.file Seiten löscht und mem.rss.anon bis mem.swap aus allen kswapdlmkdlab-Prozessen (dem Hauptprozess und den Unterprozessen) und wahrscheinlich aus anderen Prozessen, die in diesem Screenshot nicht zu sehen sind, verschiebt. Gleichzeitig weist die kswapdlmkdlab-Anwendung weiterhin Arbeitsspeicher zu, wodurch der Druck schneller steigt, als kswapd ihn freigeben kann.

Wenn der Druck zu hoch wird, was sich daran zeigt, dass psi.mem über einen längeren Zeitraum hoch ist, wird lmkd aktiviert und beendet einige Prozesse. Sie können sehen, dass lmkd die OOM-Werte berücksichtigt und zuerst kswapdlmkdlab:cached, dann :service und dann :foreground beendet.

Sehen Sie sich die Zeitleiste der Ereignisse an und denken Sie über Folgendes nach:

  • So ergänzen sich kswapd und lmkd: kswapd reagiert zuerst auf Speicherdruck, aber lmkd hilft, wenn die Bandbreite für die Rückgewinnung von kswapd nicht den Zuweisungsbedarf deckt.
  • Die Rate, mit der Arbeitsspeicher freigegeben wird: Beobachten Sie die CPU-Zeit, die in kswapd verbracht wird, und den Arbeitsspeicher, der durch das Löschen von mem.rss.file oder das Auslagern von mem.rss.anon in mem.swap freigegeben wird. Vergleichen Sie dies mit den kurzen Aktivitätsphasen in lmkd, die dazu führen, dass große Mengen an privatem Prozessspeicher freigegeben werden.

Abfragen

Führen Sie diese Abfragen in der Perfetto-Benutzeroberfläche (Query SQL) aus, um Daten aus Ihrem Trace zu extrahieren.

1. kswapd-Aktivität

Führen Sie folgenden Befehl aus, um herauszufinden, wann der Swap-Daemon des Kernels aktiv war und ausgeführt wurde:

SELECT ts, dur, state
FROM thread_state
JOIN thread USING (utid)
WHERE thread.name LIKE 'kswapd%'
  AND state = 'Running'
LIMIT 5;

Beispielausgabe:

ts dur state
30317633052969 159180 Wird ausgeführt
30317633398347 3999186 Wird ausgeführt
30317638448111 1847941 Wird ausgeführt
30317641455354 1031006 Wird ausgeführt
30317643272615 888387 Wird ausgeführt

2. LMKD-Beendigungen

LMKD schreibt Low-Memory-Kills in die Tabelle instant. So fragen Sie alle LMKD-Beendigungen ab:

SELECT ts, name
FROM instant
WHERE name LIKE 'lmk,%'
ORDER BY ts ASC;

Das Format des Ereignisnamens ist: lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>.

  • kill_reason: Eine Ganzzahl, die angibt, warum lmkd den Prozess beendet hat (definiert in system/memory/lmkd/statslog.h):
    • 0: PRESSURE_AFTER_KILL
    • 1: NOT_RESPONDING
    • 2: LOW_SWAP_AND_THRASHING (ZRAM-Swap-Speicher ist extrem niedrig und es ist zu Thrashing gekommen)
    • 3: LOW_MEM_AND_SWAP (Das System hat wenig verfügbaren Arbeitsspeicher und ZRAM-Swap)
    • 4: LOW_MEM_AND_THRASHING
    • 5: DIRECT_RECL_AND_THRASHING
    • 6: LOW_MEM_AND_SWAP_UTIL
    • 7: LOW_FILECACHE_AFTER_THRASHING
    • 8: LOW_MEM
    • 9: DIRECT_RECL_STUCK
  • max_thrashing: Der maximale Prozentsatz des Seiten-Thrashings zum Zeitpunkt des Beendens.

Beispielausgabe:

ts Name
30326148679967 lmk,2308,3,900,201,0
30330439763710 lmk,2309,3,500,201,0
30334985999837 lmk,2310,2,200,0,307

Visualisierung

Zur Visualisierung der Daten können Sie das folgende gnuplot-Skript verwenden, um den verfügbaren Systemspeicher, die kswapd-Aktivität, die lmkd-Kills und den PSI in einer einzigen Zeitreihe darzustellen. Das Script geht davon aus, dass es im obersten Verzeichnis des Android-Builds ausgeführt wird.

Beispiel für ein Ausgabediagramm

Das folgende Diagramm wurde mit dem vorherigen Script und Beispieldaten generiert, die für den Testlauf repräsentativ sind.

Diagramm des verfügbaren Arbeitsspeichers, der kswapd-Aktivität, des PSI und der lmkd-Kills.
Abbildung 3. Diagramm zur Arbeitsspeicherauslastung mit verfügbarem Arbeitsspeicher, kswapd, PSI und lmkd-Kills.

Erläuterung des Diagramms:

Dieses Diagramm veranschaulicht die dynamische Interaktion zwischen den verschiedenen Komponenten der Speicherverwaltung während des Stresstests:

  1. Abfall des verfügbaren Arbeitsspeichers: Da die Haupt-App Arbeitsspeicher zuweist, nimmt der verfügbare Arbeitsspeicher des Systems (lila Linie) kontinuierlich ab.
  2. kswapd-Aktivierung: Wenn der verfügbare Arbeitsspeicher unter bestimmte Grenzwerte fällt, wird kswapd aktiviert (blaue Impulse), um Arbeitsspeicher durch Auslagerung in ZRAM zurückzugewinnen. Dies ist an den unregelmäßigen Spitzen zu erkennen.
  3. PSI-Spitzen: Da kswapd mit der hohen Zuweisungsrate nicht Schritt halten kann, werden Threads bei der Arbeitsspeicherzuweisung blockiert, was zu Spitzen bei den PSI (Pressure Stall Information) (grüne Linie) führt.
  4. LMKD-Beendigungen: Wenn der PSI kritische Werte erreicht (z.B. den Schwellenwert ro.lmk.psi_complete_stall_ms überschreitet) und der verfügbare Arbeitsspeicher extrem niedrig ist, greift lmkd ein und beendet Prozesse mit niedriger Priorität (orangefarbene Impulse bei 30 und 45), um große Mengen an Arbeitsspeicher wieder verfügbar zu machen. Unmittelbar nach dem Beenden steigt der verfügbare Arbeitsspeicher und der PSI sinkt, was die Wirksamkeit des Beendens bei der Druckentlastung zeigt. ________________________________________________________________________________

← Speicherfreigabe: Entfernung und Auslagerung | ↑ Nach oben | Speicherseiten an das Betriebssystem zurückgeben →