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

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

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

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
    
  • כדי ליצור עדכון מצטבר של OTA:
    ./build/make/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip \
        incremental_ota_update.zip
    

הגדרת מחיצות

update_engine יכול לעדכן כל זוג של מחיצות A/B שמוגדרות באותו דיסק. לזוג מחיצות יש קידומת משותפת (למשל 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 באתחול הראשון.