הטמעת עדכוני A/B

יצרני ציוד מקורי (OEM) וספקי מערכת על שבב (SoC) שרוצים להטמיע עדכוני מערכת A/B צריכים לוודא שתוכנת האתחול שלהם מטמיעה את HAL של boot_control ומעבירה את הפרמטרים הנכונים לליבת המערכת.

הטמעה של boot control HAL

בטועני אתחול עם תמיכה ב-A/B צריך להטמיע את boot_control HAL בכתובת hardware/libhardware/include/hardware/boot_control.h. אפשר לבדוק את ההטמעות באמצעות כלי השירות system/extras/bootctl ו-system/extras/tests/bootloader/.

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

איור 1. מכונת מצבים של תוכנת אתחול

הגדרת הליבה

כדי להטמיע עדכוני מערכת מסוג A/B:

  1. בוחרים את סדרת תיקוני הליבה הבאה (אם צריך):
  2. מוודאים שהארגומנטים בשורת הפקודה של ליבת המערכת מכילים את הארגומנטים הנוספים הבאים:
    skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
    … כאשר הערך <public-key-id> הוא המזהה של המפתח הציבורי שמשמש לאימות החתימה של טבלת האמת (פרטים נוספים זמינים במאמר בנושא dm-verity).
  3. מוסיפים את אישור X .509 שמכיל את המפתח הציבורי למחזיק המפתחות של המערכת:
    1. מעתיקים את אישור X .509 בפורמט .der אל ספריית הבסיס kernel. אם אישור X .509 הוא בפורמט של קובץ .pem, משתמשים בפקודה openssl הבאה כדי להמיר מפורמט .pem לפורמט .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. יוצרים את zImage כך שיכלול את האישור כחלק מ-keyring המערכת. כדי לאמת,בודקים את הרשומה procfs (נדרשת הפעלה של KEYS_CONFIG_DEBUG_PROC_KEYS):
      angler:/# cat /proc/keys
      
      1c8a217e I------     1 perm 1f010000     0     0 asymmetri
      Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
      2d454e3e I------     1 perm 1f030000     0     0 keyring
      .system_keyring: 1/4
      הכללה מוצלחת של אישור X .509 מציינת שהמפתח הציבורי קיים במחזיק המפתחות של המערכת (ההדגשה מציינת את מזהה המפתח הציבורי).
    3. מחליפים את הרווח ב-# ומעבירים אותו כ-<public-key-id> בשורת הפקודה של ליבת המערכת. לדוגמה, מעבירים את Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f במקום <public-key-id>.

הגדרת משתני build

טועני אתחול עם תמיכה בבדיקות A/B צריכים לעמוד בקריטריונים הבאים של משתני build:

חובה להגדיר את היעד לבדיקת A/B
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    ומחיצות אחרות שעודכנו דרך update_engine (רדיו, bootloader, וכו')
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
לדוגמה, אפשר לעיין ב/device/google/marlin/+/android-7.1.0_r1/device-common.mk. אפשר גם לבצע את השלב dex2oat אחרי ההתקנה (אבל לפני ההפעלה מחדש) שמתואר במאמר בנושא קומפילציה.
מומלץ מאוד לשימוש ביעד A/B
  • הגדרה של TARGET_NO_RECOVERY := true
  • הגדרה של BOARD_USES_RECOVERY_AS_BOOT := true
  • לא להגדיר את BOARD_RECOVERYIMAGE_PARTITION_SIZE
אי אפשר להגדיר יעד לבדיקת A/B
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
אופציונלי בגרסאות ניפוי באגים PRODUCT_PACKAGES_DEBUG += update_engine_client

הגדרת מחיצות (משבצות)

במכשירים עם חלוקה למחיצות A/B אין צורך במחיצת שחזור או במחיצת מטמון, כי מערכת Android כבר לא משתמשת במחיצות האלה. מחיצת הנתונים משמשת עכשיו לחבילת ה-OTA שהורדה, וקוד קובץ אימג' לשחזור מערכת ההפעלה נמצא במחיצת האתחול. כל המחיצות שמופעל בהן A/B טסט צריכות להיקרא באופן הבא (המשבצות תמיד נקראות a,‏ b וכו'): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b.

מטמון

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

שחזור

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

בעדכונים שהם לא A/B, מחיצת השחזור מכילה את הקוד שמשמש להחלת העדכונים. עדכוני A/B מוחלים על ידי update_engine שפועל בתמונה הרגילה של המערכת שהופעלה. עדיין יש מצב שחזור שמשמש להטמעת איפוס לנתוני היצרן ולהעברה צדדית של חבילות עדכון (מכאן השם 'שחזור'). הקוד והנתונים של מצב שחזור (recovery) מאוחסנים במחיצת האתחול הרגילה ב-ramdisk. כדי לבצע אתחול לקובץ אימג' של המערכת, תוכנת האתחול אומרת לליבה (kernel) לדלג על ה-ramdisk (אחרת המכשיר יאתחל למצב שחזור (recovery)). מצב השחזור קטן (וחלק גדול ממנו כבר היה במחיצת האתחול), ולכן גודל מחיצת האתחול לא גדל.

Fstab

הארגומנט slotselect חייב להיות בשורה של המחיצות שמוגדרות לבדיקת A/B. לדוגמה:

<path-to-block-device>/vendor  /vendor  ext4  ro
wait,verify=<path-to-block-device>/metadata,slotselect

אף מחיצה לא יכולה להיקרא vendor. במקום זאת, מחיצה vendor_a או vendor_b תיבחר ותותקן בנקודת הטעינה /vendor.

ארגומנטים של יחידות קיבולת (Slot) בליבה

צריך להעביר את הסיומת של המשבצת הנוכחית דרך צומת ספציפי של עץ המכשיר (DT) (/firmware/android/slot_suffix) או דרך שורת הפקודה של ליבת androidboot.slot_suffix או ארגומנט bootconfig.

כברירת מחדל, fastboot מפעיל את החריץ הנוכחי במכשיר A/B. אם חבילת העדכון מכילה גם תמונות עבור החריץ השני, שלא נמצא בשימוש, fastboot יעדכן גם את התמונות האלה. האפשרויות הזמינות כוללות:

  • --slot SLOT. אפשר לשנות את התנהגות ברירת המחדל ולגרום ל-fastboot להפעיל את המשבצת שמועברת כארגומנט.
  • --set-active [SLOT]. מגדירים את המשבצת כפעילה. אם לא מציינים ארגומנט אופציונלי, המשבצת הנוכחית מוגדרת כפעילה.
  • fastboot --help. מידע נוסף על פקודות

אם טוען האתחול מטמיע את fastboot, הוא צריך לתמוך בפקודה set_active <slot> שמגדירה את המשבצת הפעילה הנוכחית למשבצת הנתונה (הפקודה הזו צריכה גם לנקות את הדגל unbootable עבור המשבצת הזו ולאפס את מספר הניסיונות לערכי ברירת המחדל). בנוסף, תוכנת האתחול צריכה לתמוך במשתנים הבאים:

  • has-slot:<partition-base-name-without-suffix>. הפונקציה מחזירה yes אם המחיצה שצוינה תומכת במשבצות, אחרת היא מחזירה no.
  • current-slot. מחזירה את הסיומת של המשבצת שממנה תתבצע האתחול הבא.
  • slot-count. הפונקציה מחזירה מספר שלם שמייצג את מספר המשבצות הפנויות. בשלב הזה, המערכת תומכת בשני מיקומי מודעות, ולכן הערך הוא 2.
  • slot-successful:<slot-suffix>. הפונקציה מחזירה yes אם המשבצת הנתונה סומנה כמשבצת שהפעלה שלה הסתיימה בהצלחה, אחרת היא מחזירה no.
  • slot-unbootable:<slot-suffix>. הפונקציה מחזירה yes אם המשבצת הנתונה מסומנת כמשבצת שלא ניתן לאתחל ממנה, אחרת היא מחזירה no.
  • slot-retry-count:<slot-suffix>. מספר הניסיונות החוזרים שנותרו כדי לנסות לאתחל את המשבצת הנתונה.

כדי להציג את כל המשתנים, מריצים את הפקודה fastboot getvar all.

יצירת חבילות OTA

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

דוגמאות:

  • כדי ליצור OTA מלא:
    ./build/make/tools/releasetools/ota_from_target_files \
        dist_output/tardis-target_files.zip \
        ota_update.zip
    
  • כדי ליצור עדכון מצטבר של מערכת ההפעלה:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

הגדרת מחיצות

אפשר לעדכן כל זוג של מחיצות A/B שמוגדרות באותו דיסק.update_engine לזוג מחיצות יש קידומת משותפת (כמו system או boot) וסיומת לכל משבצת (כמו _a). רשימת המחיצות שעבורן גנרטור המטען הייעודי (payload) מגדיר עדכון מוגדרת על ידי המשתנה AB_OTA_PARTITIONS make.

לדוגמה, אם כלולות זוג מחיצות bootloader_a ו-booloader_b (_a ו-_b הם הסיומות של המשבצות), אפשר לעדכן את המחיצות האלה על ידי ציון ההגדרות הבאות בתצורת המוצר או הלוח:

AB_OTA_PARTITIONS := \
  boot \
  system \
  bootloader

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

הגדרות אחרי ההתקנה

אפשר להגדיר את השלב שאחרי ההתקנה באופן שונה לכל מחיצה מעודכנת באמצעות קבוצה של צמדים של מפתח וערך. כדי להריץ תוכנית שנמצאת בנתיב /system/usr/bin/postinst בתמונה חדשה, צריך לציין את הנתיב ביחס לשורש של מערכת הקבצים במחיצת המערכת.

לדוגמה, הערך של usr/bin/postinst הוא system/usr/bin/postinst (אם לא משתמשים בדיסק RAM). בנוסף, מציינים את סוג מערכת הקבצים להעברה לקריאת המערכת mount(2). מוסיפים את הטקסט הבא לקובצי .mk של המוצר או המכשיר (אם רלוונטי):

AB_OTA_POSTINSTALL_CONFIG += \
  RUN_POSTINSTALL_system=true \
  POSTINSTALL_PATH_system=usr/bin/postinst \
  FILESYSTEM_TYPE_system=ext4

קומפילציה של אפליקציות

אפשר להדר אפליקציות ברקע לפני ההפעלה מחדש עם קובץ אימג' של המערכת החדש. כדי לקמפל אפליקציות ברקע, מוסיפים את השורה הבאה להגדרת המכשיר של המוצר (ב-device.mk של המוצר):

  1. כדי לוודא שסקריפט ההידור וקובצי הבינאריים עוברים הידור ונכללים בקובץ אימג' של המערכת, צריך לכלול את הרכיבים המקוריים ב-build.
      # A/B OTA dexopt package
      PRODUCT_PACKAGES += otapreopt_script
    
  2. מחברים את סקריפט ההידור אל update_engine כך שהוא יפעל כשלב אחרי ההתקנה.
      # A/B OTA dexopt update_engine hookup
      AB_OTA_POSTINSTALL_CONFIG += \
        RUN_POSTINSTALL_system=true \
        POSTINSTALL_PATH_system=system/bin/otapreopt_script \
        FILESYSTEM_TYPE_system=ext4 \
        POSTINSTALL_OPTIONAL_system=true
    

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