בקטעים הקודמים ראינו איך ליבת המערכת מנהלת את הזיכרון בכל המערכת. בפרק הזה נסביר לעומק על קבוצות של בקרת זיכרון (memcg), המנגנון שבו מערכת Android משתמשת כדי לחלק את הזיכרון ולשלוט בשימוש בזיכרון של אפליקציות ספציפיות.
חשוב להבין את memcg כי זו הרמה שבה המערכת יכולה לזהות אפליקציות ספציפיות כדי לשחרר זיכרון או להגדיר מגבלות זיכרון, וכך לנהל באופן יזום את הזיכרון שבשימוש באפליקציות.
קבוצות לבקרת זיכרון (memcg)
קבוצות בקרה (cgroups) הן תכונה של ליבת Linux שמאפשרת לארגן תהליכים לקבוצות היררכיות ולחלק ביניהן משאבי מערכת (כמו CPU, זיכרון וקלט/פלט). memcg הוא בקר cgroup שספציפי לזיכרון.
מושגים מרכזיים ב-memcg
- Memcg Charge: כשמעבד ב-memcg מקצה דף זיכרון (אנונימי או מגובה בקובץ), הדף הזה מחויב ב-memcg. החיוב הכולל של memcg הוא סכום כל הדפים שמשמשים את כל התהליכים בתוך memcg.
- הנהלת חשבונות היררכית: השימוש בזיכרון מחושב לפי העץ. חיוב בקבוצת צאצאים נספר גם בשימוש של קבוצת ההורים.
- מגבלות זיכרון: לכל memcg יכולות להיות מגבלות (כמו
memory.maxאוmemory.high) שמפעילות שחזור או אפילו את OOM killer אם הן חורגות מהן, ללא קשר לזיכרון המערכת הגלובלי. - Per-memcg Reclaim: כש-memcg חורג מהמגבלה שלו, או כשהמערכת צריכה זיכרון, ליבת המערכת יכולה לטרגט memcg ספציפי כדי לשחרר זיכרון. כלומר, צריך להוציא את דפי הקובץ שלו מהזיכרון או להחליף את הדפים האנונימיים שלו ב-ZRAM.
ההיררכיה של memcg ב-Android
מערכת Android משתמשת בהיררכיה ספציפית כדי לנהל תהליכים של אפליקציות. המבנה הזה מאפשר למערכת להחיל מדיניות שונה על סוגים שונים של אפליקציות (למשל, אפליקציות שפועלות בחזית לעומת אפליקציות שפועלות ברקע).

-
/sys/fs/cgroup/apps/: נתיב הבסיס לכל אפליקציות Android. -
uid_<UID>/: ספרייה לכל מזהה משתמש של אפליקציה. כל התהליכים ששייכים לאותה חבילת APK חולקים את הקבוצה הזו. -
pid_<PID>/: ספרייה לכל תהליך בודד ולכל תהליכי הבן שנוצרו ממנו. כך אפשר לשלוט בצורה פרטנית ולנהל חשבונות של אפליקציות עם כמה תהליכים.
תרגיל מעשי: בדיקת memcg
בתרגיל הזה תמצאו את ספריית memcg של MemoryLab ותעקבו אחרי השימוש בזיכרון בזמן אמת.
1. הפעלת MemoryLab
מוודאים שאפליקציית MemoryLab פועלת במכשיר.
2. חיפוש של memcg של MemoryLab
קודם כל, צריך לקבל את ה-PID של אפליקציית MemoryLab שפועלת:
adb shell pidof com.android.memorylab
# Example output: 11672
עכשיו מאתרים את ספריית ה-cgroup שלו. אפשר למצוא את זה ב-/proc/<PID>/cgroup:
adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672
ב-cgroup v2, הנתיב אחרי 0:: מייצג את ההיררכיה של memcg ביחס ל-/sys/fs/cgroup. הנתיב המלא הוא
/sys/fs/cgroup/apps/uid_10274/pid_11672/.
3. קריאת נתונים סטטיסטיים של memcg
נכנסים לספרייה הזו ומסתכלים על קובצי המפתח:
# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672
memory.current מציג את כמות הזיכרון הכוללת (בבייטים) שכרגע מחויבת לקבוצת הבקרה הזו.
# Detailed statistics
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.stat | head -n 10
# Example output:
# anon 1585098752
# file 741376
# kernel 5484544
# kernel_stack 393216
# pagetables 4583424
# sec_pagetables 0
# percpu 216
# sock 0
# vmalloc 4096
# shmem 20480
memory.stat מספק פירוט: * anon: נפח הזיכרון האנונימי (ערימות, מחסניות). * file: כמות הזיכרון שמוגדרת לקובץ (מטמון דפים). *
swap: כמות הזיכרון שהועברה ל-ZRAM.
4. הבחנה בשינויים
- פותחים את MemoryLab.
- שימו לב לערך של
memory.current. - מקישים כמה פעמים על הקצאת זיכרון Java (10MB).
- כדאי לקרוא שוב את
memory.current. אפשר לראות שהגודל גדל בכ-10MB בכל הקשה. - מקישים על הקצאת מפות סיביות. כדאי לבדוק ב-
memory.statאם יש עלייה ב-anon.
החזרת כספים יזומה עם memory.reclaim
תכונה חשובה של memcg (גרסה 2) היא הקובץ memory.reclaim. כתיבת ערך לקובץ הזה מורה לליבה לנסות באופן מיידי לפנות את כמות הזיכרון הזו מ-memcg ומכל memcg שמתחתיו.
איך מערכת Android משתמשת ב-memory.reclaim
התכונה הזו משמשת את CachedAppOptimizer ב-Android כדי לפנות כמה שיותר זיכרון מאפליקציות אחרי שהן מוקפאות. התכונה הקפאת אפליקציות ב-Android מוודאת שאפליקציות שנשמרו במטמון יצרכו כמה שפחות זיכרון RAM בזמן שהן לא פועלות. כשאפליקציה עוברת לרקע ומוקפאת, המערכת כותבת את השימוש הנוכחי בזיכרון של האפליקציה בקובץ memory.reclaim שלה. הפעולה הזו מאלצת את ליבת המערכת לפנות את כל דפי הקבצים האפשריים ולהחליף את כל הדפים האנונימיים ל-ZRAM, וכך מצמצמת את טביעת הרגל של האפליקציה בזיכרון.
אפשר להשיג את אותו "החזר מקסימלי" באופן ידני על ידי קריאת memory.current
וכתיבתו בחזרה לתוך memory.reclaim:
# Force reclaim of everything
adb shell "cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
תרגיל: ביטול הקצאה בכוח ומעקב
עכשיו נכריח את ליבת המערכת להקצות מחדש זיכרון מ-MemoryLab ולתעד את הפעילות בנתוני מעקב של Perfetto.
- הכנה: מוודאים שיש הקצאות ב-MemoryLab (Java ו-Bitmaps).
התחלת מעקב: אפשר להשתמש בהגדרת המעקב המוטמעת הזו כדי לתעד אירועים של תזמון, שחזור ומטמון דפים.
# Use a config that captures scheduling, reclaim and page cache events adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/reclaim.perfetto-trace <<EOF buffers: { size_kb: 131072 fill_policy: RING_BUFFER } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "kmem/rss_stat" ftrace_events: "mm_filemap_add_to_page_cache" ftrace_events: "mm_filemap_delete_from_page_cache" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_end" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_end" symbolize_ksyms: true } } } data_sources: { config { name: "linux.process_stats" process_stats_config { scan_all_processes_on_start: true } } } EOFהפעלת לחץ ודרישה בחזרה:
- ב-MemoryLab, מקישים על Allocate Native Memory (1GB) (הקצאת זיכרון מקומי (1GB)). הפעולה הזו תגרום ללחץ על המערכת כולה.
במסוף נפרד, משחררים 200MB:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Fault Back In: חזרה ל-MemoryLab. מקישים על Thrash Pagecache (Refault test).
עצירת המעקב: מקישים על Ctrl+C בטרמינל של המעקב.
ניתוח של תהליך השחזור ב-Perfetto
כשפותחים את הנתונים, אפשר לראות את הפעולה של השבתת השימוש בזיכרון גם ברמת המערכת וגם ברמת האפליקציה:

מחפשים את הדברים הבאים בנתוני המעקב:
- kswapd: מחפשים את
kswapd0בקטע Kernel threads (בצילום המסך שלמעלה, הוא הוצמד ידנית לחלק העליון). תוכלו לראות אותו מתעורר ופועל (פרוסות ירוקות) כשהמערכת מתקשה למצוא דפים פנויים. - החזרת כספים ישירה: בודקים את שרשורי התהליך
com.android.memorylab. פרוסות סגולות של אירועי ftrace (כמוmm_vmscan_direct_reclaim_begin) יופיעו ישירות מתחת ל-scheduling track של השרשור. ההודעה הזו מציינת שה-thread של האפליקציה תקוע בהמתנה שהליבה תשחרר דפים. - RSS and Swap Counters:
-
mem.rss.anon: הערך עולה כשלוחצים על לחצני ההקצאה. -
mem.swap: הערך עולה בהדרגה ככל ש-kswapdוהשרשורים של האפליקציה (בכפוף להחזרה ישירה) דוחסים את הדפים האנונימיים האלה ל-ZRAM.
-
- memcg Reclaim: אם תגדילו את התצוגה של הרגע שבו הפעלתם את השחרור הידני, תראו ירידה חדה בערכים של
rss.anonושלrss.file, לצד אירועים שלmm_vmscan_memcg_reclaim.
← System-wide | ↑ Up | kswapd and lmkd interaction →