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

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

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

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

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

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

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

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

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

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

המעבר ל-LZ4 אמור להביא לקיצור זמן האתחול ב-500ms עד 1,000ms.

נמנעים מהתחברות מוגזמת לנהגים

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • watchdog
  • reset
  • cpufreq

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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