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

ב-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 מבצע דחיסה של הזיכרון. אם הפסקת התהליך בגלל מחסור בזיכרון תהיה גלויה למשתמש, כלומר התהליך מארח פעילות, ואם כתיבת הנתונים מה-ZRAM לזיכרון הווירטואלי לכל תהליך תביא את הזיכרון שבשימוש של התהליך כמעט לאפס, המערכת תפעל לפי השלבים הבאים:

  1. אחרי הדחיסה, CachedAppOptimizer מפרסם הודעה מושהית (ZRAM_WRITEBACK_MSG) למטפל הדחיסה הפנימי שלו (ההשהיה היא mZramWritebackWaitSeconds).
  2. כשההשהיה מסתיימת, ActivityManager פותח תיאור קובץ מאובטח של תהליך pidfd.
  3. שרת המערכת קורא ל-mmd.asyncWritebackProcessZramMemory(pfd, callback).
  4. mmd מבצע את ioctl של כתיבה חוזרת לכל תהליך ומדווח בחזרה באמצעות 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 מנפיק את ioctl‏ ZRAM_ANDROID_IOC_PROCESS_PREFETCH, ומורה לליבה לבצע אחזור מראש באופן אסינכרוני של דפים שהועברו לזיכרון הווירטואלי בחזרה ל-RAM בזמן שהתהליכון הראשי של ממשק המשתמש של האפליקציה עובר אתחול.

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

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

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

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

  1. system_server periodically fires doZramMaintenanceAsync() across Binder.

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

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

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

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

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

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

כשדף מוחלף בהתחלה, המערכת נותנת עדיפות למהירות: היא משתמשת באלגוריתם דחיסה מהיר ראשי (כמו 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: עיבוד לאחר ההעברה 2 – כתיבה חוזרת (הוצאה לאחסון פלאש): אם העומס על הזיכרון נמשך והדפים מגיעים לגיל סרק של כתיבה חוזרת (בדרך כלל 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. אפשר להגדיר את המאפיינים ברשימת המכשירים לכל מכשיר בנפרד. 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

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

מאפיין (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 הצליח לפעול.