בדף הזה מפורטים טיפים לשיפור זמן האתחול.
הסרת סמלים לניפוי באגים ממודולים
בדומה לאופן שבו מסירים סמלי ניפוי באגים מהליבה במכשיר ייצור, חשוב להסיר גם את סמלי ניפוי הבאגים מהמודולים. הסרת סמלי ניפוי באגים ממודולים עוזרת לקצר את זמן האתחול על ידי צמצום של:
- הזמן שלוקח לקרוא את הקבצים הבינאריים מהזיכרון.
- הזמן שלוקח לבטל את הדחיסה של ה-ramdisk.
- הזמן שלוקח לטעון את המודולים.
הסרת סמל ניפוי הבאגים מהמודולים עשויה לחסוך כמה שניות במהלך האתחול.
הסרת סמלים מופעלת כברירת מחדל בגרסת ה-build של פלטפורמת Android, אבל כדי להפעיל אותה באופן מפורש, צריך להגדיר את BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES בקובץ ההגדרות הספציפי למכשיר, בתיקייה device/vendor/device.
שימוש בדחיסת LZ4 עבור ליבת מערכת ההפעלה ודיסק ה-RAM
Gzip יוצר פלט דחוס קטן יותר בהשוואה ל-LZ4, אבל LZ4 מבצע דחיסה מהירה יותר מ-Gzip. במקרה של ליבת המערכת והמודולים, הצמצום המוחלט בגודל האחסון כתוצאה משימוש ב-Gzip לא משמעותי בהשוואה ליתרון בזמן הפתיחה של LZ4.
נוספה תמיכה בדחיסת ramdisk של LZ4 בגרסת ה-build של פלטפורמת Android דרך BOARD_RAMDISK_USE_LZ4. אפשר להגדיר את האפשרות הזו בקובץ ההגדרות הספציפי למכשיר. אפשר להגדיר דחיסה של הליבה באמצעות defconfig של הליבה.
מעבר ל-LZ4 אמור להקטין את זמן האתחול ב-500 אלפיות השנייה עד 1,000 אלפיות השנייה.
הימנעו מרישום יתר ביומנים של הדרייברים
ב-ARM64 וב-ARM32, בקשות להפעלת פונקציות שנמצאות במרחק גדול ממיקום הקריאה צריכות טבלת קפיצות (שנקראת טבלת קישור של פרוצדורות, או PLT) כדי שיהיה אפשר לקודד את כתובת הקפיצה המלאה. המודולים נטענים באופן דינמי, ולכן צריך לתקן את טבלאות הדילוג האלה במהלך טעינת המודול. השיחות שצריך להעביר נקראות רשומות העברה עם תוספות מפורשות (או RELA בקיצור) בפורמט ELF.
ליבת ה-Linux מבצעת אופטימיזציה של גודל הזיכרון (למשל אופטימיזציה של פגיעה במטמון) כשמוקצה PLT. עם הקומט הזה במעלה הזרם, מורכבות תוכנית האופטימיזציה היא O(N^2), כאשר N הוא מספר ה-RELA מסוג R_AARCH64_JUMP26 או R_AARCH64_CALL26. לכן, כדאי להשתמש בפחות כללי RELA מהסוגים האלה כדי לקצר את זמן הטעינה של המודול.
דפוס נפוץ של קידוד שמגדיל את מספר ה-RELA של R_AARCH64_CALL26 או R_AARCH64_JUMP26 הוא רישום יתר ביומן בכלי לניהול התקנים. כל קריאה ל-printk() או לכל סכימת רישום אחרת בדרך כלל מוסיפה רשומה של CALL26/JUMP26 RELA. בטקסט של הקומיט בקומיט במעלה הזרם, אפשר לראות שגם אחרי האופטימיזציה, טעינת ששת המודולים אורכת כ-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 מתבצעת מוקדם יותר, כך אפשר להגדיל את תדירות המעבד למקסימום (או למקסימום מוגבל מבחינת טמפרטורה) מוקדם יותר במהלך האתחול. ככל שהמעבד מהיר יותר, כך האתחול מהיר יותר. ההנחיה הזו חלה גם על מנהלי התקנים של devfreq
ששולטים ב-DRAM, בזיכרון ובתדר של הקישוריות.
במודולים, סדר הטעינה יכול להיות תלוי ברמה initcall ובסדר ההידור או הקישור של מנהלי ההתקנים. משתמשים בכינוי MODULE_SOFTDEP() כדי לוודא שמנהל ההתקן cpufreq הוא בין המודולים הראשונים שנטענים.
בנוסף לטעינה מוקדמת של המודול, צריך גם לוודא שכל התלויות בבדיקה של מנהל ההתקן CPUfreq נבדקו גם הן. לדוגמה, אם אתם צריכים שעון או ידית לוויסות כדי לשלוט בתדירות של המעבד, ודאו שהם נבדקים קודם. או שאולי צריך לטעון מנהלי התקנים תרמיים לפני מנהל ההתקן של CPUfreq אם יכול להיות שהמעבדים יתחממו מדי במהלך האתחול. לכן, כדאי לעשות כל מה שאפשר כדי לוודא שהבדיקה של מנהלי ההתקנים של CPUfreq ו-devfreq הרלוונטיים תתבצע מוקדם ככל האפשר.
החיסכון שמתקבל מבדיקה מוקדמת של מנהל ההתקן CPUfreq יכול להיות קטן מאוד או גדול מאוד, בהתאם למידת המוקדמות של הבדיקה ולתדירות שבה טוען האתחול משאיר את יחידות העיבוד המרכזיות.
העברת מודולים לאתחול בשלב השני, למחיצת הספק או למחיצת vendor_dlkm
תהליך האתחול של השלב הראשון הוא סדרתי, ולכן אין הרבה אפשרויות להפעיל את תהליך האתחול במקביל. אם מודול מסוים לא נדרש כדי שהשלב הראשון של ההפעלה יסתיים, צריך להעביר את המודול לשלב השני של ההפעלה על ידי הצבתו במחיצה של הספק או vendor_dlkm.
בשלב הראשון של ההפעלה לא נדרש לבדוק כמה מכשירים כדי להגיע לשלב השני של ההפעלה. נדרשות רק יכולות של מסוף ואחסון פלאש כדי לבצע את תהליך האתחול הרגיל.
טוענים את מנהלי ההתקנים החיוניים הבאים:
watchdogresetcpufreq
כדי לבצע שחזור ולהיכנס למצב fastbootd של מרחב משתמש, בשלב הראשון של init נדרש חיפוש של עוד מכשירים (כמו USB) ותצוגה. שומרים עותק של המודולים האלה ב-ramdisk של השלב הראשון ובמחיצה של הספק או של vendor_dlkm. כך אפשר לטעון אותם בשלב הראשון של האתחול לצורך שחזור או fastbootd תהליך האתחול. עם זאת,
אל תטען את המודולים של מצב השחזור בשלב הראשון של האתחול במהלך אתחול רגיל. אפשר לדחות את המודולים של מצב השחזור עד לאתחול בשלב השני כדי לקצר את זמן האתחול. כל המודולים האחרים שלא נדרשים בשלב הראשון של ההפעלה צריכים להיות מועברים למחיצה של הספק או vendor_dlkm.
בהינתן רשימה של מכשירי עלים (לדוגמה, UFS או סדרתי),
dev needs.sh
הסקריפט מוצא את כל מנהלי ההתקנים, המכשירים והמודולים שנדרשים לתלות או
לספקים (לדוגמה, שעונים, רגולטורים או gpio) כדי לבדוק.
העברת מודולים לאתחול בשלב השני מקצרת את זמני האתחול בדרכים הבאות:
- הקטנת הגודל של Ramdisk.
- כך קריאות ה-flash מהירות יותר כשה-bootloader טוען את ה-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 שקל יותר לנהל, שצריך לציין באופן מקומי בקובצי ההגדרות של הלוח. הסקריפט הקודם מאתר וממלא כל אחד ממודולי קבוצת המשנה מתוך מודולי הליבה הזמינים שנבחרו, ומשאיר את המודולים הנותרים לאתחול בשלב השני.
באתחול של השלב השני, מומלץ להריץ את טעינת המודול כשירות כדי שלא לחסום את תהליך האתחול. משתמשים בסקריפט מעטפת כדי לנהל את טעינת המודול, כך שאפשר לדווח על פעולות לוגיסטיות אחרות (או להתעלם מהן) לפי הצורך, כמו טיפול בשגיאות, צמצום הסיכון לשגיאות או השלמת טעינת המודול.
אפשר להתעלם מכשל בטעינת מודול ניפוי באגים שלא מופיע בגרסאות למשתמשים.
כדי להתעלם מהכשל הזה, מגדירים את המאפיין 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 ספציפית, כך שהמודולים ייטענו רק כשהשכבה הזו מופעלת.
כדי לשפר את זמני האתחול הנראים לעין, אתם יכולים לבחור באופן ספציפי מודולים בשירות טעינת המודולים שמתאימים יותר לטעינה אחרי מסך ההפעלה. לדוגמה, אפשר לבצע טעינה מאוחרת מפורשת של המודולים של מפענח הווידאו או של Wi-Fi אחרי שזרם האתחול של init נוקה (sys.boot_completeאות מאפיין של Android, לדוגמה). מוודאים שרכיבי ה-HAL של המודולים לטעינה מאוחרת חוסמים מספיק זמן כשאין מנהלי התקנים של ליבת המערכת.
לחלופין, אפשר להשתמש בפקודה wait<file>[<timeout>] של init בסקריפט rc של תהליך האתחול כדי להמתין עד שיופיעו ערכים נבחרים של sysfs שמעידים על כך שמודולי הדרייבר השלימו את פעולות הבדיקה. דוגמה לכך היא המתנה עד שטעינת מנהל ההתקן של המסך תושלם ברקע של השחזור או של fastbootd, לפני הצגת גרפיקה של התפריט.
הפעלת תדר המעבד (CPU) לערך סביר בתוכנת האתחול
יכול להיות שלא כל המערכות על שבב (SoC) או המוצרים יוכלו להפעיל את המעבד בתדר הגבוה ביותר בגלל בעיות שקשורות לטמפרטורה או להספק במהלך בדיקות של לולאות אתחול. עם זאת, חשוב לוודא שתוכנת האתחול מגדירה את התדירות של כל המעבדים המקוונים לרמה הגבוהה ביותר שאפשר להגדיר בבטחה עבור SoC או מוצר. זה חשוב מאוד כי אם הגרעין מודולרי לחלוטין, הפעולה של ביטול הדחיסה של init ramdisk מתבצעת לפני שניתן לטעון את מנהל ההתקן של CPUfreq. לכן, אם טוען האתחול משאיר את המעבד בקצה התחתון של התדירות שלו, זמן הפתיחה של ramdisk יכול להיות ארוך יותר מליבת מערכת שהקומפילציה שלה היא סטטית (אחרי התאמה להבדל בגודל ramdisk), כי תדירות המעבד תהיה נמוכה מאוד כשמבצעים עבודה שדורשת הרבה משאבי מעבד (פתיחת ramdisk). אותו עיקרון חל על הזיכרון ועל תדירות הקישוריות.
אתחול תדר המעבד של מעבדים גדולים בתוכנת האתחול
לפני שהדרייבר CPUfreq נטען, ליבת המערכת לא מודעת לתדרים של המעבד, ולא משנה את קיבולת התזמון של המעבד בהתאם לתדר הנוכחי. יכול להיות שהליבה תעביר את השרשורים למעבד הגדול אם העומס על המעבד הקטן יהיה גבוה מספיק.
חשוב לוודא שהמעבדים הגדולים לפחות יעילים כמו המעבדים הקטנים בתדר שבו טוען האתחול משאיר אותם. לדוגמה, אם מעבד ה-big מהיר פי 2 ממעבד ה-little באותה תדירות, אבל טוען האתחול מגדיר את התדירות של מעבד ה-little ל-1.5GHz ואת התדירות של מעבד ה-big ל-300MHz, אז ביצועי האתחול ירדו אם ליבת מערכת ההפעלה תעביר שרשור למעבד ה-big. בדוגמה הזו, אם אפשר להפעיל את המעבד הגדול ב-750MHz, כדאי לעשות זאת גם אם אתם לא מתכננים להשתמש בו באופן מפורש.
מנהלי התקנים לא צריכים לטעון קושחה באתחול בשלב הראשון
יכולים להיות מקרים שבהם אין ברירה אלא לטעון את הקושחה בשלב הראשון של ההפעלה. אבל באופן כללי, מנהלי התקנים לא אמורים לטעון קושחה בשלב הראשון של ההפעלה, במיוחד בהקשר של בדיקת מכשיר. טעינת קושחה באתחול בשלב הראשון גורמת לתהליך האתחול כולו להיעצר אם הקושחה לא זמינה ב-ramdisk בשלב הראשון. גם אם הקושחה קיימת ב-ramdisk של השלב הראשון, היא עדיין גורמת לעיכוב מיותר.