kswapd 和 lmkd 互動

本頁面說明 Android 上 kswapd 和 lmkd 之間的互動,並示範兩者如何搭配運作,管理記憶體壓力。

背景

當系統記憶體不足時,Android 會使用兩種主要機制回收記憶體:

  1. kswapd:標準 Linux 核心 Daemon,可透過清除乾淨的檔案支援頁面,或將匿名頁面交換至 ZRAM 來回收頁面。
  2. lmkd:Android 的使用者空間記憶體不足終止工具 Daemon。當 kswapd 無法滿足記憶體需求,且系統壓力增加 (如 PSI 所示) 時,lmkd 會介入終止程序,釋出較大的記憶體區塊。

kswapd 和 lmkd 是兩個互補的記憶體回收系統。兩者都致力於達成相同目標 (建立可用記憶體),但方法不同。比較與對照:

  • 可復原與破壞性:kswapd 會捨棄可從儲存空間讀回的記憶體頁面,或將記憶體頁面交換至其他記憶體分區或儲存空間,以便視需要讀回。lmkd 會終止程序,進而破壞非揮發性狀態。
  • 回收頻寬:kswapd 會比 lmkd 耗用更多 CPU 來回收指定數量的可用記憶體,且回收該記憶體所需的時間也較長。lmkd 可以在短時間內回收大量記憶體,且相對不費力。請注意,在行動裝置上,CPU 效能不僅會影響延遲時間,也會影響電池電力。

OOM 分數值區

Android 會根據程序對使用者的重要性,為程序指派記憶體不足 (OOM) 分數調整值 (oom_score_adj)。系統會優先終止分數較高的程序。

常見的 bucket 包括:

  • 快取 (分數 >= 900):目前不需要的程序,保留這些程序可加快重新啟動速度。
  • 服務 (分數 500):託管已啟動服務的程序。
  • 前景服務 (分數 200):代管前景服務的程序。
  • 前景 (分數 0):使用者目前互動的應用程式。

深入探討:PSI 和 LMKD 觸發

什麼是 PSI?

壓力停滯資訊 (PSI) 是 Linux 核心功能,可提供標準方法來測量資源短缺情況。這項指標會追蹤因 CPU、記憶體和 I/O 不足而延遲的工作時間。

記憶體 PSI:「some」與「full」

記憶體 PSI 專門追蹤記憶體不足造成的延遲 (例如等待頁面回收或重新錯誤)。這項工具提供兩項指標:

  • some:至少有一項工作因記憶體而停滯的時間百分比。這表示記憶體不足會影響效能,但部分工作仍在進行中。
  • full:所有非閒置工作同時停滯的時間百分比。這表示系統完全遭到封鎖,等待記憶體釋出,處於嚴重狀態。

PSI 如何觸發 LMKD

使用者空間 lmkd 守護程序會向核心註冊,以便在超過 PSI 門檻時接收通知 (透過監控 /proc/pressure/memory)。當系統遇到高記憶體壓力時 (例如 full 停滯超過設定的時間長度),核心會喚醒 lmkd。接著,lmkd 會評估系統狀態,並根據 OOM 分數和大小,決定是否終止程序以及要終止哪些程序。

LMKD 調整

LMKD 行為可透過系統屬性調整。

在裝置上尋找微調值

如要查看連結裝置目前的 LMKD 調整值,請執行以下指令:

adb shell getprop | grep lmk

常見 LMKD 屬性

以下是用於調整 LMKD 的一些重要屬性 (完整清單請參閱 Android 說明文件):

  • ro.lmk.use_psi:設為 true,即可使用 PSI 監控器偵測記憶體壓力 (Android 10 以上版本預設為啟用)。
  • ro.lmk.psi_partial_stall_ms:some (部分) 停滯時間長度 (以毫秒為單位) 的閾值,達到此值就會觸發終止 (一般裝置預設為 70 毫秒,低 RAM 裝置預設為 200 毫秒)。
  • ro.lmk.psi_complete_stall_ms:full 停滯時間長度 (以毫秒為單位) 的閾值,達到此值就會觸發終止 (預設為 700 毫秒)。
  • ro.lmk.kill_heaviest_task:如果為 true,LMKD 會終止相同 OOM 值區中候選項目中記憶體用量最多的程序。

測試裝置 (Google Pixel 8 Pro) 上的相關調整值

(注意:搭載 Android 15 的高階裝置一般預設值)

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

ZRAM 和交換驗證

ZRAM 容量會大幅影響交換空間填滿的速度,並觸發 LMKD 終止。您可以使用下列指令,檢查裝置上的 ZRAM 和交換狀態:

# Check ZRAM sizing and usage
adb shell free -m

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

實作練習:觸發 kswapd 和 lmkd

在本練習中,您將使用 KswapdLmkdLab 範例應用程式在不同的 OOM 值區中建立程序,然後在主要程序中耗用記憶體,觸發 kswapd 和 lmkd。

應用程式設定為自動產生子程序並分配記憶體,因此很容易成為 LMKD 的目標。

1. 建立子程序

應用程式啟動時會自動產生下列子程序:

  • 快取程序 (oom_score_adj >= 900)
  • 服務程序 (oom_score_adj 500)
  • 前景服務程序 (oom_score_adj 200)

使用 ADB 驗證程序及其 OOM 分數:

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

輸出內容程式碼片段範例:

    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)

程序狀態縮寫對應如下:

  • fg:前景 (OOM 分數 0)
  • prcp:可感知 (前景服務,OOM 分數 200)
  • svc:已啟動服務 (OOM 分數 500)
  • cch:快取程序 (OOM 分數 900)

2. 觸發記憶體壓力

應用程式會自動開始在主要程序中分配記憶體。為了在高 RAM 裝置上有效進行測試,應用程式也會命令子程序分配記憶體 (例如每個 500 MB),讓 LMKD 更容易鎖定這些子程序。

監控子程序終止

應用程式會使用 IBinder.linkToDeath 機制監控這些子程序是否終止:

  1. 主要程序會使用 bindService 繫結至目標程序中的服務。
  2. 連線後,系統會取得代表服務的 IBinder 物件。
  3. 它會透過在繫結器上呼叫 linkToDeath 來註冊 IBinder.DeathRecipient 實作。
  4. 當目標程序終止時 (例如由 LMKD 終止),系統會呼叫 binderDied() 方法,讓應用程式以非同步方式記錄終止事件。

為避免這個繫結提高目標程序的優先順序 (這會違背練習目的),應用程式會使用 Context.BIND_WAIVE_PRIORITY 旗標。

範例應用程式實際運作情形

以下是在 Google Pixel 8 Pro (總 RAM 約 12 GB) 上完成測試執行後的應用程式螢幕截圖。

測試執行完成後的範例應用程式螢幕截圖。
圖 1:在 Pixel 8 Pro 上完成測試執行的範例應用程式。

Perfetto 追蹤記錄分析

如要擷取互動,請使用下列 Perfetto 設定,擷取排程、記憶體計數器、LMK 終止和壓力停滯資訊 (PSI)。

Perfetto 設定

執行 Perfetto,並啟用排程、記憶體計數器、LMK 終止和壓力停滯資訊 (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

查看追蹤記錄

以下螢幕截圖取自追蹤記錄,顯示 :cached (最容易終止) 程序終止前不久,到 :foreground (最不容易終止) 程序終止的時間。

Perfetto UI 螢幕截圖,顯示 kswapd 和 lmkd 活動。
圖 2 Perfetto 追蹤記錄顯示 kswapd 回收和 lmkd 終止。

您可以看到 MemFree 已經相當低,且持續承受壓力。kswapd 會持續運作,並透過捨棄 mem.rss.file 頁面,以及將 mem.rss.anon 從所有 kswapdlmkdlab 程序 (主要程序和子程序) 移至 mem.swap 來減輕壓力,也可能從這張螢幕截圖中未顯示的其他程序移動。同時,kswapdlmkdlab 應用程式會繼續分配記憶體,增加壓力的速度比 kswapd 回收的速度更快。

當壓力過高時 (psi.mem 長時間處於高位),lmkd 就會喚醒並終止部分程序。您可以看到 lmkd 遵循 OOM 分數,先終止 kswapdlmkdlab:cached,然後是 :service,最後是 :foreground。

請查看事件時間軸,並思考下列問題:

  • kswapd 和 lmkd 如何互補:kswapd 是第一個回應記憶體壓力的機制,但當 kswapd 的回收頻寬無法滿足分配需求時,lmkd 會提供協助。
  • 記憶體釋放率:觀察 kswapd 耗用的 CPU 作業時間,以及透過捨棄 mem.rss.file 或將 mem.rss.anon 交換至 mem.swap 來回收的記憶體。相較之下,lmkd 中的短暫活動會釋出大量程序私人記憶體。

查詢

在 Perfetto UI (查詢 SQL) 中執行這些查詢,從追蹤記錄中擷取資料。

1. kswapd 活動

如要找出核心交換 Daemon 的啟用和執行時間,請執行:

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

輸出示例:

ts dur 州
30317633052969 159180 執行中
30317633398347 3999186 執行中
30317638448111 1847941 執行中
30317641455354 1031006 執行中
30317643272615 888387 執行中

2. LMKD 終止

LMKD 會將記憶體不足終止事件寫入 instant 資料表。如要查詢所有 LMKD 終止作業,請執行以下操作:

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

事件名稱格式為:lmk,<pid>,<kill_reason>,<oom_score_adj>,<min_oom_score>,<max_thrashing>。

  • kill_reason:整數,表示 lmkd 決定終止程序的原因 (定義於 system/memory/lmkd/statslog.h):
    • 0:PRESSURE_AFTER_KILL
    • 1:NOT_RESPONDING
    • 2:LOW_SWAP_AND_THRASHING (ZRAM 交換空間極低,且發生顛簸)
    • 3:LOW_MEM_AND_SWAP (系統的可用記憶體和 ZRAM 交換空間不足)
    • 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:終止時的頁面顛簸百分比上限。

輸出示例:

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

圖表

如要以圖表呈現資料,您可以使用下列 gnuplot 指令碼,在單一時間序列中繪製系統可用記憶體、kswapd 活動、lmkd 終止和 PSI。指令碼會假設它是從 Android 建構頂層目錄執行。

輸出圖表範例

下列圖表是使用上述指令碼和代表測試執行的樣本資料產生。

可用記憶體、kswapd 活動、PSI 和 lmkd 終止的圖表。
圖 3. 記憶體壓力圖,顯示可用記憶體、kswapd、PSI 和 lmkd 終止。

圖表說明:

這張圖表說明壓力測試期間,不同記憶體管理元件之間的動態互動:

  1. 可用記憶體減少:主要應用程式配置記憶體時,系統可用記憶體 (紫色線) 會穩定減少。
  2. kswapd 啟用:當可用記憶體低於特定水位時,kswapd 會喚醒 (藍色脈衝),透過交換至 ZRAM 來回收記憶體。這會顯示為間歇性尖峰。
  3. PSI 尖峰:由於 kswapd 無法跟上高配置率,執行緒開始在記憶體配置上停滯,導致 PSI (壓力停滯資訊) (綠線) 飆升。
  4. LMKD 終止:當 PSI 達到臨界值 (例如概念上超過 ro.lmk.psi_complete_stall_ms 門檻),且可用記憶體極低時,lmkd 會介入並終止低優先順序程序 (時間 30 和 45 的橘色脈衝),釋出大量記憶體。請注意,終止後,可用記憶體會立即上升,PSI 則會下降,顯示終止作業能有效減輕壓力。________________________________________________________________________________

← 記憶體回收:剔除和交換 | ↑ 向上 | 將記憶體頁面傳回 OS →