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

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

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

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

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

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

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

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

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

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

מעבר ל-LZ4 אמור לתת זמן אתחול מהיר יותר של 500ms עד 1000ms.

הימנע מכניסה מוגזמת למנהלי התקנים שלך

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • כֶּלֶב שְׁמִירָה
  • אִתחוּל
  • cpufreq

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

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

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

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

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

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

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

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

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

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

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

אתחל את תדר המעבד לערך סביר במטען האתחול

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

אתחול תדירות מעבד של מעבדים גדולים במטען האתחול

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

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

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

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