צמצום הגודל של עדכוני OTA

בדף הזה מתוארים שינויים שנוספו ל-AOSP כדי לצמצם את השינויים הלא נחוצים בקובצים בין גרסאות build. מפתחי מכשירים שמנהלים מערכות build משלהם יכולים להשתמש במידע הזה כמדריך לצמצום הגודל של העדכונים שלהם דרך האוויר (OTA).

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

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

מערכת build יכולה ליצור תיקונים גדולים מדי שלא נחוצים בכמה דרכים. כדי לצמצם את הבעיה, בגרסה Android 8.0 ואילך הוחלו תכונות חדשות שמפחיתות את גודל התיקון לכל קובץ. השיפורים שהקטינו את הגודל של חבילות העדכונים דרך OTA כוללים:

  • שימוש ב-ZSTD, אלגוריתם דחיסה ללא אובדן נתונים למטרות כלליות, לתמונות מלאות בעדכוני מכשיר שאינם A/B. אפשר להתאים אישית את ZSTD כדי לקבל יחסי דחיסה גבוהים יותר על ידי הגדלת רמת הדחיסה. רמת הדחיסה מוגדרת במהלך הזמן שבו מתבצע ה-OTA, וניתן להגדיר אותה על ידי העברת הדגל --vabc_compression_param=zstd,$COMPRESSION_LEVEL.
  • הגדלת גודל חלון הדחיסה שמשמש במהלך OTA. אפשר להגדיר את גודל חלון הדחיסה המקסימלי על ידי התאמה אישית של פרמטר ה-build בקובץ .mk של המכשיר. המשתנה הזה מוגדר בתור PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 262144.
  • שימוש ב-Puffin, כלי לתיקון דטרמיניסטי של זרמים של deflate, שמטפל בפונקציות הדחיסה וההשוואה ליצירת עדכוני OTA של A/B.
  • שינויים בשימוש בכלי ליצירת דלתא, למשל אופן השימוש בספרייה bsdiff לדחיסת תיקוני באגים. ב-Android 9 ואילך, הכלי bsdiff בוחר את אלגוריתם הדחיסה שיניב את תוצאות הדחיסה הטובות ביותר לתיקון.
  • שיפורים ב- update_engine הביאו לירידה בצריכת הזיכרון כשחלים תיקונים על עדכוני מכשירים בניסוי A/B.

בקטעים הבאים נדון בבעיות שונות שמשפיעות על גודל העדכונים דרך OTA, בפתרון שלהן ובדוגמאות להטמעה ב-AOSP.

סדר הקבצים

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

פתרון: כשמשתמשים בכלים כמו find ו-make עם פונקציית התו הכללי לחיפוש, צריך למיין את הפלט של הפקודות האלה לפני שמשתמשים בהן. כשמשתמשים ב-$(wildcard) או ב-$(shell find) בקבצים מסוג Android.mk, צריך למיין אותם גם כן. כלים מסוימים, כמו Java, מבצעים מיון של הקלט, ולכן לפני שממיינים את הקבצים, צריך לוודא שהכלי שבו אתם משתמשים כבר לא עשה זאת.

דוגמאות: תיקנו הרבה מקרים במערכת הליבה ליצירת גרסאות build באמצעות המאקרו המובנה all-*-files-under, שכולל את all-cpp-files-under (כי כמה הגדרות הופצו בקובצי makefile אחרים). פרטים נוספים זמינים במאמרים הבאים:

ספריית ה-build

הבעיה: שינוי הספרייה שבה מתבצע ה-build יכול לגרום לקבצים הבינאריים להיות שונים. רוב הנתיבים ב-build של Android הם נתיבים יחסיים, כך ש-__FILE__ ב-C/C++ לא מהווה בעיה. עם זאת, סמלי ניפוי הבאגים מקודדים את נתיב הנתונים המלא כברירת מחדל, וה-.note.gnu.build-id נוצר מהצפנה של הקובץ הבינארי שנחסר ממנו תוכן מראש, כך שהוא ישתנה אם סמלי ניפוי הבאגים ישתנו.

הפתרון: עכשיו נתיבי ניפוי הבאגים ב-AOSP הם יחסיים. פרטים נוספים זמינים ב-CL: https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02.

חותמות זמן

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

  • __DATE__/__TIME__/__TIMESTAMP__ מאקרו בקוד C או C++.
  • חותמות זמן שמוטמעות בארכיונים מבוססי-zip.

פתרונות/דוגמאות: כדי להסיר חותמות זמן מפלט ה-build, יש לפעול לפי ההוראות שמפורטות בהמשך בקטע __DATE__/__TIME__/__TIMESTAMP__ ב-C/C++ ובקטע חותמות זמן מוטמעות בארכיונים.

__DATE_/__TIME__/__TIMESTAMP__ ב-C/C++

המאקרואים האלה תמיד יוצרים תוצאות שונות לגרסאות build שונות, לכן אל תשתמשו בהם. ריכזנו כאן כמה אפשרויות להסרת המאקרו:

חותמות זמן מוטמעות בארכיונים (zip, ‏ jar)

ב-Android 7.0 תוקנה הבעיה של חותמות זמן מוטמעות בארכיונים בפורמט zip, על ידי הוספת -X לכל השימושים בפקודה zip. כך הוסרו מקובץ ה-zip ה-UID/GID של ה-builder וחותמת הזמן המורחבת של Unix.

כלי חדש, ziptime (שנמצא ב-/platform/build/+/android16-release/tools/ziptime/), מאפס את חותמות הזמן הרגילות בכותרות ה-zip. פרטים נוספים זמינים בקובץ ה-README.

הכלי signapk מגדיר חותמות זמן לקובצי ה-APK, שעשויות להשתנות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL‏ https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.

הכלי signapk מגדיר חותמות זמן לקובצי ה-APK, שעשויות להשתנות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL‏ https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.

מחרוזות גרסאות

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

פתרון: מסירים את מספר ה-build ממחרושת גרסת ה-APK.

דוגמאות:

הפעלת חישוב של אימות במכשיר

אם התכונה dm-verity מופעלת במכשיר, הכלים לעדכון OTA מזהים באופן אוטומטי את הגדרות האימות ומפעילים את החישוב של האימות במכשיר. כך אפשר לחשב בלוקים של אימות במכשירי Android, במקום לאחסן אותם כבייט גולמיים בחבילת ה-OTA. בלוקים של Verity יכולים להשתמש ב-16MB בערך למחיצה של 2GB.

עם זאת, חישוב האימות במכשיר עשוי להימשך זמן רב. באופן ספציפי, הקוד לתיקון שגיאות יכול להימשך זמן רב. במכשירי Pixel, התהליך נמשך עד 10 דקות. במכשירים בסיסיים, התהליך עשוי להימשך זמן רב יותר. אם רוצים להשבית את החישוב של אימות במכשיר, אבל עדיין להפעיל את dm-verity, אפשר להעביר את הערך --disable_fec_computation לכלי ota_from_target_files בזמן יצירת עדכון OTA. הדגל הזה משבית את חישוב האימות במכשיר במהלך עדכונים דרך OTA. היא מקצרת את זמן ההתקנה דרך OTA, אבל מגדילה את גודל חבילת ה-OTA. אם ה-dm-verity לא מופעל במכשיר, העברת הדגל הזה לא תשפיע.

כלים עקביים ל-build

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

פתרונות/דוגמאות: נדרשו שינויים בכלי ה-build הבאים:

שימוש בכלי build diff

במקרים שבהם אי אפשר למנוע שינויים בקבצים שקשורים ל-build, מערכת AOSP כוללת target_files_diff.py, כלי להשוואה בין שתי חבילות קבצים. הכלי מבצע השוואה רפלקסיבית בין שני גרסאות build, לא כולל שינויים נפוצים בקובצי build, כמו

  • שינויים צפויים בפלט של ה-build (לדוגמה, עקב שינוי במספר ה-build).
  • שינויים עקב בעיות ידועות במערכת ה-build הנוכחית.

כדי להשתמש בכלי build diff, מריצים את הפקודה הבאה:

target_files_diff.py dir1 dir2

dir1 ו-dir2 הן ספריות בסיס שמכילות את קובצי היעד שחולצו לכל build.

שמירה על עקביות בהקצאת בלוקים

בקובץ נתון, התוכן שלו נשאר זהה בין שני גרסאות build, אבל יכול להיות שהבלוקים בפועל שמכילים את הנתונים השתנו. כתוצאה מכך, מנהל העדכונים צריך לבצע פעולות קלט/פלט מיותרות כדי להעביר את הבלוק לעדכון OTA.

בעדכון OTA וירטואלי של A/B, פעולות קלט/פלט מיותרות יכולות להגדיל באופן משמעותי את נפח האחסון הנדרש לשמירת קובץ ה-snapshot של 'העתקה בזמן כתיבה'. בעדכון OTA שאינו A/B, העברת הבלוק לעדכון OTA תורמת לזמן העדכון כי יש יותר קלט/פלט בגלל העברת הבלוק.

כדי לטפל בבעיה הזו, Google הרחיבה את הכלי make_ext4fs ב-Android 7.0 כדי לשמור על עקביות בהקצאת בלוקים בין גרסאות build. הכלי make_ext4fs מקבל את הדגל האופציונלי -d base_fs, שמנסה להקצות קבצים לאותו בלוק בזמן יצירת קובץ האימג' ext4. אפשר לחלץ את קובצי המיפוי של הבלוק (כמו קובצי המפה base_fs) מקובץ ה-zip של קובצי היעד של build קודם. לכל מחיצה ext4 יש קובץ .map בספרייה IMAGES (לדוגמה, IMAGES/system.map תואם למחיצה system). לאחר מכן אפשר להוסיף את קובצי ה-base_fs למאגר ולציין אותם באמצעות PRODUCT_<partition>_BASE_FS_PATH, כמו בדוגמה הבאה:

  PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map
  PRODUCT_SYSTEM_EXT_BASE_FS_PATH := path/to/base_fs_files/base_system_ext.map
  PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map
  PRODUCT_PRODUCT_BASE_FS_PATH := path/to/base_fs_files/base_product.map
  PRODUCT_ODM_BASE_FS_PATH := path/to/base_fs_files/base_odm.map

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

הימנעות משימוש בעדכונים של אפליקציות

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