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

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

הטמעת HAL לבקרת האתחול

מנהלי אתחול עם תמיכה ב-A/B חייבים להטמיע את ה-HAL של boot_control ב-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. מוסיפים את אישור ה-X509 שמכיל את המפתח הציבורי לאוסף המפתחות של המערכת:
    1. מעתיקים את אישור ה-X509 בפורמט .der לספריית הבסיס (root) של kernel. אם אישור ה-X509 בפורמט של קובץ .pem, משתמשים בפקודה openssl הבאה כדי להמיר אותו מפורמט .pem לפורמט .der:
      openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. יוצרים את zImage כך שיכלול את האישור כחלק מ-keychain המערכת. כדי לוודא,בודקים את הרשומה 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
      הכללה מוצלחת של אישור X509 .מציינת את נוכחות המפתח הציבורי במפתחות המערכת (הדגשה מציינת את מזהה המפתח הציבורי).
    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
אופציונלי לגרסאות build לניפוי באגים 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.

ארגומנטים של משבצות בליבה

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

  • 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). רשימת המחיצות שלגביהן ה-generator של עומס העבודה מגדיר עדכון מוגדרת על ידי משתנה ה-make‏ AB_OTA_PARTITIONS.

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