אופטימיזציה של זמן האתחול

בדף הזה מפורטים טיפים לשיפור זמן האתחול.

הסרת סמלים לניפוי באגים ממודולים

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

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

הסרת סמל ניפוי הבאגים מהמודולים עשויה לחסוך כמה שניות במהלך האתחול.

הסרת סמלים מופעלת כברירת מחדל ב-build של פלטפורמת Android, אבל כדי להפעיל אותה באופן מפורש, צריך להגדיר את BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES בהגדרה הספציפית למכשיר בנתיב device/vendor/device.

שימוש בדחיסת LZ4 עבור ליבת המערכת ו-ramdisk

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

נוספה תמיכה בדחיסת ramdisk ב-LZ4 ב-build של פלטפורמת Android דרך BOARD_RAMDISK_USE_LZ4. אפשר להגדיר את האפשרות הזו בהגדרות הספציפיות למכשיר. אפשר להגדיר דחיסה של הליבה באמצעות defconfig של הליבה.

מעבר ל-LZ4 אמור להקטין את זמן האתחול ב-500 אלפיות השנייה עד שנייה אחת.

הימנעו מרישום יתר ביומנים של הדרייברים

ב-ARM64 וב-ARM32, בקשות להפעלת פונקציה שנמצאות במרחק גדול ממיקום הקריאה צריכות טבלת קפיצות (שנקראת טבלת קישור של פרוצדורה, או PLT) כדי שיהיה אפשר לקודד את כתובת הקפיצה המלאה. המודולים נטענים באופן דינמי, ולכן צריך לתקן את טבלאות הדילוג האלה במהלך טעינת המודול. השיחות שצריך להעביר נקראות רשומות העברה עם תוספות מפורשות (או RELA, בקיצור) בפורמט ELF.

ליבת ה-Linux מבצעת אופטימיזציה של גודל הזיכרון (למשל אופטימיזציה של פגיעה במטמון) כשמוקצה PLT. עם הקומט הזה במעלה הזרם, מורכבות תוכנית האופטימיזציה היא O(N^2), כאשר N הוא מספר ה-RELAs מסוג R_AARCH64_JUMP26 או R_AARCH64_CALL26. לכן, כדאי להשתמש בפחות כללי RELA מהסוגים האלה כדי לקצר את זמן הטעינה של המודול.

דפוס נפוץ של קידוד שמגדיל את מספר ה-RELA של R_AARCH64_CALL26 או R_AARCH64_JUMP26 הוא רישום יתר ביומן בכלי לניהול התקנים. כל קריאה ל-printk() או לכל סכימת רישום אחרת בדרך כלל מוסיפה רשומת RELA של CALL26/JUMP26. בטקסט של הקומיט בקומיט במעלה הזרם, אפשר לראות שגם אחרי האופטימיזציה, טעינת ששת המודולים נמשכת כ-250 אלפיות השנייה – הסיבה לכך היא שששת המודולים האלה היו ששת המודולים המובילים עם הכי הרבה רישום ביומן.

הפחתת הרישום ביומן יכולה לחסוך בערך 100 עד 300 אלפיות השנייה בזמני האתחול, בהתאם לכמות הרישום ביומן שקיימת.

הפעלה סלקטיבית של בדיקה אסינכרונית

כשמודול נטען, אם המכשיר שהוא תומך בו כבר אוכלס מ-DT (עץ המכשירים) והוסף לליבת מנהל ההתקן, בדיקת המכשיר מתבצעת בהקשר של הקריאה module_init(). כשבדיקת מכשיר מתבצעת בהקשר של module_init(), הטעינה של המודול לא יכולה להסתיים עד שהבדיקה מסתיימת. מכיוון שטעינת המודולים היא ברובה טורית, מכשיר שלוקח לו זמן יחסית ארוך לבצע בדיקה מאט את זמן האתחול.

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

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

כדי להפעיל בדיקה אסינכרונית של מודול, לא מספיק להגדיר רק את הדגל PROBE_PREFER_ASYNCHRONOUS בקוד של מנהל ההתקן. בנוסף, כשמדובר במודולים, צריך להוסיף את module_name.async_probe=1 בשורת הפקודה של ליבת המערכת או להעביר את async_probe=1 כפרמטר של מודול כשמטעינים את המודול באמצעות modprobe או insmod.

הפעלת בדיקה אסינכרונית יכולה לחסוך כ-100 עד 500 אלפיות השנייה בזמני האתחול, בהתאם לחומרה ולמנהלי ההתקנים.

בדיקה של מנהל ההתקן של CPUfreq מוקדם ככל האפשר

ככל שהבדיקה של מנהל ההתקן CPUfreq מתבצעת מוקדם יותר, כך אפשר להגדיל את תדירות יחידת העיבוד המרכזית (CPU) למקסימום (או למקסימום מוגבל מבחינה תרמית) מוקדם יותר במהלך האתחול. ככל שהמעבד מהיר יותר, כך האתחול מהיר יותר. ההנחיה הזו רלוונטית גם לdevfreq מנהלי התקנים ששולטים בתדר של DRAM, זיכרון וקישוריות.

כשמשתמשים במודולים, סדר הטעינה יכול להיות תלוי ברמה initcall ובסדר ההידור או הקישור של הדרייברים. משתמשים בכינוי MODULE_SOFTDEP() כדי לוודא שמנהל ההתקן cpufreq הוא בין המודולים הראשונים שנטענים.

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

החיסכון שמתקבל מבדיקה מוקדמת של מנהל ההתקן CPUfreq יכול להיות קטן מאוד או גדול מאוד, בהתאם למועד שבו אפשר לבדוק את מנהל ההתקן ולתדירות שבה תוכנת האתחול משאירה את המעבדים (CPU) ב.

העברת מודולים לאתחול בשלב השני, למחיצת הספק או למחיצת הספק של DLKM

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

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

טוענים את מנהלי ההתקנים החיוניים הבאים:

  • watchdog
  • reset
  • cpufreq

כדי לשחזר את המערכת ולעבור למצב fastbootd של מרחב המשתמש, בשלב הראשון של הפעלת init נדרש חיפוש של עוד מכשירים (כמו USB) ותצוגה. שומרים עותק של המודולים האלה ב-ramdisk של השלב הראשון ובמחיצה של הספק או של vendor_dlkm. כך אפשר לטעון אותם בשלב הראשון של האתחול לצורך שחזור או fastbootd תהליך האתחול. עם זאת, המודולים של מצב השחזור לא נטענים בשלב הראשון של ההפעלה במהלך תהליך הפעלה רגיל. אפשר לדחות את המודולים של מצב השחזור לשלב השני של ההפעלה כדי לקצר את זמן האתחול. כל המודולים האחרים שלא נדרשים בשלב הראשון של ההפעלה צריכים להיות מועברים למחיצה של הספק או vendor_dlkm.

בהינתן רשימה של מכשירי עלים (לדוגמה, UFS או סדרתי), dev needs.sh הסקריפט מוצא את כל מנהלי ההתקנים, המכשירים והמודולים שנדרשים לתלות או לספקים (לדוגמה, שעונים, רגולטורים או gpio) כדי לבצע בדיקה.

העברת מודולים לאתחול בשלב השני מקצרת את זמני האתחול בדרכים הבאות:

  • הקטנת הגודל של Ramdisk.
    • כך קריאות ה-flash מהירות יותר כשה-bootloader טוען את ה-ramdisk (שלב אתחול בסדר).
    • התוצאה היא מהירויות מהירות יותר של ביטול הדחיסה כשהליבה מבטלת את הדחיסה של ה-ramdisk (שלב אתחול בסדר).
  • האתחול בשלב השני פועל במקביל, כך שזמן הטעינה של המודול מוסתר על ידי העבודה שמתבצעת באתחול בשלב השני.

העברת מודולים לשלב השני יכולה לחסוך 500 עד 1,000 אלפיות השנייה בזמני האתחול, בהתאם למספר המודולים שאפשר להעביר לאתחול בשלב השני.

לוגיסטיקה של טעינת מודולים

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

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES. רשימת המודולים שיועתקו ל-ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD. רשימת המודולים שצריך לטעון בשלב הראשון של ההפעלה.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD. רשימת המודולים שייטענו כשבוחרים באפשרות שחזור או fastbootd מתוך ה-ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES. רשימת המודולים שיועתקו למחיצה של הספק או vendor_dlkm בתיקייה /vendor/lib/modules/.
  • BOARD_VENDOR_KERNEL_MODULES_LOAD. רשימת המודולים שצריך לטעון בשלב השני של ההפעלה.

בנוסף, צריך להעתיק את מודולי האתחול והשחזור ב-ramdisk למחיצת הספק או vendor_dlkm ב-/vendor/lib/modules. העתקת המודולים האלה למחיצת הספק מוודאת שהמודולים לא יהיו בלתי נראים במהלך האתחול בשלב השני, וזה שימושי לניפוי באגים ולאיסוף modinfo לדוחות באגים.

השכפול צריך לתפוס נפח מינימלי אצל הספק או vendor_dlkmבמחיצה, בתנאי שערכת מודול האתחול מצומצמת. מוודאים שבקובץ modules.list של הספק יש רשימה מסוננת של מודולים ב-/vendor/lib/modules. הרשימה המסוננת מבטיחה שזמני האתחול לא יושפעו מהטעינה מחדש של המודולים (תהליך יקר).

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

אפשר להשתמש בקבצים של המכשיר Board.Config.mk כדי לבצע את הפעולות האלה, כמו בדוגמה הבאה:

# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)

# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))

# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
     $(filter $(BOOT_KERNEL_MODULES_FILTER) \
                $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
#     $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
        $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
            $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
        $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
    $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
    $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
    $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))

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

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

אפשר להתעלם מטעינה של מודול ניפוי באגים שנכשלת ולא מופיעה בגרסאות למשתמשים. כדי להתעלם מהכשל הזה, מגדירים את המאפיין vendor.device.modules.ready כך שיפעיל שלבים מאוחרים יותר בתהליך האתחול של סקריפטים של init rc כדי להמשיך למסך ההפעלה. אם הקוד הבא מופיע ב-/vendor/etc/init.insmod.sh, אפשר להשתמש בסקריפט לדוגמה הבא:

#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
  cfg_file=$1
else
  # Set property even if there is no insmod config
  # to unblock early-boot trigger
  setprop vendor.common.modules.ready
  setprop vendor.device.modules.ready
  exit 1
fi

if [ -f $cfg_file ]; then
  while IFS="|" read -r action arg
  do
    case $action in
      "insmod") insmod $arg ;;
      "setprop") setprop $arg 1 ;;
      "enable") echo 1 > $arg ;;
      "modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
     . . .
    esac
  done < $cfg_file
fi

בקובץ hardware rc, אפשר לציין את השירות one shot באמצעות:

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

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

כדי לשפר את זמני האתחול הנראים לעין, אפשר לבחור באופן ספציפי מודולים בשירות טעינת המודולים שמתאימים יותר לטעינה אחרי מסך ההפעלה. לדוגמה, אפשר להגדיר טעינה מאוחרת מפורשת של מודולים של מפענח וידאו או Wi-Fi אחרי שסיימתם את תהליך האתחול (sys.boot_complete אות מאפיין של Android, לדוגמה). מוודאים שרכיבי HAL לטעינה מאוחרת של מודולים חוסמים מספיק זמן כשאין מנהלי התקנים של ליבת המערכת.

לחלופין, אפשר להשתמש בפקודה wait<file>[<timeout>] של init בסקריפט rc של תהליך האתחול כדי להמתין עד שערכים נבחרים של sysfs יראו שמודולי הדרייבר סיימו את פעולות הבדיקה. דוגמה לכך היא המתנה עד שטעינת מנהל ההתקן של התצוגה תושלם ברקע של השחזור או של fastbootd, לפני הצגת הגרפיקה של התפריט.

הפעלת תדר המעבד (CPU) לערך סביר בתוכנת האתחול

יכול להיות שלא כל המערכות על שבב (SoC) או המוצרים יוכלו להפעיל את המעבד בתדר הגבוה ביותר בגלל בעיות שקשורות לטמפרטורה או להספק במהלך בדיקות של לולאות אתחול. עם זאת, חשוב לוודא שתוכנת האתחול מגדירה את התדירות של כל המעבדים המקוונים לרמה הגבוהה ביותר שאפשר להגדיר בבטחה עבור SoC או מוצר. זה חשוב מאוד כי אם הגרעין מודולרי לחלוטין, הפעולה של ביטול הדחיסה של init ramdisk מתרחשת לפני שניתן לטעון את מנהל ההתקן של CPUfreq. לכן, אם טוען האתחול משאיר את המעבד בקצה התחתון של התדירות שלו, זמן הפריסה של ramdisk יכול להיות ארוך יותר מליבת מערכת שהקומפילציה שלה סטטית (אחרי התאמה להבדל בגודל ramdisk), כי תדירות המעבד תהיה נמוכה מאוד כשמבצעים עבודה שדורשת הרבה משאבי מעבד (פריסה). אותו עיקרון חל על הזיכרון ועל תדירות הקישוריות.

אתחול תדר המעבד של מעבדים גדולים בתוכנת האתחול

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

חשוב לוודא שהמעבדים הגדולים לפחות יעילים כמו המעבדים הקטנים בתדר שבו תוכנת האתחול משאירה אותם. לדוגמה, אם הביצועים של המעבד הגדול טובים פי 2 מהביצועים של המעבד הקטן באותה תדירות, אבל טוען האתחול מגדיר את התדירות של המעבד הקטן ל-1.5GHz ואת התדירות של המעבד הגדול ל-300MHz, אז ביצועי האתחול ירדו אם ליבת מערכת ההפעלה תעביר שרשור למעבד הגדול. בדוגמה הזו, אם אפשר להפעיל את המעבד הגדול ב-750MHz, כדאי לעשות זאת גם אם אתם לא מתכננים להשתמש בו באופן מפורש.

מנהלי התקנים לא צריכים לטעון קושחה באתחול בשלב הראשון

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