דמון לניהול זיכרון

ב-Android 17 ואילך יש תמיכה בדימון (daemon) לניהול זיכרון (mmd), שהוא דימון (daemon) של המערכת שמטפל בהגדרת הדימון (daemon), בפרמטרים שניתנים לשינוי ובמשימות שוטפות של החלפה או תחזוקה של ZRAM.

רקע

לפני ההשקה של mmd, ההגדרות של ZRAM ב-Android היו מפוצלות והציעו אפשרויות מוגבלות להתאמה אישית. mmd פותרת את הבעיה הזו על ידי ריכוז הניהול של ZRAM, ומאפשרת לוגיקת הגדרה מתוחכמת יותר ופשוטה יותר להוספה של תכונות חדשות ולשיפורים בארכיטקטורה. ‫mmd גם יוצר הפרדה ברורה בין תהליך system_server שמבוסס על Java לבין החלפה ברמת הליבה או ניהול זיכרון.

ארכיטקטורה וניהול ZRAM

בסיום האתחול (כלומר, כש-sys.boot_completed=1), mmd_setup מנסה להגדיר את ZRAM עם הפרמטרים שצוינו. אחרי שסיימתם להגדיר את ZRAM, המערכת מפעילה את שירות mmd שמטפל במשימות תחזוקה שוטפות.

בפרויקט mmd, פעולות התחזוקה מתחילות מ-system_server על ידי שליחת בקשות Binder אל mmd באמצעות הממשק IMmd. ‫mmd מטפל במשימות התחזוקה של ביצוע כתיבה חוזרת של ZRAM, דחיסה מחדש וכתיבה חוזרת לכל תהליך על סמך מנוע מדיניות פנימי משלו. אפשר להגדיר את התזמון מ-ActivityManagerService ואת מדיניות התחזוקה של ZRAM באמצעות מאפייני מערכת.

שילוב שרת מערכת (system_server)

תהליך system_server מבוסס-Java קובע מתי מתבצעת קריאה ל-mmd. התהליך מפריד בין סריקות גלובליות של תחזוקה לבין אופטימיזציות ממוקדות של זיכרון לכל אפליקציה.

תחזוקה רגילה של פוסט-פרודקשן

התחזוקה הגלובלית של ZRAM מופעלת על ידי ActivityManagerService באמצעות com.android.server.memory.ZramMaintenance.

zram-maintenance

איור 1. תהליך תזמון התחזוקה של ZRAM.

  • מנוע התזמון: ZramMaintenance רושם משימת רקע תקופתית ב-JobScheduler של Android.
  • הגבלות על משימות: כדי למנוע גמגום בממשק המשתמש של האפליקציה או תחרות על משאבי המעבד, המשימה מוגדרת באופן מפורש עם setRequiresDeviceIdle(true) ו-setRequiresBatteryNotLow(true).
  • הפעלת Binder: כשהמתזמן מפעיל את onStartJob(), system_server מפעיל את mmd.doZramMaintenanceAsync(). זוהי קריאה חד-כיוונית ל-Binder באופן אסינכרוני. system_server לא חוסם את ההמתנה לסיום של סריקות תחזוקה. ‫mmd מעביר את הפעולה הזו לתור של תהליך רקע כדי לבצע דחיסה מחדש וכתיבה חוזרת ברצף.

החזרת נתונים לכל תהליך

הסרת זיכרון ממוקדת לכל תהליך מנוהלת על ידי ActivityManagerService באמצעות com.android.server.am.CachedAppOptimizer.

mmd-writeback

איור 2. זרימת כתיבה חוזרת של mmd לכל תהליך.

כשמעבירים תהליך למצב שמור במטמון ברקע, ActivityManager מבצע דחיסה של הזיכרון. אם הפסקת התהליך בגלל מחסור בזיכרון תהיה גלויה למשתמש, כלומר התהליך מארח Activity, ואם כתיבה חוזרת של ZRAM לכל תהליך תביא את הזיכרון שבשימוש של התהליך קרוב לאפס, המערכת תפעל לפי השלבים הבאים:

  1. אחרי הדחיסה, CachedAppOptimizer מפרסם הודעה מושהית (ZRAM_WRITEBACK_MSG) למטפל הדחיסה הפנימי שלו (ההשהיה היא של mZramWritebackWaitSeconds).
  2. כשזמן ההשהיה מסתיים, ActivityManager פותח תיאור קובץ של תהליך מאובטח pidfd.
  3. שרת המערכת קורא ל-mmd.asyncWritebackProcessZramMemory(pfd, callback).
  4. mmd מבצע את פעולת ה-ioctl של הכתיבה החוזרת לכל תהליך (per-process writeback) ומדווח בחזרה באמצעות IMmdProcessWritebackCallback. אם הפעולה תצליח, ActivityManager יסמן את רשומת התהליך (setIsZramWrittenBack(app, true)) כדי לשפר את oom_score_adj של התהליך, ויתעד את המדדים ב-FrameworkStatsLog.ZRAM_WRITEBACK_EVENT.

אחזור מראש לכל תהליך

כשמשתמש מפעיל מחדש אפליקציה ששמורה במטמון (ההקפאה שלה בוטלה בגלל UNFREEZE_REASON_ACTIVITY), ActivityManager מצמצם את זמן האחזור של הפעלת האפליקציה שנגרם בגלל תקלות חמורות בדפים מאחסון הגיבוי:

  1. CachedAppOptimizer מיירט את אירוע הביטול של ההקפאה ומפעיל את prefetchZram(app).
  2. שרת המערכת שולח את pidfd של האפליקציה באמצעות Binder באמצעות mmd.asyncPrefetchProcessZramMemory(pfd). ‫mmd מנפיק את ZRAM_ANDROID_IOC_PROCESS_PREFETCH ioctl, ומורה לליבה לבצע שליפה מראש באופן אסינכרוני של דפים שהועברו בחזרה ל-RAM בזמן ששרשור ה-UI הראשי של האפליקציה עובר אתחול.

סקירה כללית של משימות תחזוקה ועיבוד לאחר העיבוד

בקטע הזה מתוארות פעולות התחזוקה ברקע ומשימות העיבוד שאחרי ש-mmd מבצע כדי לבצע אופטימיזציה של שטח ההחלפה וזיכרון המערכת.

תחזוקה בפורמט mmd

ב-mmd, תחזוקה מתייחסת לסריקות תחזוקה מתוזמנות ברקע, שמבצעות אופטימיזציה של השימוש באזור ההחלפה ובזיכרון הפיזי בלי להשפיע על הביצועים של משתמשים פעילים בחזית. במקום לבצע סריקות סינכרוניות רציפות (שיגרמו להפעלות חמורות של המעבד ולבעיות בממשק המשתמש), התחזוקה מתבצעת באופן אסינכרוני:

  1. system_server מופעל מעת לעת doZramMaintenanceAsync() ב-Binder.

  2. mmd מעביר את הבקשה לתור של עבודות רקע LowPrioWorkItem::ZramMaintenance.

  3. ב-mmd יש שרשור עובד יחיד שמנהל תור בעדיפות גבוהה ותור בעדיפות נמוכה. פריטי עבודה בעדיפות גבוהה (כמו שליפה מראש לכל תהליך) מקבלים עדיפות ויכולים להפסיק לפני הזמן פריטי עבודה בעדיפות נמוכה. תחזוקה וכתיבה חוזרת לכל תהליך פועלות כפריטי עבודה בעדיפות נמוכה. כשפותחים את חלון ה-pop-up, ה-thread של העובד מבצע שתי פעולות תחזוקה עיקריות ברצף:

    • דחיסה מחדש של ZRAM: סורקת דפי החלפה קיימים ודוחסת מחדש דפים לא פעילים באמצעות אלגוריתם דחיסה משני עם יחס גבוה יותר, לדוגמה, zstd.

    • ZRAM writeback: סורק דפים לא פעילים ומפנה אותם לחלוטין מ-RAM לאחסון פלאש תומך, מכשיר לולאה מקובץ ב-/data.

משימות עיבוד תמונה (Post Processing) ב-ZRAM

במודול ZRAM של ליבת Linux ובארכיטקטורה של mmd, משימות של עיבוד פוסט הן טרנספורמציות אסינכרוניות שמוחלות על דפי זיכרון אחרי שהן כבר הוחלפו על ידי נתיבי ההחזרה הרגילים של הליבה (kswapd או compaction).

כשדף מוחלף בהתחלה, המערכת נותנת עדיפות למהירות: היא משתמשת באלגוריתם דחיסה מהיר ראשי (כמו lz4) ומאחסנת את הדף הדחוס ב-RAM. עם זאת, לאורך זמן, הרבה דפים שהועברו לזיכרון הווירטואלי הופכים ללא פעילים, למשל אפליקציות ששמורות במטמון ברקע ולא נפתחות מחדש במשך שעות. השארת דפים לא פעילים ב-ZRAM מהיר ודחוס קלות היא לא יעילה.

צינור עיבוד נתונים (pipeline)

mmd מטמיע מחזור חיים של עיבוד שלאחר מכן, שכולל כמה שלבים, כדי לבצע אופטימיזציה של הדפים האלה:

mmd-page-lifecycle

איור 3. mmd מחזור החיים של הדף.

  1. שלב 1: החלפה ראשונית (דחיסה מהירה): הזיכרון משוחרר קודם באמצעות kswapd או דחיסת אפליקציות. בדרך כלל, השחזור הראשון מתבצע באמצעות אלגוריתם דחיסה מהיר כמו lz4, והתוכן מאוחסן ב-RAM.

  2. שלב 2: סימון של חוסר פעילות (התיישנות ומעקב): mmd מעקב חוסר פעילות ניגש למעקב זיכרון ליבה (CONFIG_ZRAM_TRACK_ENTRY_ACTIME) או משתמש בסמן חוסר הפעילות של התוכנה כדי לעקוב אחרי משך הזמן שדפים נשארו ללא מגע.

  3. שלב 3: עיבוד שלאחר מכן 1 – דחיסה מחדש (שחזור בזיכרון): דחיסה מחדש מתבצעת בדפים שמגיעים לגיל סרק של דחיסה מחדש (min_idle_seconds עד max_idle_seconds). ‫mmd כותב ל-/sys/block/zram0/recompress כדי להנחות את ליבת המערכת לבטל את הדחיסה של הדף lz4 ולדחוס אותו מחדש באמצעות zstd. הפעולה הזו מפנה זיכרון RAM פיזי בלי לגרום לשחיקה של צריבת ה-ROM‏ (flash).

  4. שלב 4: עיבוד אחרי – כתיבה חוזרת (פינוי לאחסון פלאש): אם העומס על הזיכרון נמשך והדפים מגיעים לגיל סרק של כתיבה חוזרת (בדרך כלל 20 שעות או יותר), mmd מפעיל כתיבה חוזרת. ‫mmd כותב ל-/sys/block/zram0/idle ו-/sys/block/zram0/writeback כדי להוציא את הדף הדחוס לגמרי מ-RAM לאחסון פלאש לגיבוי.

הגדרת תצורה של ZRAM

הפקודה mmd טוענת ומעבדת את מאפייני ההגדרה הבאים של ZRAM:

מאפיין (property) שימוש ברירת מחדל
mmd.zram.enabled האם ההגדרה mmd ZRAM מופעלת. false
mmd.zram.num_devices מספר מכשירי ה-ZRAM שצריך להגדיר. כדי להגדיר מספר N, צריך לוודא שמכשירים zram0 עד zram<N-1> נמצאים בסביבה לפני שהמערכת מגדירה את sys.boot_completed=1. אפשר להגדיר את המאפיינים ברשימת המכשירים עם ZRAM לכל מכשיר בנפרד. 1
mmd.zram.device_priority ערכי עדיפות להעברה בעת קריאה ל-swapon. לא מוגדר
mmd.zram.comp_algorithm אלגוריתם דחיסה של ZRAM. אם לא מציינים אלגוריתם, המערכת משתמשת באלגוריתם הדחיסה שמוגדר כברירת מחדל בקרנל. לא מוגדר
mmd.zram.size גודל מכשיר ZRAM בבייט, או אחוז מגודל זיכרון ה-RAM של המכשיר, לדוגמה, 75%. 50%
mmd.zram.writeback.enabled האם להפעיל ZRAM writeback. false
mmd.zram.writeback.device_size הגודל של מכשיר הכתיבה החוזרת בבייטים או באחוזים ממחיצת הנתונים. אפשר לשנות את הגודל של המכשיר בפועל בהתאם לנפח האחסון הזמין במחיצת הנתונים. 1073741824 (1 GiB)
mmd.zram.writeback.min_free_space_mib השטח הפנוי המינימלי ב-MiB שצריך להיות זמין אחרי שמגדירים את מכשיר הכתיבה. 1536 (1.5 GiB)
mmd.zram.writeback.use_nr_tags_prop כש-true משתמש בערך ב-mmd.zram.writeback.nr_tags כדי להגדיר את עומק התור של מכשיר הלולאה שמגבה את ZRAM writeback. זהו פתרון עקיף למצבים שבהם אי אפשר להגדיר את מדיניות SELinux של הספק כך שתאפשר ל-mmd לקרוא ישירות את nr_tags של מכשיר הבלוק שמגבה את /data. false
mmd.zram.writeback.nr_tags מומלץ לקרוא את mmd.zram.writeback.use_nr_tags_prop. לא מוגדר
mmd.zram.recompression.enabled האם להפעיל את התכונה ZRAM recompression. false
mmd.zram.recompression.algorithm אלגוריתם משני לדחיסה מחדש של ZRAM. zstd

מאפייני מכשיר לכל ZRAM

אם הערך של mmd.zram.num_devices גדול מ-1, אפשר להגדיר מאפיינים ספציפיים לכל מכשיר ZRAM בנפרד. לשם כך, צריך להגדיר את המאפיין לערך מופרד בפסיקים שמכיל בדיוק mmd.zram.num_devices רכיבים. המאפיינים האלה כוללים:

  • mmd.zram.size
  • mmd.zram.comp_algorithm
  • mmd.zram.device_priority
  • mmd.zram.recompression.enabled
  • mmd.zram.recompression.huge_idle.enabled
  • mmd.zram.recompression.idle.enabled
  • mmd.zram.recompression.huge.enabled
  • mmd.zram.recompression.threshold_bytes
  • mmd.zram.recompression.algorithm
  • mmd.zram.writeback.device_size
  • mmd.zram.writeback.huge_idle.enabled
  • mmd.zram.writeback.idle.enabled
  • mmd.zram.writeback.huge.enabled

הוצאה משימוש של הגדרת ZRAM קיימת

אפשר עדיין להשתמש ב-swapon_all ב-Android כדי להגדיר ZRAM ושטח החלפה מבוסס-דיסק, אבל mmd היא הגישה המומלצת לניהול ZRAM, כי היא מאפשרת הגדרה קלה יותר ותכונות מתקדמות כמו דחיסה מחדש של ZRAM.

כשההגדרה של mmd ZRAM מופעלת על ידי mmd.zram.enabled:

  • הגדרת ZRAM בהטמעה של swapon_all הופכת לפעולה שלא עושה כלום.
  • המערכת מתעלמת מהגדרות ZRAM קיימות, כמו config_zramWriteback בקובץ שכבת העל config.xml ומאפייני המערכת של ro.zram.* writeback.

פרמטרים שניתנים לשינוי לתחזוקת ZRAM

תחזוקת ZRAM אמורה לפעול מחוץ לקופסה, ואפשר לבצע כוונון עדין נוסף באמצעות מאפייני המערכת שמופיעים בקטע הזה.

תזמון תחזוקה של ZRAM

המאפיינים האלה קובעים איך ומתי system_server יתזמן משימות תחזוקה של ZRAM.

מאפיין (property) שימוש ברירת מחדל
mm.zram.maintenance.first_delay_seconds ההשהיה לפני התחלת התחזוקה הראשונה של ZRAM. 3600 (שעה אחת)
mm.zram.maintenance.periodic_delay_seconds ההשהיה בין תזמון תחזוקה עוקב של ZRAM. 3600 (שעה אחת)
mm.zram.maintenance.require_device_idle האם להפעיל תחזוקת ZRAM רק כשהמכשיר בלי פעילות. true
mm.zram.maintenance.require_battery_not_low האם נדרש שהסוללה לא תהיה חלשה לפני הפעלת תחזוקת ZRAM. true

מדיניות writeback של ZRAM

הפרמטרים הבאים קובעים מתי ואיזה סוג של זיכרון נכתב במכשיר הגיבוי:

מאפיין (property) שימוש ברירת מחדל
mmd.zram.writeback.backoff_seconds הזמן שחלף מאז פעולת הכתיבה החוזרת האחרונה. 600 (10 דקות)
mmd.zram.writeback.min_idle_seconds בשילוב עם mmd.zram.writeback.max_idle_seconds כדי לחשב את משך הזמן שהדף לא היה פעיל, כדי לקבוע אם הוא עומד בדרישות לכתיבה חוזרת על סמך חלק ניצול הזיכרון. הגיל המחושב של חוסר הפעילות הוא אינטרפולציה אקספוננציאלית בין שני הפרמטרים כדי למזער את העבודה בזמן שאין לחץ על הזיכרון. 72000 (20 שעות)
mmd.zram.writeback.max_idle_seconds מספר השניות המקסימלי שמשמש לחישוב דינמי של משך הזמן שהדף לא פעיל, על סמך ניצול הזיכרון. 90000 (25 שעות)
mmd.zram.writeback.huge.enabled האם להפעיל HUGE כתיבה חוזרת לדף. false
mmd.zram.writeback.idle.enabled האם להפעיל IDLE כתיבה חוזרת לדף. true
mmd.zram.writeback.huge_idle.enabled האם להפעיל HUGE_IDLE כתיבה חוזרת לדף. true
mmd.zram.writeback.min_bytes מספר הבייטים המינימלי לכתיבה חוזרת בסבב אחד של כתיבה חוזרת במצב סרק. 5242880 (5 MiB)
mmd.zram.writeback.max_bytes מספר הבייטים המקסימלי לכתיבה חוזרת בסיבוב אחד של כתיבה חוזרת במצב סרק. 314572800 (‎300 MiB)
mmd.zram.writeback.max_bytes_per_day המספר המקסימלי של בייטים לכתיבה חוזרת בפרק זמן של 24 שעות. 25769803776 (24 GiB)
mmd.zram.writeback.limit.enabled האם להפעיל את ניהול הרישום של מכסת התקציב היומי להחזרת נתונים. true

מדיניות דחיסה מחדש של ZRAM

הפרמטרים הבאים קובעים מתי ואיזה סוג של זיכרון יידחס מחדש:

מאפיין (property) שימוש ברירת מחדל
mmd.zram.recompression.backoff_seconds הזמן שחלף מאז הדחיסה מחדש האחרונה. 1800 (30 דקות)
mmd.zram.recompression.min_idle_seconds בשילוב עם mmd.zram.recompression.max_idle_seconds כדי לחשב את משך הזמן שהדף לא פעיל, כדי לקבוע אם הוא עומד בדרישות לדחיסה מחדש על סמך חלק ניצול הזיכרון. הגיל המחושב של בלי פעילות הוא אינטרפולציה אקספוננציאלית בין שני הפרמטרים כדי למזער את העבודה בזמן שאין לחץ על הזיכרון. 7200 (שעתיים)
mmd.zram.recompression.max_idle_seconds מספר השניות המקסימלי שמשמש לחישוב דינמי של משך הזמן שהדף היה בלי פעילות. 14400 (4 שעות)
mmd.zram.recompression.threshold_bytes הגודל המינימלי בבייטים של דפי ZRAM שנלקחים בחשבון לצורך דחיסה מחדש. 1024 (1 KiB)
mmd.zram.recompression.huge.enabled האם להפעיל דחיסה מחדש של דף HUGE. true
mmd.zram.recompression.idle.enabled האם להפעיל דחיסה מחדש של דף IDLE. true
mmd.zram.recompression.huge_idle.enabled האם להפעיל דחיסה מחדש של דף HUGE_IDLE. true

מעקב אחר דפים לא פעילים ב-ZRAM

mmd תחזוקת ZRAM מסמנת דפי ZRAM כלא פעילים על סמך משך הזמן שעבר מאז הגישה האחרונה אליהם. כדי להשתמש בתכונה הזו צריך להפעיל את ההגדרות של ליבת CONFIG_ZRAM_TRACK_ENTRY_ACTIME או CONFIG_ZRAM_MEMORY_TRACKING. ‫CONFIG_ZRAM_TRACK_ENTRY_ACTIME מופעל כברירת מחדל בקרנלים של GKI בגרסה 6.18 ומעלה. בליבות קודמות, יש תקורה של זיכרון והיא לא מופעלת כברירת מחדל.

אם הגדרת הליבה לא מופעלת, mmd תחזוקת ZRAM חוזרת ללוגיקה של תחליף תוכנה כדי לעקוב אחרי דפי ZRAM בלי פעילות:

  1. סימון כל דפי ה-ZRAM כלא פעילים כשמפעילים את mmd.

  2. מדלגים על תחזוקת ZRAM הבאה עד שתקופת ההשהיה הנדרשת לפני ניסיון חוזר מסתיימת.

  3. ZRAM writeback or recompress idle pages. אם נשארו דפים לא פעילים בגלל מגבלות הכתיבה החוזרת, mmd ממשיך לכתוב בחזרה דפים בתחזוקה הבאה בלי לסמן דפים חדשים כלא פעילים (דילוג על שלב 4).

  4. אם כל הדפים הלא פעילים נכתבים בחזרה, מסמנים את כל דפי ה-ZRAM כלא פעילים שוב וחוזרים לשלב 2. אם ההגדרה ZRAM writeback מושבתת, mmd מסמן את כל דפי ZRAM כלא פעילים כשמתבצעת דחיסה מחדש של ZRAM אחרי משך הזמן של recompression idle.

הנחיות לפתרון בעיות ולאימות

כדי לאמת את הפעולות של mmd ו-ZRAM ולאבחן אותן, צריך לפעול לפי השלבים הבאים לאימות ולפתרון בעיות.

אימות ההגדרה של ZRAM

כדי לוודא ש-mmd הגדיר בהצלחה את ZRAM במהלך האתחול:

  1. בודקים את אלגוריתם הדחיסה הפעיל ואת גודל הדיסק:

    cat /sys/block/zram0/comp_algorithm
    cat /sys/block/zram0/disksize
    
  2. בודקים את mmd מאפייני המערכת ואת מצב השירות הפועל:

    getprop | grep mmd.zram
    dumpsys -l | grep mmd
    

אימות התחזוקה והכתיבה החוזרת של ZRAM

מוודאים שמשימות התחזוקה של כתיבה חוזרת ודחיסה מחדש של ZRAM פועלות:

  1. בודקים את הסטטוס של מכשיר הבלוקים הבסיסי:

    cat /sys/block/zram0/bd_stat
    
  2. כדי לבדוק את יעילות הדחיסה מחדש, עוקבים אחרי /sys/block/zram0/mm_stat. שינויים בגדלים של נתונים דחוסים אמורים להופיע אחרי מחזורי תחזוקה.

אימות של כתיבה חוזרת לכל תהליך

אפשר להשתמש בדרכים הבאות כדי לוודא שהכתיבה החוזרת לכל תהליך פועלת:

  • בודקים את adb logcat -s mmd כדי לראות אם יש יומנים של כתיבה חוזרת מוצלחת או אבחון של כשלים.

בעיות נפוצות וניתוחים

אלה מצבי השגיאה הנפוצים שבהם המשתמשים עשויים להיתקל:

  • WritebackDailyLimitExceeded: השגיאה הזו מציינת שהגעתם למכסה של mmd.zram.writeback.max_bytes_per_day. במקרה כזה, mmd משהה את הכתיבה למטמון בלי פעילות עד שחלון 24 השעות המתגלגל מתקדם.
  • Process prefetch or writeback failed: השגיאה הזו יכולה להופיע ב-logcat כש-ioctl נכשל. היא מופיעה בדרך כלל מהסיבות הבאות:
    • EBADF או ESRCH: תהליך היעד הסתיים לפני ש-mmd יכול היה לשלוח את pidfd לליבה.
    • ENOSPC: מחיצת האחסון של הגיבוי מלאה, או שהתור של מכשיר הלולאה מוצה.
  • ZRAM לא מוגדר: אם mmd לא מצליח להגדיר ZRAM במהלך האתחול, יכול להיות שהסיבה לכך היא שסקריפטים של init מדור קודם swapon_all או של ספקים נעלו את /dev/block/zram0 לפני ש-mmd הצליח לפעול.