הקצאת זיכרון בין תהליכים

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

בדף הזה נסביר את העקרונות הבסיסיים של הקצאת הזיכרון ב-Android למערכת ולאפליקציות של המשתמשים. בנוסף, נסביר איך מערכת ההפעלה מגיבה למצבים של זיכרון נמוך.

סוגי זיכרונות

מכשירי Android מכילים שלושה סוגים שונים של זיכרון: RAM,‏ zRAM ואחסון. חשוב לזכור שגם המעבד וגם המעבד הגרפי (GPU) ניגשים לאותו זיכרון RAM.

סוגי זיכרונות

איור 1. סוגי זיכרון – RAM,‏ zRAM ואחסון

  • זיכרון RAM הוא סוג הזיכרון המהיר ביותר, אבל בדרך כלל הוא מוגבל בגודל. בדרך כלל, במכשירים מתקדמים יש את נפחי ה-RAM הגדולים ביותר.

  • zRAM היא מחיצה של זיכרון RAM המשמשת למרחב החלפה. הנתונים נדחסים כשהם מועברים ל-zRAM, ולאחר מכן הם נדחסים כשהם מועתקים מ-zRAM. החלק הזה של זיכרון ה-RAM גדל או מתכווץ כאשר דפים מועברים ל-zRAM או ממנו. יצרני המכשירים יכולים להגדיר את הגודל המקסימלי.

  • האחסון מכיל את כל הנתונים הקבועים, כמו מערכת הקבצים וקוד האובייקט הכלול של כל האפליקציות, הספריות והפלטפורמה. קיבולת האחסון גדולה בהרבה מזו של שני סוגי הזיכרון האחרים. ב-Android, האחסון לא משמש למרחב החלפה כמו בהטמעות אחרות של Linux, כי כתיבת תכופה עלולה לגרום לבלאי בזיכרון הזה ולקצר את חיי המדיום לאחסון.

דפי זיכרון

ה-RAM מחולק לדפים. בדרך כלל כל דף מכיל 4KB של זיכרון.

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

  • במטמון: זיכרון שמגובה על ידי קובץ באחסון (לדוגמה, קוד או קבצים שממופים לזיכרון). יש שני סוגים של זיכרון במטמון:
    • פרטי: בבעלות תהליך אחד ולא משותף
      • נקי: עותק ללא שינוי של קובץ באחסון. אפשר למחוק אותו באמצעות kswapd כדי להגדיל את נפח הזיכרון הפנוי
      • 'לא נקי': עותק שעבר שינוי של הקובץ באחסון. אפשר להעביר אותו ל-zRAM או לדחוס אותו ב-zRAM באמצעות kswapd כדי להגדיל את נפח הזיכרון הפנוי
    • משותף: משמש כמה תהליכים
      • נקי: עותק ללא שינוי של הקובץ באחסון, אפשר למחוק אותו באמצעות kswapd כדי להגדיל את נפח הזיכרון הפנוי
      • מושחת: עותק שעבר שינוי של הקובץ באחסון. מאפשר לכתוב חזרה את השינויים בקובץ באחסון כדי להגדיל את הזיכרון הפנוי באמצעות kswapd, או באופן מפורש באמצעות msync() או munmap()
  • אנונימי: זיכרון לא מגובה בקובץ באחסון (לדוגמה, הוקצה על ידי mmap() עם הדגל MAP_ANONYMOUS מוגדר)
    • מלוכלכת: אפשר להעביר או לדחוס ב-zRAM באמצעות kswapd כדי להגדיל את נפח הזיכרון הפנוי

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

ניהול זיכרון יעיל

ל-Android יש שני מנגנונים עיקריים לטיפול במצבים של זיכרון נמוך: הדימון של החלפת הליבה (kernel swap daemon) וה-killer של זיכרון נמוך.

דימון להחלפת ליבה

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

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

דף נקי שמאוחסן נמחק

איור 2. דף נקי, עם גיבוי באחסון, נמחק

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

אם נפח הזיכרון הפנוי יורד מתחת לסף מסוים, המערכת תתחיל להרוג תהליכים.

דף מלוכלך הועבר ל-zRAM ודחוס

איור 3. דף מלוכלך הועבר ל-zRAM ודחוס

גורם להפסקה של תהליכים בזיכרון נמוך

לעיתים קרובות, kswapd לא מצליח לפנות מספיק זיכרון למערכת. במקרה כזה, המערכת משתמשת ב-onTrimMemory() כדי להודיע לאפליקציה שהזיכרון עומד להיגמר ושעליה לצמצם את ההקצאות שלה. אם זה לא מספיק, הליבה מתחילה להרוג תהליכים כדי לפנות זיכרון. כדי לעשות זאת, המערכת משתמשת ב-LMK (Low-memory killer).

כדי להחליט איזה תהליך להרוג, LMK משתמש בציון 'חסר זיכרון' שנקרא oom_adj_score כדי לתת עדיפות לתהליכים שפועלים. תהליכים עם ציון גבוה ימותו קודם. אפליקציות ברקע הן הראשונות שיושבתות, ותהליכי המערכת הם האחרונים שיושבתים. בטבלה הבאה מפורטות קטגוריות הדירוג של LMK, מהגבוהה לנמוכה. הפריטים בקטגוריה עם הציון הגבוה ביותר, בשורה הראשונה, יימחקו קודם:

תהליכים ב-Android, התוצאות הגבוהות בחלק העליון

איור 4. תהליכים ב-Android, כאשר ציונים גבוהים מופיעים בחלק העליון וציונים נמוכים בחלק התחתון

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

  • אפליקציות ברקע: אפליקציות שפעלו בעבר ולא פעילות כרגע. LMK יסגור קודם את האפליקציות ברקע, החל מהאפליקציה עם הערך הגבוה ביותר של oom_adj_score.

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

  • אפליקציית Home: זוהי אפליקציית מרכז האפליקציות. סגירת האפליקציה הזו תגרום לטפט להיעלם.

  • שירותים: שירותים מופעלים על ידי אפליקציות ויכולים לכלול סנכרון או העלאה לענן.

  • אפליקציות גלויות: אפליקציות שלא נמצאות בחזית אבל גלויות למשתמש בדרך כלשהי, למשל הפעלת תהליך חיפוש שמוצג בו ממשק משתמש קטן או האזנה למוזיקה.

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

  • קבועים (שירותים): אלה שירותי הליבה של המכשיר, כמו טלפוניה ו-Wi-Fi.

  • מערכת: תהליכי מערכת. כשהתהליכים האלה יופסקו, יכול להיות שתראו שהטלפון מופעל מחדש.

  • מקורי: תהליכים ברמה נמוכה מאוד שבהם המערכת משתמשת (לדוגמה, kswapd).

יצרני המכשירים יכולים לשנות את ההתנהגות של LMK.

חישוב הזיכרון שבשימוש

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

דפים שמשמשים תהליכים שונים

איור 5. דפים שמשמשים תהליכים שונים

כשהמערכת קובעת כמה זיכרון נעשה בו שימוש על ידי אפליקציה, היא צריכה להביא בחשבון דפים משותפים. אפליקציות שיש להן גישה לאותו שירות או לאותה ספרייה ישתפו דפי זיכרון. לדוגמה, יכול להיות ש-Google Play Services ואפליקציית משחק משתפים שירות מיקום. לכן קשה לקבוע כמה זיכרון שייך לשירות באופן כללי, לעומת כל אפליקציה.

דפים ששותפו על ידי שתי אפליקציות

איור 6. דפים ששותפו על ידי שתי אפליקציות (במרכז)

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

  • Resident Set Size‏ (RSS): מספר הדפים המשותפים והלא משותפים שבהם האפליקציה משתמשת
  • Proportional Set Size‏ (PSS): מספר הדפים הלא משותפים שבהם האפליקציה משתמשת, והחלוקה השווה של הדפים המשותפים (לדוגמה, אם שלושה תהליכים משתפים 3MB, כל תהליך מקבל 1MB ב-PSS)
  • גודל קבוצה ייחודית (USS): מספר הדפים שלא משותפים שבהם האפליקציה משתמשת (דפים משותפים לא נכללים)

הערך PSS שימושי למערכת ההפעלה כשהיא רוצה לדעת כמה זיכרון מנוצל על ידי כל התהליכים, כי הדפים לא נספרים כמה פעמים. חישוב ה-PSS נמשך זמן רב כי המערכת צריכה לקבוע אילו דפים משותפים וכמה תהליכים משתפים אותם. ב-RSS אין הבחנה בין דפים משותפים לדפים לא משותפים (כך החישוב מהיר יותר), והוא מתאים יותר למעקב אחרי שינויים בהקצאת הזיכרון.

מקורות מידע נוספים