בדף הזה מוסבר על האינטראקציה בין kswapd ל-lmkd ב-Android, ואיך הם פועלים יחד כדי לנהל את העומס על הזיכרון.
רקע
ב-Android יש שני מנגנונים עיקריים לשחזור זיכרון כשהמערכת מתחילה להתמלא:
-
kswapd: דימון סטנדרטי של ליבת Linux שמחזיר דפים על ידי פינוי דפים נקיים שמגובים בקבצים או החלפת דפים אנונימיים ל-ZRAM. -
lmkd: תהליך הרקע Low Memory Killer (מחסל זיכרון נמוך) במרחב המשתמש של Android. כאשרkswapdלא מצליח לעמוד בדרישות הזיכרון והלחץ על המערכת גובר (כפי שמצוין על ידי PSI),lmkdמתערב כדי להרוג תהליכים ולפנות נתחים גדולים יותר של זיכרון.
kswapd ו-lmkd הן שתי מערכות משלימות להחזרת זיכרון. הם פועלים כדי להשיג את אותה מטרה (לפנות זיכרון), אבל בדרכים שונות. כדי להשוות
ולמצוא הבדלים:
- ניתן לשחזור לעומת הרסני:
kswapdמשחרר דפי זיכרון שאפשר לקרוא בחזרה מהאחסון, או מחליף דפי זיכרון למחיצות זיכרון אחרות או לאחסון שאפשר לקרוא בחזרה לפי דרישה. הפקודהlmkdסוגרת תהליכים, מה שגורם לאובדן של מצב לא נדיף. - החזרת רוחב פס:
kswapdמשתמש ביותר משאבי מעבד (CPU) כדי להחזיר כמות נתונה של זיכרון פנוי בהשוואה ל-lmkd, ולוקח לו יותר זמן להחזיר את הזיכרון הזה. lmkdיכול לפנות כמויות גדולות של זיכרון בפרקי זמן קצרים ובמאמץ קטן יחסית. חשוב לזכור שבמכשירים ניידים, מאמץ של המעבד משפיע לא רק על זמן האחזור, אלא גם על צריכת הסוללה.
קטגוריות של ניקוד OOM
מערכת Android מקצה התאמה של ניקוד OOM (oom_score_adj) לתהליכים על סמך החשיבות שלהם למשתמש. תהליכים עם ציונים גבוהים יותר נסגרים קודם.
דוגמאות לקטגוריות נפוצות:
- שמורים במטמון (ציון >= 900): תהליכים שלא נדרשים כרגע, נשמרים כדי להפעיל מחדש מהר יותר.
- שירות (ציון 500): תהליכים שמארחים שירותים שהופעלו.
- שירות שפועל בחזית (ציון 200): תהליכים שמארחים שירותים שפועלים בחזית.
- חזית (ציון 0): האפליקציה שהמשתמש נמצא איתה באינטראקציה כרגע.
מידע מעמיק: הפעלת PSI ו-LMKD
מה זה PSI?
Pressure Stall Information (PSI) הוא תכונה של ליבת Linux שמספקת דרך קנונית למדוד מחסור במשאבים. הוא עוקב אחרי הזמן שבו משימות מתעכבות בגלל מחסור במעבד (CPU), בזיכרון ובקלט/פלט (I/O).
Memory PSI: "some" לעומת "full"
Memory PSI עוקב באופן ספציפי אחרי עיכובים שנגרמים בגלל מחסור בזיכרון (למשל, המתנה להחזרת דף או לשגיאות חוזרות). הוא כולל שני מדדים:
-
some: אחוז הזמן שבו לפחות משימה אחת נתקעה בגלל הזיכרון. זה מצביע על כך שמחסור בזיכרון משפיע על הביצועים, אבל חלק מהעבודה עדיין מתבצעת. -
full: אחוז הזמן שבו כל המשימות הפעילות נתקעו בו-זמנית. הסטטוס הזה מציין מצב קריטי שבו המערכת חסומה לחלוטין וממתינה לזיכרון.
איך PSI מפעיל את LMKD
הדמון (daemon) של 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 אלפיות השנייה במכשירים רגילים, ו-200 אלפיות השנייה במכשירים עם זיכרון RAM נמוך). 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 משפיעה מאוד על המהירות שבה ה-swap מתמלא ומפעיל את סגירת התהליכים של 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)
מאמתים את התהליכים ואת ציוני ה-OOM שלהם באמצעות ADB:
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 גדול, האפליקציה גם מורה לתהליכי המשנה להקצות זיכרון (למשל, 500MB לכל אחד) כדי להפוך אותם ליעדים טובים יותר ל-LMKD.
מעקב אחרי סיום תהליך משנה
האפליקציה עוקבת אחרי הסגירה של תהליכי המשנה האלה באמצעות המנגנון IBinder.linkToDeath:
- התהליך הראשי נקשר לשירות בתהליך היעד באמצעות
bindService. - אחרי החיבור, הוא מקבל את האובייקט
IBinderשמייצג את השירות. - הוא רושם הטמעה של
IBinder.DeathRecipientעל ידי קריאה ל-linkToDeathב-binder. - כשהתהליך של היעד מסתיים (לדוגמה, כשהוא נסגר על ידי LMKD), המערכת קוראת ל-method
binderDied(), וכך האפליקציה יכולה לתעד את אירוע הסיום באופן אסינכרוני.
כדי למנוע מהקישור הזה להעלות את העדיפות של תהליכי היעד (מה שיפגע במטרת התרגיל), האפליקציה משתמשת בדגל Context.BIND_WAIVE_PRIORITY.
אפליקציה לדוגמה בפעולה
הנה צילום מסך של האפליקציה אחרי הרצת בדיקה ב-Google Pixel 8 Pro (עם זיכרון RAM כולל של כ-12GB).
ניתוח נתוני מעקב של 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 (הכי חשוב) נסגר.
אפשר לראות שהערך של MemFree כבר נמוך למדי ונמצא תחת לחץ מתמיד.
kswapd פועל כל הזמן ומפחית את הלחץ על ידי הסרת mem.rss.file דפים והעברת mem.rss.anon אל mem.swap מכל התהליכים של kswapdlmkdlab (התהליך הראשי ותהליכי המשנה), וכנראה גם מתהליכים אחרים שלא מוצגים בצילום המסך הזה. במקביל, האפליקציה kswapdlmkdlab ממשיכה להקצות זיכרון, ומגבירה את הלחץ מהר יותר מהקצב שבו kswapd יכולה להחזיר את הזיכרון.
כשהלחץ גבוה מדי, כמו במצב שבו psi.mem גבוה למשך זמן מספיק, lmkd מתעורר והורג כמה תהליכים. אפשר לראות ש-lmkd פועל לפי ציוני ה-OOM, ומפסיק קודם את kswapdlmkdlab:cached, אחר כך את :service ואז את :foreground.
בודקים את ציר הזמן של האירועים וחושבים על הדברים הבאים:
- איך
kswapdו-lmkdמשלימים זה את זה:kswapdהוא הראשון שמגיב ללחץ על הזיכרון, אבלlmkdעוזר כשרוחב הפס להחזרה שלkswapdלא עומד בדרישת ההקצאה. - קצב פינוי הזיכרון: בודקים את זמן המעבד (CPU) שמושקע ב-
kswapdואת הזיכרון שמתפנה על ידי השמטתmem.rss.fileאו החלפתmem.rss.anonב-mem.swap. אפשר להשוות את זה לפעילות הקצרה והאינטנסיבית ב-lmkd, שגורמת לשחרור כמויות גדולות של זיכרון פרטי של תהליכים.
שאילתות
כדי לחלץ נתונים מהמעקב, מריצים את השאילתות האלה בממשק המשתמש של Perfetto (שאילתת SQL).
1. פעילות kswapd
כדי לראות מתי ה-swap 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 כותב אירועי LMK בטבלה 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_KILL1:NOT_RESPONDING-
2:LOW_SWAP_AND_THRASHING(שטח ההחלפה של ZRAM נמוך מאוד והתרחשה תופעת Thrashing) -
3:LOW_MEM_AND_SWAP(במערכת יש מעט זיכרון פנוי וגם מעט זיכרון החלפה ב-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: האחוז המקסימלי של דפים שמועברים בין הזיכרון לדיסק בזמן הסגירה.
פלט לדוגמה:
| 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 בסדרת זמן אחת. הסקריפט מניח שהוא מופעל מהספרייה העליונה של ה-build של Android.
תרשים פלט לדוגמה
התרשים הבא נוצר באמצעות הסקריפט הקודם עם נתונים לדוגמה שמייצגים את הרצת הבדיקה.
הסבר על התרשים:
התרשים הזה ממחיש את האינטראקציה הדינמית בין הרכיבים השונים של ניהול הזיכרון במהלך בדיקת העומס:
- ירידה בזיכרון הפנוי: ככל שהאפליקציה הראשית מקצה זיכרון, הזיכרון הפנוי במערכת (הקו הסגול) יורד בהדרגה.
- הפעלה של kswapd: כשהזיכרון הפנוי יורד מתחת לסימני מים מסוימים,
kswapdמתעורר (פולסים כחולים) כדי לפנות זיכרון על ידי החלפה ל-ZRAM. התופעה הזו נראית כמו קפיצות לסירוגין. - קפיצות ב-PSI: כשהתהליך
kswapdמתקשה לעמוד בקצב הגבוה של ההקצאה, השרשורים מתחילים להיתקע בהקצאת הזיכרון, מה שגורם לקפיצות ב-PSI (מידע על עיכובים בגלל עומס) (הקו הירוק). - סגירת תהליכים על ידי LMKD: כשמדד ה-PSI מגיע לרמות קריטיות (למשל, חריגה מהסף של
ro.lmk.psi_complete_stall_ms) והזיכרון הפנוי נמוך מאוד,lmkdמתערב וסוגר תהליכים בעדיפות נמוכה (הקווים הכתומים בזמנים 30 ו-45) כדי לפנות חלקים גדולים מהזיכרון. שימו לב שמיד אחרי סגירת תהליך, הזיכרון הפנוי עולה וה-PSI יורד, מה שמראה שהסגירה יעילה בהפחתת הלחץ. ________________________________________________________________________________
← החזרת זיכרון: פינוי והחלפה | ↑ למעלה | החזרת דפי זיכרון למערכת ההפעלה →